Я бы ожидал, что записи в буфере char *
будут выполняться одинаково независимо от существующего содержимого памяти 1. Не правда ли?
Однако, сужая несогласованность в бенчмарке, я столкнулся с ситуацией, когда это, по-видимому, неверно. Буфер, содержащий все нули, ведет себя по-разному, по производительности, из буфера, заполненного 42
.
Графически это выглядит (подробнее см. ниже):
Здесь код, который я использовал для создания выше 3:
#include <stdio.h>
#include <stdlib.h>
#include <inttypes.h>
#include <string.h>
#include <time.h>
volatile char *sink;
void process(char *buf, size_t len) {
clock_t start = clock();
for (size_t i = 0; i < len; i += 678)
buf[i] = 'z';
printf("Processing took %lu μs\n",
1000000UL * (clock() - start) / CLOCKS_PER_SEC);
sink = buf;
}
int main(int argc, char** argv) {
int total = 0;
int memset42 = argc > 1 && !strcmp(argv[1], "42");
for (int i=0; i < 5; i++) {
char *buf = (char *)malloc(BUF_SIZE);
if (memset42)
memset(buf, 42, BUF_SIZE);
else
memset(buf, 0, BUF_SIZE);
process(buf, BUF_SIZE);
}
return EXIT_SUCCESS;
}
Я скомпилирую его в своем ящике Linux, например:
gcc -O2 buffer_weirdness.cpp -o buffer_weirdness
... и когда я запускаю версию с нулевым буфером, я получаю:
./buffer_weirdness zero
Processing took 12952 μs
Processing took 403522 μs
Processing took 626859 μs
Processing took 626965 μs
Processing took 627109 μs
Обратите внимание, что первая итерация выполняется быстро, а оставшиеся итерации длиной 50 раз больше.
Когда буфер сначала заполняется 42
, обработка всегда быстрая:
./buffer_weirdness 42
Processing took 12892 μs
Processing took 13500 μs
Processing took 13482 μs
Processing took 12965 μs
Processing took 13121 μs
Поведение зависит от `BUF_SIZE (1GB в примере выше) - более крупные размеры, скорее всего, покажут проблему, а также зависят от текущего состояния хоста. Если я останусь на ходу один на некоторое время, медленные итерации занимают, возможно, 60 000 мкс, а не 600 000 - поэтому в 10 раз быстрее, но все же ~ 5 раз медленнее, чем быстрое время обработки. В конце концов, времена возвращаются к полностью медленному поведению.
Поведение также зависит хотя бы частично от прозрачных огромных страниц - если я отключу их 2 производительность медленных итераций улучшится примерно в 3 раза, а быстрые итерации не изменится.
Конечная нота: общая продолжительность выполнения процесса намного ближе, чем просто синхронизация процедуры процесса (фактически, заполненная нулем, версия THP выключена примерно в 2 раза быстрее остальных, что примерно одинаково).
Что здесь происходит?
1 Вне некоторой очень необычной оптимизации, такой как компилятор, понимает, какое значение уже содержит буфер, и возвращает его одно значение, чего здесь не происходит.
2sudo sh -c "echo never > /sys/kernel/mm/transparent_hugepage/enabled"
3 Это дистиллированная версия исходного теста. Да, я утечка распределений, преодолеваю это - это приводит к более кратким примерам. Исходный пример не просачивался. Фактически, когда вы не просачиваете распределения, поведение меняется: возможно, потому, что malloc
может просто повторно использовать область для следующего выделения, вместо того, чтобы запрашивать ОС для большей памяти.