направление итерации по массиву

Скажем, у нас есть два массива a и b фундаментального типа (скажем, float), и нам нужно вычислить a[i] + b[i] для каждого действительного индекса i, а также сохранить результат. Каков наилучший способ перебора массивов, чтобы максимизировать количество попаданий в кеш? Это вперед-назад, задом-наперед или что-то еще?


person user1095108    schedule 21.07.2014    source источник
comment
Для начала не используйте простые массивы, используйте либо std::array, если вы узнать размер во время компиляции или std::vector.   -  person Some programmer dude    schedule 21.07.2014
comment
Я бы сказал, кодируйте свою итерацию самым обычным способом и дайте компилятору сделать свою работу;)   -  person Korchkidu    schedule 21.07.2014
comment
Если вы заботитесь об этом, вы должны настроить некоторые тесты.   -  person juanchopanza    schedule 21.07.2014
comment
На современном процессоре разница обычно незначительна, и компиляторы часто умнее вас в оптимизации. Если у вас нет фактической причины (т. е. вы профилировали свой код и пришли к выводу, что у вас узкое место), вас это не должно волновать.   -  person Stefano Sanfilippo    schedule 21.07.2014
comment
@StefanoSanfilippo Я использую трюк с индексами, чтобы исключить циклы for/while, и мой компилятор недостаточно умен, чтобы понять, что то, что я делаю, на самом деле является развернутым циклом.   -  person user1095108    schedule 21.07.2014
comment
Какой трюк с индексами? Приводит ли это к измеримому снижению производительности?   -  person Stefano Sanfilippo    schedule 21.07.2014
comment
@StefanoSanfilippo Это способ принудительно развернуть цикл, хотя иногда компилятор может сделать это сам. Что касается измерений, то я недостаточно опытен, чтобы их делать, я просто хотел бы получить эмпирическое правило, что лучше, так как я могу развернуть произвольным образом. Я мог бы даже сделать случайный порядок.   -  person user1095108    schedule 21.07.2014
comment
Почему бы просто не использовать -funroll-loops и -funroll-all-loops и оставить решение за компилятором? Воздержитесь от такого рода чрезмерных усложнений, если у вас нет веской причины для его использования. Однако, если вы не можете измерить падение производительности, как вы можете быть уверены, что ваши оптимизации не замедлят вычисления?   -  person Stefano Sanfilippo    schedule 21.07.2014
comment
Что вы подразумеваете под сохранением результата? Вы мужчины c[i] = a[i] + b[i] или вы имеете в виду сумму += a[i] + b[i]. Если вы делаете первый случай, то не думайте об этом дальше: ваш компилятор справится с задачей лучше всего. Если вы делаете второй случай (сокращение). Затем нужно развернуть 4 петли.   -  person Z boson    schedule 21.07.2014
comment
@StefanoSanfilippo, это просто то, что у меня есть. Компилятор умен, но у него есть свои ограничения, иначе он бы программировал за нас.   -  person user1095108    schedule 21.07.2014
comment
Вы совершенно ошибаетесь, если думаете, что сможете справиться с конвейером и кэшем современной архитектуры ЦП. Пишите код, который вы можете прочитать и понять, и пусть компилятор сделает свою работу.   -  person Stefano Sanfilippo    schedule 21.07.2014
comment
Кроме того, вы, похоже, не понимаете, какова роль компилятора. Он не может преобразовать ваши мысли в программы, но вполне может преобразовать анализируемое машиной представление (то есть исходный код) в исполняемые программы. В определенном смысле компиляторы выполняют часть вашей работы. Или вы когда-нибудь выделяли кадр стека вручную? Или переупорядочили инструкции, чтобы максимизировать пропускную способность конвейера?   -  person Stefano Sanfilippo    schedule 21.07.2014
comment
@StefanoSanfilippo Я просто следую эмпирическому правилу. Компилятор может сколько угодно переупорядочивать инструкции в моем развернутом цикле. Но если развертывание цикла с помощью трюка с индексами вредно, то, пожалуйста, приведите пример.   -  person user1095108    schedule 21.07.2014
comment
Нет никакого эмпирического правила, преодолейте это. Узнайте, как профилировать свой код (это несложно), внедрить его простым способом, попробовать -O3 оптимизации и рассчитать время. Чрезмерное усложнение исходного кода всегда вредно, так как оно создает дополнительные точки отказа и затрудняет его понимание другими людьми.   -  person Stefano Sanfilippo    schedule 21.07.2014
comment
@StefanoSanfilippo Есть эмпирические правила, 2 плаката говорят спереди назад.   -  person user1095108    schedule 21.07.2014


