Могу ли я попросить ядро ​​заполнить (ошибиться) ряд анонимных страниц? - PullRequest
2 голосов
/ 02 июня 2019

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

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

Для большого региона (скажем, 1 ГиБ) это большое количество сбоев страниц (~ 260 тыс. для страниц по 4 Кбайт), и каждый отказ вызывает пользователяпереход к ядру-пользователю, который особенно медленен в ядрах с мерами по уменьшению Spectre и Meltdown.В некоторых случаях это время сбоя страницы может доминировать над фактической работой над буфером.

Если я знаю, что собираюсь использовать весь буфер, есть ли способ попросить ядро ​​отобразить уже сопоставленный регион раньше времени?

Если бы я выделял собственную память, используя mmap, способ сделать это был бы MAP_POPULATE - но это не работает для регионовПолучено от malloc или new.

. Вызов madvise существует, но параметры, по-видимому, в основном применяются к областям с файловой поддержкой.Например, вызов madvise(..., MADV_WILLNEED) выглядит многообещающе - со страницы руководства:

MADV_WILLNEED

Ожидайте доступ в ближайшем будущем.(Следовательно, было бы неплохо прочитать некоторые страницы впереди.)

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

1 Ответ

1 голос
/ 06 июня 2019

Это немного грязный хак и лучше всего подходит для привилегированных процессов или в системах с высоким RLIMIT_MEMLOCK, но ... пары mlock и munlock достигнут желаемого эффекта.

Например, с учетом следующей тестовой программы:

# compile with (for e.g.,): cc -O1 -Wall    pagefaults.c   -o pagefaults

#include <stdlib.h>
#include <stdio.h>
#include <err.h>
#include <sys/mman.h>

#define DEFAULT_SIZE        (40 * 1024 * 1024)
#define PG_SIZE     4096

void failcheck(int ret, const char* what) {
    if (ret) {
        err(EXIT_FAILURE, "%s failed", what);
    } else {
        printf("%s OK\n", what);
    }
}

int main(int argc, char **argv) {
    size_t size = (argc == 2 ? atol(argv[1]) : DEFAULT_SIZE);
    char *mem = malloc(size);

    if (getenv("DO_MADVISE")) {
        failcheck(madvise(mem, size, MADV_WILLNEED), "madvise");
    }

    if (getenv("DO_MLOCK")) {
        failcheck(mlock(mem, size), "mlock");
        failcheck(munlock(mem, size), "munlock");
    }

    for (volatile char *p = mem; p < mem + size; p += PG_SIZE) {
        *p = 'z';
    }
    printf("size: %6.2f MiB, pages touched: %zu\npoitner value : %p\n",
            size / 1024. / 1024., size / PG_SIZE, mem);
}

Запуск его с правами root для области размером 1 ГБ и подсчет ошибок страницы с perf приводит к:

$ perf stat ./pagefaults 1000000000
size: 953.67 MiB, pages touched: 244140
poitner value : 0x7f2fc2584010

 Performance counter stats for './pagefaults 1000000000':

        352.474676      task-clock (msec)         #    0.999 CPUs utilized          
                 2      context-switches          #    0.006 K/sec                  
                 0      cpu-migrations            #    0.000 K/sec                  
           244,189      page-faults               #    0.693 M/sec                  
       914,276,474      cycles                    #    2.594 GHz                    
       703,359,688      instructions              #    0.77  insn per cycle         
       117,710,381      branches                  #  333.954 M/sec                  
           447,022      branch-misses             #    0.38% of all branches        

       0.352814087 seconds time elapsed

Однако, если вы запускаете с префиксом DO_MLOCK=1, вы получите:

sudo DO_MLOCK=1 perf stat ./pagefaults 1000000000
mlock OK
munlock OK
size: 953.67 MiB, pages touched: 244140
poitner value : 0x7f8047f6b010

 Performance counter stats for './pagefaults 1000000000':

        240.236189      task-clock (msec)         #    0.999 CPUs utilized          
                 0      context-switches          #    0.000 K/sec                  
                 0      cpu-migrations            #    0.000 K/sec                  
                49      page-faults               #    0.204 K/sec                  
       623,152,764      cycles                    #    2.594 GHz                    
       959,640,219      instructions              #    1.54  insn per cycle         
       150,713,144      branches                  #  627.354 M/sec                  
           484,400      branch-misses             #    0.32% of all branches        

       0.240538327 seconds time elapsed

Обратите внимание, что количество сбоев страниц сократилось с 244 189 до 49, и ускорение в 1,46 раза.Подавляющее большинство времени все еще остается в ядре, так что это может быть намного быстрее, если бы не было необходимости вызывать оба mlock и munlock и, возможно, также потому, что семантика mlock более

Для непривилегированных процессов вы, вероятно, нажмете RLIMIT_MEMLOCK, если попытаетесь сделать большой регион сразу (в моей системе Ubuntu он установлен на 64 Киб), но вы можетеЗацикливайтесь на регионе, вызывая mlock(); munlock() на меньшем регионе.

...