Какой из следующих двух фрагментов кода обычно работает быстрее (без оптимизации компилятора)? Этот код является всего лишь примером - я знаю, что есть более быстрые способы выполнить те же вычисления.
// arr points to the following array: [1,2,4,8,16,32,64]
// assume that it has already been created, so that the
// array creation does not cause a time penalty
int result = 0;
for (int i = 0; i < 7; i++) {
result += arr[i];
}
int result = 0;
for (int i = 0; i < 7; i++) {
result += (1 << i);
}
Я почти уверен, что доступ к памяти будет медленнее, но мне нужно подтверждение.
РЕДАКТИРОВАТЬ: чтобы прояснить этот вопрос, я КОНКРЕТНО интересуюсь неоптимизированной версией этого кода. Не потому, что я на самом деле намереваюсь использовать этот код в производстве, а потому, что меня интересует концепция того, быстрее ли доступ к памяти или арифметика. Возможно, мне следовало написать код на языке ассемблера вместо C / C ++, чтобы еще раз прояснить, что меня не интересует оптимизация кода компилятором.
Некоторые из ответов говорят, что доступ к памяти в большинстве случаев медленнее, в то время как другие говорят, что мне нужно выполнить тест, и это зависит от моего процессора и системы.
Интересующая архитектура - x86 или x86-64 - то, что вы можете найти в современном ноутбуке или настольном компьютере в 2021 году. Я отредактирую этот вопрос еще раз, как только я проведу несколько тестов как для неоптимизированной, так и для оптимизированной версии этого кода. Спасибо всем, кто ответил.
3 ответа
Я рекомендую вам взглянуть на отличные руководства от Agner Fog, особенно на относящийся к C ++
Оптимизация скорости важна, когда доступ к ЦП и доступ к памяти являются критически важными потребителями времени.
В большинстве современных архитектур переключение (инструкция ALU) будет намного быстрее, чем доступ к памяти, цитируя руководство:
Операции сдвига (1 такт)
на большинстве микропроцессоров операции сдвига занимают только один такт
Доступ к памяти (от 2 до 4 тактовых циклов или хуже)
Доступ к данным из оперативной памяти может занять довольно много времени по сравнению со временем, которое требуется для выполнения вычислений с данными. Это причина того, что все современные компьютеры имеют кеш-память. Обычно кэш данных уровня 1 составляет от 8 до 64 Кбайт, а кэш уровня 2 - от 256 кбайт до 2 Мбайт. Часто также имеется кэш-память 3-го уровня размером в несколько мегабайт.
общий размер всех данных в программе больше, чем кеш уровня 2 и данные разбросаны по памяти или доступны непоследовательным образом, то вполне вероятно, что Доступ к памяти - самый большой потребитель времени в программе. Чтение или письмо переменная в памяти занимает всего 2-4 такта, если она кэширована , но несколько сотен тактов циклы, если он не кэширован. См. Стр. 25 о хранении данных и стр. 89 о памяти. кеширование.
Итак, в вашем конкретном примере вы должны переключиться.
Также было бы интересно включить в сравнение устройство Даффа. Он использует то, что упоминается в статье как разворачивание цикла, чтобы сократить количество инструкций, необходимых для копирования, за счет сокращения количество петель.
"Основная идея разворачивания цикла состоит в том, что количество инструкций, выполняемых в цикле, может быть уменьшено за счет уменьшения количества циклических тестов, иногда сокращая время, затрачиваемое на петля. Например, в случае цикла только с одним инструкции в коде блока, тест цикла обычно будет выполняется для каждой итерации цикла, то есть каждый раз, когда инструкция выполнена. Если вместо этого восемь копий того же инструкции помещаются в цикл, затем будет проведен тест только каждые восемь итераций, и это может выиграть время, избегая семи тесты. Однако это обрабатывает только несколько итераций, кратных восьми, требуется что-то еще для обработки оставшейся части итераций "
Пример фрагмента кода устройства Даффа:
void copy_duff(register short *to, register short *from, register count)
{
register n=(count+7)/8;
switch(count%8) {
case 0: do { *to = *from++;
case 7: *to = *from++;
case 6: *to = *from++;
case 5: *to = *from++;
case 4: *to = *from++;
case 3: *to = *from++;
case 2: *to = *from++;
case 1: *to = *from++;
} while(--n>0);
}
}
Поскольку ваш вопрос, похоже, касается относительной скорости загрузки слова из памяти vs . выполняя арифметическую операцию, вы, кажется, действительно просите сравнения, больше похожего на оценку
*p
против . что из
p + 1
, где p
- указатель на int
, значение которого не нужно извлекать из памяти (потому что оно уже находится в регистре ЦП). Вообще говоря, арифметические устройства современных ЦП работают, по крайней мере, в несколько раз быстрее, чем подсистемы подсистемы памяти, даже для тех ячеек памяти, содержимое которых в настоящее время доступно из самого быстрого кеша ЦП, поэтому обычно можно ожидать, что последний будет быстрее.
Но на самом деле нет смысла рассматривать вопросы производительности с такой степенью детализации, и особенно не спрашивать о неоптимизированном коде. На производительность всей программы, которая включает в себя такие операции, очень сильно влияет контекст операций, и если вы ищете максимальную производительность, вы, конечно, скомпилируете с оптимизацией, а не без нее. Более того, в целом совершенно неясно, как код, созданный без включенной оптимизации, будет отличаться от кода, созданного с включенной оптимизацией, если он вообще будет отличаться в каждом конкретном случае.
В целом вопрос сильно пахнет преждевременной оптимизацией, то есть микрооптимизацией программиста, а не оптимизацией компилятора, а это в целом контрпродуктивно. Для достижения максимальной производительности используйте лучшие алгоритмы решения проблемы и пишите чистый, естественный код. Это часто помогает компилятору лучше выполнять работу по оптимизации и, безусловно, облегчает отладку и сопровождение кода. Если полученная программа недостаточно быстрая, профилируйте ее, чтобы определить, где находятся самые большие узкие места, и поработайте над ними.
Похожие вопросы
Новые вопросы
c++
C ++ - это язык программирования общего назначения. Первоначально он был разработан как расширение C и имеет аналогичный синтаксис, но теперь это совершенно другой язык. Используйте этот тег для вопросов о коде (который должен быть) скомпилирован с помощью компилятора C ++. Используйте тег для конкретной версии для вопросов, связанных с конкретной версией стандарта [C ++ 11], [C ++ 14], [C ++ 17], [C ++ 20] или [C ++ 23] и т. Д. .