В два раза больше ошибок страниц при чтении из большого массива malloced вместо простого хранения?

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

 ptr[i+4096] = 'A'

Я получил 25 722 ошибок страницы с помощью инструмента perf, чего я и ожидал, но если я воспользуюсь

tmp = ptr[i+4096]

вместо этого количество ошибок страниц увеличилось до 51,322. Я не знаю, как это объяснить. Ниже приведен полный код. Спасибо!

void do_something() {
    int i;
    char* ptr;
    char tmp;
    ptr = malloc(100*1024*1024);
    int j = 0;
    int k = 0;

    for (i = 0; i < 100*1024*1024; i+=4096) {

       //ptr[i+4096] = 'A' ;
       tmp = ptr[i+4096];

       for (j = 0 ; j < 4096; j++)
           ptr[i+j] = (char) (i & 0xff); // pagefault
    }
    free(ptr);
}

int main(int argc, char* argv[]) {
    do_something();
    return 0;
}

Информация о машине: Архитектура: x86_64 Операционные режимы ЦП: 32-разрядные, 64-разрядные Порядок байтов: Little Endian ЦП: 40 Список оперативных ЦП: 0-39 потоков на ядро : 2 ядра на сокет: 10 сокетов: 2 узла (ов) NUMA: 2 ID производителя: GenuineIntel Семейство процессоров: 6 Модель: 63 Название модели: Intel (R) Xeon (R) CPU E5-2687W v3 @ 3,10 ГГц Степпинг: 2 ЦП МГц: 3096,188 BogoMIPS: 6197,81 Виртуализация: Кэш VT-x L1d: 32 КБ кеш-памяти L1i: 32 КБ кеш-памяти L2: 256 КБ кеш-памяти L3: 25600 КБ NUMA node0 ЦП: 0-9,20-29 NUMA node1 ЦП: 10-19,30-39

3.10.0-514.32.3.el7.x86_64 #1


person Yunzhou Wu    schedule 05.09.2018    source источник
comment
А ты скомпилировал без оптимизации, наверное? Потому что вы не использовали volatile ни для чего, поэтому все будет оптимизировано, если компилируется с включенной нормальной оптимизацией (-O2 или -O3). Так что в любом случае вы сохраняете в стек, потому что ваши счетчики циклов не будут оптимизированы в регистры. В любом случае, я мог видеть лишний промах dTLB из-за сохранения в стек после того, как ошибка страницы изменяет таблицы HW-страниц для подключения новой страницы (если смягчение последствий Meltdown сделало недействительными TLB), но не дополнительную ошибку страницы. IDK, почему это могло произойти.   -  person Peter Cordes    schedule 05.09.2018
comment
Если важно, какая версия Linux и какая архитектура? Это x86 с защитой от Meltdown / Spectre? (И, кстати, с оптимизацией загрузка tmp = ptr[i+4096] будет оптимизирована, но, вероятно, не версия хранилища, потому что gcc не обнаруживает malloc / free без передачи указателя куда-либо как мертвое хранилище.)   -  person Peter Cordes    schedule 05.09.2018
comment
Я построил его без какой-либо оптимизации. @Peter, а на линуксе можно повторить?   -  person Yunzhou Wu    schedule 05.09.2018
comment
ПРИМЕЧАНИЕ ptr[i+4096] вызовет доступ к памяти вне допустимого диапазона на последней итерации цикла.   -  person Leo K    schedule 05.09.2018


Ответы (1)


malloc() часто удовлетворяет запросы на память, запрашивая у ОС новые страницы, например, через mmap. Такие страницы обычно распределяются лениво: фактическая страница не выделяется до первого доступа.

Что происходит дальше, зависит от типа первого доступа: когда вы сначала выполняете чтение, Linux будет отображать в общем доступном только для чтения COW страниц нулей, чтобы удовлетворить его, а затем, если вы позже напишете, потребуется вторая ошибка для выделения частной записываемой страницы.

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

Обратите внимание, что приведенное выше является описанием того, как недавно выделенные страницы работают в Linux - когда вы используете malloc, появляется другой уровень: malloc обычно пытается удовлетворить запросы блоков, освобожденных ранее процессом, вместо того, чтобы постоянно запрашивать новую память. В случае повторного использования памяти она, как правило, уже будет выгружена, и вышеуказанное не применимо. Конечно, для вашего первоначального большого выделения 1024 MiB, где нет памяти для повторного использования, поэтому вы можете быть уверены, что распределитель получает ее из ОС.

person BeeOnRope    schedule 06.09.2018
comment
Да, верно, и буфер не выровнен по страницам, поэтому первый доступ к каждой странице - это чтение в конце цикла char-at-a-time, а не запись по одному на страницу. (glibc malloc обычно хранит первые 16 байтов страницы для бухгалтерского учета, поэтому ptr, вероятно, что-то вроде 0x...0010) - person Peter Cordes; 06.09.2018
comment
@PeterCordes - я не думаю, что выравнивание имеет значение для этого конкретного кода: чтение происходит в i + 4096 каждом цикле, а затем остальная часть цикла записывает до i + 4095, поэтому чтение всегда выполняется впереди записи, и поэтому каждая страница будет сначала получить доступ для чтения, независимо от выравнивания блока, возвращаемого malloc. - person BeeOnRope; 06.09.2018
comment
ой, я вспомнил вещи совершенно задом наперед, когда посмотрел на это вчера. - person Peter Cordes; 06.09.2018