До сих пор я использовал std::queue
в своем проекте. Я измерил среднее время, которое требуется конкретной операции в этой очереди.
Время было измерено на 2 машинах: Моя локальная Ubuntu VM и удаленный сервер.
Используя std::queue
, среднее значение было почти одинаковым на обеих машинах: ~ 750 микросекунд.
Затем я "обновил" std::queue
до boost::lockfree::spsc_queue
, чтобы я мог избавиться от мьютексов, защищающих очередь. На моей локальной виртуальной машине я видел огромный прирост производительности, а средний показатель составляет 200 микросекунд. Однако на удаленной машине среднее значение достигало 800 микросекунд, что было медленнее, чем раньше.
Сначала я подумал, что это может быть связано с тем, что удаленная машина может не поддерживать реализацию без блокировки:
Не все аппаратные средства поддерживают один и тот же набор атомных инструкций. Если он недоступен на оборудовании, его можно эмулировать в программном обеспечении с помощью защитных устройств. Однако это имеет очевидный недостаток в потере функции блокировки.
Чтобы узнать, поддерживаются ли эти инструкции, boost::lockfree::queue
имеет метод bool is_lock_free(void) const;
.
Тем не менее, boost::lockfree::spsc_queue
не имеет такой функции, что для меня подразумевает, что он не полагается на аппаратное обеспечение и всегда свободен - на любой машине.
Что может быть причиной потери производительности?
Код Exmple (Производитель/Потребитель)
// c++11 compiler and boost library required
#include <iostream>
#include <cstdlib>
#include <chrono>
#include <async>
#include <thread>
/* Using blocking queue:
* #include <mutex>
* #include <queue>
*/
#include <boost/lockfree/spsc_queue.hpp>
boost::lockfree::spsc_queue<int, boost::lockfree::capacity<1024>> queue;
/* Using blocking queue:
* std::queue<int> queue;
* std::mutex mutex;
*/
int main()
{
auto producer = std::async(std::launch::async, [queue /*,mutex*/]()
{
// Producing data in a random interval
while(true)
{
/* Using the blocking queue, the mutex must be locked here.
* mutex.lock();
*/
// Push random int (0-9999)
queue.push(std::rand() % 10000);
/* Using the blocking queue, the mutex must be unlocked here.
* mutex.unlock();
*/
// Sleep for random duration (0-999 microseconds)
std::this_thread::sleep_for(std::chrono::microseconds(rand() % 1000));
}
}
auto consumer = std::async(std::launch::async, [queue /*,mutex*/]()
{
// Example operation on the queue.
// Checks if 1234 was generated by the producer, returns if found.
while(true)
{
/* Using the blocking queue, the mutex must be locked here.
* mutex.lock();
*/
int value;
while(queue.pop(value)
{
if(value == 1234)
return;
}
/* Using the blocking queue, the mutex must be unlocked here.
* mutex.unlock();
*/
// Sleep for 100 microseconds
std::this_thread::sleep_for(std::chrono::microseconds(100));
}
}
consumer.get();
std::cout << "1234 was generated!" << std::endl;
return 0;
}