Скажем, у нас есть два массива a
и b
фундаментального типа (скажем, float
), и нам нужно вычислить a[i] + b[i]
для каждого действительного индекса i
, а также сохранить результат. Каков наилучший способ перебора массивов, чтобы максимизировать количество попаданий в кеш? Это вперед-назад, задом-наперед или что-то еще?
направление итерации по массиву
Ответы (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 должны эффективно автоматически векторизовать ваше сокращение.
Чтобы использовать возможность предварительной выборки кэша, вам необходимо последовательно читать массивы от начала до конца.
Кроме того, массивы должны быть выровнены по SSE (16 байт). Еще важнее то, что элементы (например, числа с плавающей запятой) будут выровнены по размеру (4 байта для числа с плавающей запятой). Это важно, чтобы данные не пересекали строки кэша (медленнее считывались).
После того, как массивы выровнены, вы можете использовать SSE/AVX для чтения, добавления и сохранения результатов, выполняя 4 или 8 операций в одной инструкции.
Изменить. Подробнее о предварительной выборке кэша можно прочитать здесь и подробное описание в Руководство разработчика программного обеспечения Intel.
std::array
, если вы узнать размер во время компиляции илиstd::vector
. - person Some programmer dude   schedule 21.07.2014-funroll-loops
и-funroll-all-loops
и оставить решение за компилятором? Воздержитесь от такого рода чрезмерных усложнений, если у вас нет веской причины для его использования. Однако, если вы не можете измерить падение производительности, как вы можете быть уверены, что ваши оптимизации не замедлят вычисления? - person Stefano Sanfilippo   schedule 21.07.2014-O3
оптимизации и рассчитать время. Чрезмерное усложнение исходного кода всегда вредно, так как оно создает дополнительные точки отказа и затрудняет его понимание другими людьми. - person Stefano Sanfilippo   schedule 21.07.2014