Должно ли процессорное время всегда быть одинаковым при выполнении одного и того же кода? - PullRequest
2 голосов
/ 19 января 2020

Я понимаю, что процессорное время всегда должно быть одинаковым при каждом выполнении на одной и той же машине. Каждый раз для этого требуется одинаковое количество циклов ЦП.

Но сейчас я провожу несколько тестов для выполнения базового c эха "Hello World", и это дает мне от 0,003 до 0,005 секунды.

Правильно ли я понимаю процессорное время или есть проблема в моих измерениях?

Ответы [ 2 ]

4 голосов
/ 19 января 2020

Ваше понимание совершенно неверно. Реальные компьютеры с современными ОС на современных процессорах - это не просто теоретические абстракции. Существуют различные факторы, которые могут влиять на то, сколько времени требуется для выполнения кода ЦП.

Учитывайте пропускную способность памяти. На типичной современной машине все задачи, выполняющиеся на ядрах машины, конкурируют за доступ к системной памяти. Если код выполняется в то же время, то код на другом ядре использует большую полосу пропускания памяти, что может привести к тому, что доступ к ОЗУ займет больше тактовых циклов.

Многие другие ресурсы также используются совместно, например, кеши. Скажем, код часто прерывается, чтобы позволить другому коду работать на ядре. Это будет означать, что код будет часто находить кэш холодным и принимать много ошибок в кеше. Это также приведет к тому, что код займет больше тактов.

Давайте поговорим и о сбоях страниц. Сам код может быть в памяти или не может быть, когда код начинает выполняться. Даже если код находится в памяти, вы можете или не можете принимать программные ошибки страниц (чтобы обновить отслеживание операционной системы того, какая память активно используется), в зависимости от того, когда эта страница в последний раз принимала программные страницы или как долго go он был загружен в ОЗУ.

И ваша базовая c программа hello world выполняет ввод-вывод в терминал. Время, которое требуется, может зависеть от того, что еще взаимодействует с терминалом в данный момент.

2 голосов
/ 20 января 2020

Наибольшие эффекты для современных систем включают в себя:

  • виртуальная память лениво разбивает код и данные с диска, если в страничном кэше она не горячая. (При первом запуске программы, как правило, накладные расходы запуска возрастают.)
  • Частота ЦП не фиксирована. (холостой ход / турбо скорости. 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, каким бы оно ни было.

...