May. 11th, 2017

SMP

May. 11th, 2017 08:16 pm
thedeemon: (bednota)
А вот еще случай был. Программа генерит длинную последовательность неких значений (phase_1), и поблочно эти значения потом как-то дальше обрабатываются (phase_2), производя добро (PROFIT!). И надо бы это делать побыстрее. Пришла очевидная мысль это дело распараллелить: пока phase_2 обрабатывает первый блок данных от phase_1, в это время phase_1 может уже параллельно создавать следующий блок, и так далее, блок N обрабатывается параллельно созданию блока N+1. В каждый момент времени в памяти лишь два блока - генерируемый и обрабатываемый. Завел для них два массива: то пишем в первый, читаем из второго, то наоборот.
std::vector<Freq> ranges[2];

При этом phase_1 что-то пишет в один из массивов через push_back, а обработка блока в phase_2 выглядела как-то так:
BYTE* writeBlock(const std::vector<Freq> &ranges, BYTE *dst) {
    ...
    for(int i=ranges.size()-1; i>=0; i--) {
        ... // use ranges[i]
    }    
    ...
}

До распараллеливания phase_1 занимал по времени 12 попугаев, phase_2 - 13 попугаев, всего 25. Запускаю новый распараллеленный вариант и вдруг вижу, что общее время стало аж 41 попугай. Вместо ускорения получил замедление!
А теперь беру и меняю пару строк:
BYTE* writeBlockV(const std::vector<Freq> &rangesV, BYTE *dst) {
    ...
    const Freq * ranges = &rangesV[0];
    for(int i=rangesV.size()-1; i>=0; i--) {
        ... // use ranges[i]
    }
    ...
}

И общее время становится менее 15 попугаев, почти втрое быстрее! Т.е. в первом случае код в цикле каждый раз загружал указатель на данные в векторе, а этот указатель лежал в памяти рядом с длиной другого массива, которая менялась в другом потоке, так что два ядра тягали этот кусочек памяти туда-сюда: одно ядро туда пишет новую длину, второе ядро копирует изменившуюся кэш-линию, чтобы прочитать указатель по соседству. Из-за этих тяганий туда-сюда получается медленнее, чем если все делать в один поток. А стоило указатель на данные вектора унести в стек и перестать читать меняющуюся кэш-линию, как все заработало быстро, как и планировалось.
Причем безобразие такое только у интеловского компилятора. MSVC и GCC не перечитывают указатель и тяганий кэш-линий не устраивают, оба варианта кода работают с одинаковой скоростью. При этом с GCC время получается чуть менее 16 попугаев, а с MSVC - почти 33 (что все же быстрее непараллеленного варианта, где у MSVC 42 попугая).

Profile

thedeemon: (Default)
Dmitry Popov

May 2017

S M T W T F S
 1234 56
789 10 11 1213
14151617181920
21222324252627
28293031   

Most Popular Tags

Page Summary

Style Credit

Expand Cut Tags

No cut tags
Page generated Jul. 23rd, 2017 02:53 pm
Powered by Dreamwidth Studios