у меня странная проблема с многопоточными программами на C++. я написал первый код и не увидел никакого улучшения производительности по сравнению с однопоточным кодом. Я продолжал экспериментировать со смещением переменных в функциях, и второй код занял почти половину времени первого кода.
Первый код:
#include<iostream>
#include<thread>
#include<chrono>
typedef unsigned long long ull;
ull evenSum = 0;
ull oddSum = 0;
void findEvenSum(ull start, ull end){
for(ull i = start; i<=end; i++){
if ((i&1) == 0){
evenSum+=i;
}
}
std::cout<<"Even Sum: "<<evenSum<<"\n";
}
void findOddSum(ull start, ull end){
for(ull i = start; i<=end; i++){
if ((i&1) == 1){
oddSum+=i;
}
}
std::cout<<"Odd Sum: "<<oddSum<<"\n";
}
int main(){
ull start = 0, end = 9999999999;
auto startTime = std::chrono::high_resolution_clock::now();
std::thread evenSumCalculator(findEvenSum, start, end);
std::thread oddSumCalculator(findOddSum, start, end);
evenSumCalculator.join();
oddSumCalculator.join();
// findEvenSum(start, end);
// findOddSum(start, end);
auto stopTime = std::chrono::high_resolution_clock::now();
auto duration = std::chrono::duration_cast<std::chrono::microseconds>(stopTime - startTime);
std::cout<<"time taken: "<<duration.count()<<" microseconds\n";
return 0;
}
Второй код:
#include<iostream>
#include<thread>
#include<chrono>
typedef unsigned long long ull;
ull eSum = 0;
ull oSum = 0;
void findEvenSum(ull start, ull end){
ull evenSum = eSum;
for(ull i = start; i<=end; i++){
if ((i&1) == 0){
evenSum+=i;
}
}
std::cout<<"Even Sum: "<<evenSum<<"\n";
}
void findOddSum(ull start, ull end){
ull oddSum = oSum;
for(ull i = start; i<=end; i++){
if ((i&1) == 1){
oddSum+=i;
}
}
std::cout<<"Odd Sum: "<<oddSum<<"\n";
}
int main(){
ull start = 0, end = 9999999999;
auto startTime = std::chrono::high_resolution_clock::now();
std::thread evenSumCalculator(findEvenSum, start, end);
std::thread oddSumCalculator(findOddSum, start, end);
evenSumCalculator.join();
oddSumCalculator.join();
// findEvenSum(start, end);
// findOddSum(start, end);
auto stopTime = std::chrono::high_resolution_clock::now();
auto duration = std::chrono::duration_cast<std::chrono::microseconds>(stopTime - startTime);
std::cout<<"time taken: "<<duration.count()<<" microseconds\n";
return 0;
}
вторая программа в два раза быстрее первой. я запускал программы без оптимизации
В чем причина такой разницы во времени работы?
я запускал программы без оптимизации" Почему? Кажется, вас беспокоит время выполнения программ. Зачем говорить компилятору, что это не так?
В первом тесте evenSum
и oddSum
, вероятно, будут использовать одну и ту же строку кэша. Во втором тесте это неверно, поскольку они находятся в отдельных стопках. Google для «ложного обмена кэшем»
Вы также используете неправильные часы для отсчета времени. Вам следует использовать std::chrono::steady_clock
.
@DrewDormann, какое время мне следует использовать?
Проблема вызвана так называемым «ложным разделением». По сути, проблема в том, что вы используете глобальные переменные. Эти аккумуляторы: evenSum
и oddSum
находятся в одной строке кэша, и процессор должен выполнить некоторую синхронизацию.
Какие меры мне следует предпринять, чтобы избежать таких проблем, как ложное совместное использование в многопоточных программах?
Обычно старайтесь избегать глобальных переменных в многопоточном коде — это также упрощает рассуждения о безопасности потоков/гонках данных. Для задач, подобных описанным выше, используйте локальную переменную стека и обновляйте глобальную только один раз в конце потока.
Мне очень хочется закрыть это как дубликат `Что такое «ложный обмен»? Как воспроизвести/избежать этого?
«Я запускал программы без оптимизации» Как правило, не делайте этого. В этом конкретном случае ваши результаты, вероятно, действительны, но это становится очевидным только постфактум, когда вы знаете, что вызывает разницу.
Кстати, две программы делают разные вещи, поскольку глобальные значения в конце не совпадают.
В первом случае у вас есть две переменные, расположенные в памяти близко друг к другу. Затем, если один поток хочет обновить, например, EvenSum, он запрашивает из кэша памяти строку с этой переменной в монопольном доступе (одно из состояний доступа протокола MESI). Существует высокая вероятность наличия нечетной суммы в этой строке кэша, поэтому второй поток теряет монопольный доступ к нечетной сумме и должен запрашивать ее снова, когда он ее обновит. Это называется ложный обмен, об этом можно прочитать здесь и поиграться с протоколом MESI здесь
И сколько времени длились обе казни?