Узким местом, вероятно, является не вызов Completed()
.
На x86 чтение из выровненного по слову uint32_t
автоматически является атомарной операцией, std::atomic
или нет. Единственное, что std::atomic
делает для uint32_t
на x86, это гарантирует, что оно выровнено по словам, и что компилятор не меняет его порядок и не оптимизирует его.
Нагрузка с ограниченным контуром не является причиной конфликта шины. При первом чтении будет отсутствовать кеш, но последующие загрузки будут попадать в кеш до тех пор, пока кеш не станет недействительным из-за записи в адрес из другого потока. Есть предостережение - случайное разделение строк кэша («ложное разделение»). Одна идея о том, как вы можете исключить эту возможность, переключившись на массив с 60 байтами неиспользованного дополнения с обеих сторон вашего атома (используйте только средний).
std::atomic<uint32_t> m_buffered[31]; std::atomic<uint_32t>& m_completed = m_buffered[15];
Имейте в виду, что тесная петля свяжет одно из ваших ядер, ничего не делая, кроме как просматривая его кеш. Это пустая трата денег ...;) Это вполне может быть причиной вашей проблемы. Вы должны изменить свой код так:
int m_completed = 0; // no longer atomic
std::condition_variable cv;
// in main...(pseudocode)
lock (unique) m_mutex // the m_mutex from the class
while !Completed()
cv.wait(m_mutex)
// in thread (pseudocode)
bool toSignal = false;
lock guard m_mutex
this->m_result.insert(std::end(this->m_result),
std::begin(total_payload),
std::end(total_payload));
++m_completed;
toSignal = Completed();
if toSignal
cv.signalOne()
Также возможно, что потеря производительности связана с критической секцией мьютекса. Этот критический раздел потенциально может быть на много порядков длиннее, чем пропадание кэша. Я бы порекомендовал сравнить время для 1 потока, 2 потоков и 4 потоков в пуле потоков. если 2 потока не быстрее, чем 1 поток, то ваш код по существу работает последовательно.
Как измерить? Инструменты профилирования хороши, когда вы не знаете, что оптимизировать. У меня нет большого опыта работы с ними, но я знаю, что (по крайней мере, некоторые из старых) могут стать немного отрывочными, когда дело доходит до многопоточности. Вы также можете использовать старый добрый таймер. C ++ 11 имеет high_resolution_clock, который, вероятно, имеет разрешение в 1 микросекунды, если у вас приличное оборудование.
Наконец, я вижу много возможностей для алгоритмической / скалярной оптимизации. Предварительно выделяйте векторы вместо того, чтобы делать это каждый раз. Используйте указатели или std :: move, чтобы избежать ненужных глубоких копий. Предварительно распределите m_result и сделайте так, чтобы потоки записывали в определенные смещения индекса.