Почему обработка массива во второй раз медленнее?

Этот простой C-код сначала создает массив из элементов 0xFFFFFF, а затем передает его два раза, измеряя, сколько времени занимает каждый проход:

#include <ctype.h>
#include <stdio.h>
#include <stdlib.h>
#include <time.h>

#define TESTSIZ 0xffffff

char testcases[TESTSIZ];

void gentestcases(void)
{
        size_t i = 0;
        while(i < TESTSIZ)
                testcases[i++] = rand()%128;

        return;
}

long long time_elapsed(struct timespec beg, struct timespec end)
{
        if(end.tv_nsec < beg.tv_nsec) {
                end.tv_nsec += 1000000000;
                end.tv_sec--;
        }

        return 1000000000ll*(end.tv_sec-beg.tv_sec) + end.tv_nsec-beg.tv_nsec;
}

long long test( int(*func)(int) )
{
        struct timespec beg, end;

        clock_gettime(CLOCK_MONOTONIC, &beg);

        int volatile sink;
        size_t i = 0;
        while(i < TESTSIZ)
                sink = islower(testcases[i++]);

        clock_gettime(CLOCK_MONOTONIC, &end);

        return time_elapsed(beg, end);
}

int main()
{
        gentestcases();

        struct timespec beg, end;

        printf("1st pass took %lld nsecs\n", test(islower));
        printf("2nd pass took %lld nsecs\n", test(islower));
}

Я скомпилирую его с помощью gcc -O2 -std=gnu89 -o sb sillybench.c

Обычно я получаю результат, что обработка массива во второй раз медленнее. Эффект небольшой, но заметный (1-3 мс) и - с единственным исключением - повторяющийся:

[email protected] ~/UVA/fastIO $ ./sb
1st pass took 13098789 nsecs
2nd pass took 13114677 nsecs
[email protected] ~/UVA/fastIO $ ./sb
1st pass took 13052105 nsecs
2nd pass took 13134187 nsecs
[email protected] ~/UVA/fastIO $ ./sb
1st pass took 13118069 nsecs
2nd pass took 13074199 nsecs
[email protected] ~/UVA/fastIO $ ./sb
1st pass took 13038579 nsecs
2nd pass took 13079995 nsecs
[email protected] ~/UVA/fastIO $ ./sb
1st pass took 13070334 nsecs
2nd pass took 13324378 nsecs
[email protected] ~/UVA/fastIO $ ./sb
1st pass took 13031000 nsecs
2nd pass took 13167349 nsecs
[email protected] ~/UVA/fastIO $ ./sb
1st pass took 13019961 nsecs
2nd pass took 13310211 nsecs
[email protected] ~/UVA/fastIO $ ./sb
1st pass took 13041332 nsecs
2nd pass took 13311737 nsecs
[email protected] ~/UVA/fastIO $ ./sb
1st pass took 13030913 nsecs
2nd pass took 13177423 nsecs
[email protected] ~/UVA/fastIO $ ./sb
1st pass took 13060570 nsecs
2nd pass took 13387024 nsecs

Почему это так? Во всяком случае, я бы предположил, что обработка массива в первый раз должна быть медленнее, а не во второй раз!

Если это имеет значение:

[email protected] ~/UVA/fastIO $ gcc --version
gcc (Ubuntu 5.4.0-6ubuntu1~16.04.4) 5.4.0 20160609
Copyright (C) 2015 Free Software Foundation, Inc.
This is free software; see the source for copying conditions.  There is NO
warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.

System:    Host: m-X555LJ Kernel: 4.4.0-21-generic x86_64 (64 bit gcc: 5.3.1)
           Desktop: Cinnamon 3.0.7 (Gtk 2.24.30) Distro: Linux Mint 18 Sarah

CPU:       Dual core Intel Core i5-5200U (-HT-MCP-) cache: 3072 KB
           flags: (lm nx sse sse2 sse3 sse4_1 sse4_2 ssse3 vmx) bmips: 8786
           clock speeds: max: 2700 MHz 1: 2200 MHz 2: 2202 MHz 3: 2200 MHz
           4: 2200 MHz

Ответ 1

Этот эффект, скорее всего, вызван турбо-режимом (или технологией Intel Turbo Boost). Режим Turbo позволяет процессорному ядру работать с более высокой номинальной тактовой частотой. Одним из факторов этого является временные всплески *. Обычно в течение некоторой доли секунды процессор достигнет самой высокой частоты. Весьма вероятно, что первый цикл работает с более высокой тактовой частотой, чем второй.

Вы можете подтвердить, что вручную установив номинальную частоту (2,20 ГГц для вашего процессора), например, с помощью cpufrequtils или cpupower. Однако во многих системах используйте intel_pstate, что не позволяет использовать регулятор userpace. Здесь вы можете отключить турбо-режим для intel_pstate - или отключить intel_pstate все вместе.

Без турборежима производительность должна быть одинаковой.

*: Температура - еще один фактор, но я сомневаюсь, что он играет роль в течение 10 мс. Чтобы проиллюстрировать, предположим, что процессор превышает 15 Вт TDP и использует 20 Вт: даже крошечный 1 г меди будет только нагреваться на 0,5 К через 10 мс. Я обычно вижу короткий отчетный всплеск (время, от нескольких десятков миллисекунд до нескольких секунд), за которым следует медленное и устойчивое снижение (температура, десятки секунд до минут)

Примечание: gentestcases выполняется в течение значительного промежутка времени (например, 240 мс) перед первым фактическим тестом, способствующим "спринту" процессора.