Ответы (2)


Для такого рода операций вы должны использовать автоматическую векторизацию вашего компилятора. Итерация от маленького i к большому i. Кроме того, ответ зависит от того, что вы подразумеваете под «сохранить результат», и от количества n элементов, которые вы собираетесь перебирать.

Если вы имеете в виду, что c[i] = a[i] + b[i] и n не слишком малы, то автоматический векторизатор вашего компилятора оптимизирует это лучше всего без каких-либо изменений. Даже MSVC сделает это правильно (по крайней мере, для SSE). Ваш компилятор должен будет сделать некоторые корректировки для n, не кратного 4 (или 8 для AVX), и выравнивания, но эти затраты будут амортизированы по n, и эти накладные расходы будут иметь незначительный эффект, за исключением небольшого n. Если n мало, вы можете подумать о выравнивании. Нужно определить, насколько мала мала, но я предполагаю, что она намного меньше 100.

Если вы имеете в виду sum + = a[i] + b[i], сокращение, то вам нужно подумать об этом. У этого есть цепочка зависимостей, поэтому вам нужно развернуть свой цикл 3-10 раз. Кроме того, вам необходимо использовать расслабленную модель с плавающей запятой, поскольку арифметика с плавающей запятой не является ассоциативной, а без него автовекторизация не сработает, поэтому добавьте -ffast-math в GCC (/fp:fast в MSVC). Если вы разворачиваете цикл и используете упрощенную модель с плавающей запятой, тогда GCC, ICC, Clang и MSVC должны эффективно автоматически векторизовать ваше сокращение.

person Z boson    schedule 21.07.2014
comment
Что произойдет, если вы развернете бесконечно, то есть полностью устраните петлю? - person user1095108; 21.07.2014
comment
@ user1095108, зависит от того, что вы делаете. Если n действительно мало, то может иметь смысл полностью развернуть. Вы должны попробовать и увидеть. Учитывайте размер кеша кода (32 КБ). Не удивляйтесь, если правильная оптимизация изменится для каждого набора ЦП. - person Z boson; 21.07.2014

Чтобы использовать возможность предварительной выборки кэша, вам необходимо последовательно читать массивы от начала до конца.

Кроме того, массивы должны быть выровнены по SSE (16 байт). Еще важнее то, что элементы (например, числа с плавающей запятой) будут выровнены по размеру (4 байта для числа с плавающей запятой). Это важно, чтобы данные не пересекали строки кэша (медленнее считывались).

После того, как массивы выровнены, вы можете использовать SSE/AVX для чтения, добавления и сохранения результатов, выполняя 4 или 8 операций в одной инструкции.

Изменить. Подробнее о предварительной выборке кэша можно прочитать здесь и подробное описание в Руководство разработчика программного обеспечения Intel.

person egur    schedule 21.07.2014
comment
Обратите внимание, что компилятор (по крайней мере, GCC и Clang) может оптимизировать использование набора инструкций SSE, если это возможно. - person Stefano Sanfilippo; 21.07.2014
comment
В зависимости от уровня оптимизации, конечно. Отсюда важность выравнивания. - person egur; 21.07.2014
comment
@egur, можете ли вы предоставить источник эмпирического правила? Я мог бы даже сделать случайный порядок. - person user1095108; 21.07.2014
comment
Спереди назад означает, что вы начинаете с нулевого индекса массива и последовательно перемещаетесь вверх. 2 тривиально для исходного кода... - person egur; 21.07.2014
comment
@egur Я имел в виду источник(и) (сайты, книги, ...) утверждения, а не исходный код :) - person user1095108; 21.07.2014
comment
Согласно Справочному руководству по оптимизации архитектур Intel® 64 и IA-32 (июнь 2011 г.), раздел 2.2.4.3 «Логика предварительной выборки», модуль предварительной выборки потоков для микроархитектуры Core поддерживает как прямые, так и обратные потоки, хотя существует 12 вперед и только 4 назад (каждая из которых занимает страницу размером 4 КБ); таким образом, очевидно, что прямые потоки предпочтительнее, но также поддерживаются и обратные потоки. - person Paul A. Clayton; 21.07.2014