Наибольшие эффекты для современных систем включают в себя:
- виртуальная память лениво разбивает код и данные с диска, если в страничном кэше она не горячая. (При первом запуске программы, как правило, накладные расходы запуска возрастают.)
- Частота ЦП не фиксирована. (холостой ход / турбо скорости.
grep MHz /proc/cpuinfo
). - Кэш-память ЦП может быть горячей или нет
- (для очень коротких интервалов) случайное прерывание или не происходит в вашей временной области.
Так что даже если циклы были фиксированными (что они очень много не так), вы бы не увидели равных времен.
Ваше предположение не является полностью неправильным, но оно применимо только к тактам ядра для отдельных циклов, и только в случаях, когда доступ к памяти не осуществляется. (например, данные, уже горячие в кеше L1d, код, уже горячий в кеше L1i внутри ядра ЦП). И при условии, что при работе по времени l oop прерывание не происходит.
Выполнение всей программы - это гораздо больший масштаб работы, который потребует общих ресурсов (и возможного конфликта для них), таких как доступ к основной памяти. И, как указал @David, системный вызов write
для печати строки на эмуляторе терминала - эта связь с другим процессом может быть медленной и включать в себя пробуждение другого процесса, если ваша программа завершает ожидание Это. Перенаправление на /dev/null
или обычный файл приведет к его удалению, или простое закрытие стандартного вывода, например ./hello >&-
, приведет к возврату write
системного вызова -EBADF
(на Linux).
Современные процессоры очень сложные звери. Предположительно у вас есть процессор Intel или AMD x86-64 с неупорядоченным выполнением и десяток или около того буферов для строк входящего / исходящего кэша, что позволяет ему отслеживать такое большое количество незавершенных ошибок кэша (параллелизм на уровне памяти). И 2 уровня частного кэша на ядро и общий кэш L3. Удачи в прогнозировании точного количества тактов для любых условий, кроме самых контролируемых.
Но да, если вы do управляете условием, обычно будет работать тот же маленький l oop такое же количество тактов ядра на одну итерацию.
Однако даже , что , не всегда так. Я видел случаи, когда один и тот же l oop, похоже, имеет два стабильных состояния для того, как процессор планирует инструкции. Различные причудливые условия входа могут привести к постоянной разнице в скорости на миллионах l oop итераций.
Я видел это время от времени при микробенчмаркинге на современных процессорах Intel, таких как Sandybridge и Skylake. Обычно неясно, что именно представляют собой два стабильных состояния и что именно является причиной узкого места, даже с помощью счетчиков производительности и https://agner.org/optimize
В одном случае, который я помню, прерывание, как правило, приводит l oop в эффективный режим исполнения. @BeeOnRope измерял медленные циклы / итерации, используя или RDPM C для короткого интервала (или, может быть, RDTS C с фиксированными тактовыми частотами ядра = TS C опорных тактов), в то время как я измерял его работу быстрее с использованием действительно большого повторить count и просто использовать perf stat для всей программы (это был исполняемый файл stati c с одним l oop, написанным вручную в asm). И @Bee смог воспроизвести мои результаты, увеличив количество итераций, чтобы прерывание происходило внутри временной области, и возвращение из прерывания, как правило, выводило ЦП из этого неоптимального шаблона планирования uop, каким бы оно ни было.