Почему этот цикл задержки начинает работать быстрее после нескольких итераций без сна?


считаем:

#include <time.h>
#include <unistd.h>
#include <iostream>
using namespace std;

const int times = 1000;
const int N = 100000;

void run() {
  for (int j = 0; j < N; j++) {
  }
}

int main() {
  clock_t main_start = clock();
  for (int i = 0; i < times; i++) {
    clock_t start = clock();
    run();
    cout << "cost: " << (clock() - start) / 1000.0 << " ms." << endl;
    //usleep(1000);
  }
  cout << "total cost: " << (clock() - main_start) / 1000.0 << " ms." << endl;
}

вот пример кода. В первых 26 итерациях цикла синхронизации,

2 70

2 ответа:

после 26 итераций Linux разгоняет процессор до максимальной тактовой частоты, так как ваш процесс использует его полный квант времени пару раз подряд.

если вы проверили счетчики производительности вместо времени настенных часов, вы увидите, что основные тактовые циклы на цикл задержки остаются постоянными, подтверждая, что это просто эффект DVFS (который все современные процессоры используют для работы на более энергоэффективной частоте и напряжении большая часть время.)

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

если вы оставите его работать на некоторое время на процессор Intel с Turbo, вы, вероятно, увидите, что время на итерацию снова немного увеличивается, как только тепловые пределы требуют, чтобы тактовая частота уменьшилась до максимальной устойчивой частота.


вводим usleep предупреждает регулятор частоты процессора Linux от увеличения тактовой частоты, потому что процесс не генерирует 100% нагрузки даже на минимальной частоте. (Т. е. эвристика ядра решает, что процессор работает достаточно быстро для рабочей нагрузки, которая работает на нем.)



комментарии к другим теориям:

re:теория Дэвида о том, что a потенциальный контекст переключения с usleep может загрязнить кэши: это не плохая идея в целом, но это не поможет объяснить этот код.

загрязнение кэша / TLB совсем не важно для этого эксперимента. В основном внутри временного окна нет ничего, что касается памяти, кроме конца стека. Большая часть времени проводится в крошечном цикле (1 строка кэша команд), который касается только одного int памяти стека. Любой потенциальный кэш загрязнение во время usleep это крошечная часть времени для этого кода (реальный код будет отличаться)!

Подробнее для x86:

вызов clock() сам по себе может cache-miss, но code-fetch cache miss задерживает измерение времени начала, а не является частью того, что измеряется. Второй звонок в clock() почти никогда не задерживается, потому что еще в кэше.

The run функция может находиться в другой строке кэша main (так как gcc отмечает main как "холодный", поэтому он оптимизируется меньше и помещается с другими холодными функциями/данными). Мы можем ожидать один или два инструкция-кэш не попадает. Они, вероятно, все еще находятся на той же странице 4k, хотя, так что main вызовет потенциальный промах TLB перед входом в приуроченную область программы.

gcc-O0 скомпилирует код OP в что-то вроде этого (godbolt Compiler explorer): держать счетчик петли внутри память в стеке.

пустой цикл сохраняет счетчик циклов в памяти стека, поэтому на типичном процессор Intel x86 цикл выполняется на одной итерации за ~6 циклов на процессоре IvyBridge OP, благодаря задержке пересылки хранилища, которая является частью add С назначением памяти (чтение-изменение-запись). 100k iterations * 6 cycles/iteration составляет 600k циклов, что доминирует над вкладом не более пары промахов кэша (~200 циклов каждый для промахов выборки кода, которые предотвращают дальнейшие инструкции выдача до тех пор, пока они не будут решены).

Out-of-order execution и store-forwarding должны в основном скрывать потенциальный промах кэша при доступе к стеку (как часть call инструкции).

даже если счетчик циклов был сохранен в регистре, 100k циклов-это много.

вызов usleep может или не может привести к переключению контекста. Если это так, то это займет больше времени, чем если бы это не так.