#c #multithreading #c 11 #benchmarking #stdthread
#c #многопоточность #c 11 #сравнительный анализ #stdthread
Вопрос:
Я экспериментирую со стандартными потоками C . Я написал небольшой тест для проверки накладных расходов на производительность и общей пропускной способности. Принцип заключается в том, чтобы запускать в одном или нескольких потоках цикл из 1 миллиарда итераций, время от времени делая небольшую паузу.
В первой версии я использовал счетчики в разделяемой памяти (т. Е. Обычные переменные). Я ожидал следующего вывода:
Sequential 1e 009 loops 4703 ms 212630 loops/ms 2 thrds:t1 1e 009 loops 4734 ms 211238 loops/ms 2 thrds:t2 1e 009 loops 4734 ms 211238 loops/ms 2 thrds:tt 2e 009 loops 4734 ms 422476 loops/ms manythrd tn 1e 009 loops 7094 ms 140964 loops/ms ... manythrd tt 6e 009 loops 7094 ms 845785 loops/ms
К сожалению, на дисплее отображались некоторые счетчики, как если бы они были неинициализированы!
Я мог бы решить проблему, сохранив конечное значение каждого счетчика в atomic<>
для последующего отображения. Однако я не понимаю, почему версия, основанная на простой разделяемой памяти, не работает должным образом: каждый поток использует свой собственный счетчик, поэтому условия гонки отсутствуют. Даже поток отображения обращается к счетчикам только после завершения подсчета потоков. Использование volatile
тоже не помогло.
Может ли кто-нибудь объяснить мне это странное поведение (как будто память не обновлялась) и сказать мне, пропустил ли я что-то?
Здесь общие переменные:
const int maxthread = 6;
atomic<bool> other_finished = false;
atomic<long> acounter[maxthread];
Здесь приведен код потоковой функции:
void foo(longamp; count, int ic, long maxcount)
{
count = 0;
while (count < maxcount) {
count ;
if (count % 10000000 == 0)
this_thread::sleep_for(chrono::microseconds(1));
}
other_finished = true; // atomic: announce work is finished
acounter[ic] = count; // atomic: share result
}
Вот пример того, как я вызываю тесты потоков:
mytimer.on(); // second run, two threadeds
thread t1(foo, counter[0], 0, maxcount); // additional thread
foo(counter[1], 1, maxcount); // main thread
t1.join(); // wait end of additional thread
perf = mytimer.off();
display_perf("2 thrds:t1", counter[0], perf); // non atomic version of code
display_perf("2 thrds:t2", counter[1], perf);
display_perf("2 thrds:tt", counter[0] counter[1], perf);
Комментарии:
1. Да! Извините: MSVC 2013 на Win 8.1 с intel i7
2. Скорее всего, это не связано с проблемой. Однако, что касается производительности, вам следует обратить внимание на ложное совместное использование, т. Е. В вашем случае разные потоки не должны записывать в переменные, которые находятся в одной строке кэша
counter
.3. Очень интересная статья о ложном совместном использовании. Я подозревал, что что-то с кешем. Однако после вашего решения с помощью std::ref() я создал версию своей программы, используя глобальный массив и без передачи ссылок. Это сработало нормально, что подтвердило, что проблема заключалась не в кеше, а в ссылке.
Ответ №1:
Вот упрощенная версия для воспроизведения проблемы:
void deep_thought(intamp; value) { value = 6 * 9; }
int main()
{
int answer = 42;
std::thread{deep_thought, answer).join();
return answer; // 42
}
Это похоже на передачу ссылки answer
на рабочую функцию и присвоение 6 * 9
ссылке и, следовательно, на answer
. Однако конструктор std::thread
создает копию answer
и передает ссылку на копию в рабочую функцию, а переменная answer
в основном потоке никогда не изменяется.
Оба GCC-4.9 и Clang-3.5 отклоняют приведенный выше код, поскольку рабочая функция не может быть вызвана со ссылкой lvalue. Вы можете решить проблему, передав переменную с std::ref
:
std::thread{deep_thought, std::ref(answer)}.join();
Комментарии:
1. Возможно, стоит упомянуть, что решение заключается в использовании a
std::reference_wrapper
.2. @T.C.: Спасибо за подсказку. Я обновил ответ.