LINUX.ORG.RU

ccNUMA и fork()


0

0

Такой вопрос:

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

Я несколько растерялся от всяких cpumemsets и cpusets -- разбираюсь потихоньку, но, может, кто сразу подскажет?

★★★★★

> его новая копия была создана на определенном CPU (отличном от папы)
> (так, чтобы данные нового процесса тоже оказались вблизи этого
> определенного CPU).

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

http://linux.bkbits.net:8080/linux-2.5/cset@40b030abuWtTyhMZPaT6s4AiwjL7Mw

cpusets не имеет к этому отношения. sys_sched_setaffinity()
определяет на каком множестве процессоров может выполняться
процесс. эта маска также наследуется при fork(), но child
может ее изменить.

idle ★★★★★
()

Die-Hard:

Можно сделать sched_setaffinity (на целевой процессор)->fork, sched_setaffinity в исходном процессе(на исходный процессор). Тогда все, что связано с новым процессом, создастся на нужном процессоре (если ядреный код
Linux эффективен :) ).

При этом, как я понимаю, весь page cache где жил - там и будет жить.
Тут уж ничего не попишешь.)

Murr ★★
()
Ответ на: комментарий от idle

2idle (*) (25.06.2004 19:45:06):

Thanks.

> ...и туда же будет идти COW.

Естественно, я про COW и спрашивал.

Die-Hard ★★★★★
() автор топика
Ответ на: комментарий от Murr

2Murr :

Thanks.

Все б ничего. Но у меня ядро 2.4 ! Насколько я понимаю, sched_setaffinity появилось в 2.5?

Однако, runon, dplace и прочая лабуда вполне работает.

Я не могу добиться истины от SGI (хотя поддержку еще на 2 года обещали): второй день молчат, как партизаны. Выставка какая, чтоль?

Die-Hard ★★★★★
() автор топика
Ответ на: комментарий от Murr

Точно!

#include <sched.h>
...
sched_setaffinity(0, 0, mask);

Результат:

warning #266: function declared implicitly
  sched_setaffinity(0, 0, mask); 
  ^

/tmp/iccCyku2l.o: In function `BusyWork':
/tmp/iccCyku2l.o(.text+0x72): undefined reference to `sched_setaffinity'

:(

Die-Hard ★★★★★
() автор топика
Ответ на: комментарий от Die-Hard

> Но у меня ядро 2.4 !

тогда забудьте, что я писал, это про 2.6.

> sched_setaffinity появилось в 2.5?

да, в 2.4. это sys_ni_syscall

> Однако, runon, dplace и прочая лабуда вполне работает.

не знаю, что такое runon, но если это то, что я думаю,
и если оно _действительно_ работает, то у вас какие-то
патчи для ядра. поищите sys_sched_setaffinity в kernel/

> undefined reference to `sched_setaffinity'

так сделайте его сами. см include/asm/unistd.h:
_syscall3(..., sched_setaffinity, ...)

в принципе, Murr прав в том, чтобы affinity() делать
перед fork(), в этом случае task_struct, mm_struct
и прочая дребедень имеют шанс оказаться поближе к
target cpu, хотя не думаю, чтобы эту разницу можно
было заметить.

idle ★★★★★
()
Ответ на: комментарий от idle

idle :

>> Однако, runon, dplace и прочая лабуда вполне работает.

>не знаю, что такое runon, но если это то, что я думаю, и если оно _действительно_ работает, то у вас какие-то патчи для ядра.

runon -- стандартная Юниховая команда, запускающая процесс на определенном CPU.

Ядро у меня 2.4.21-sgi240rp04021313_10046 SMP ia64 -- УВЕРЯЮТ, что НЕ патченное. НО -- с проприетарным NUMA модулем от SGI.

> в принципе, Murr прав в том, чтобы affinity() делать перед fork(), в этом случае task_struct, mm_struct и прочая дребедень имеют шанс оказаться поближе к target cpu, хотя не думаю, чтобы эту разницу можно было заметить.

Меня интересуют исключительно результаты COW. И разница оказывается вполне ощутима: доступ к памяти далекого ЦПУ в 4 раза дольше, чем к своей памяти!

Я вот такой тест провел:

(прогу я запускал с помощью dplace, что позволило привязать процессы к процессорам)

прога делает шару (mmap MAP_SHARED|MAP_ANONYMOUS), после чего 10 раз форкается. Каждый процесс выделяет память malloc'ом (ПОСЛЕ форка) того же размера, что и шара. Затем все, включая папу, в цикле читают и из шары, и из mallocом распределенной памяти. Циклы обернуты таймерами.

Результат: папа читает и из шары, и из mallocом распределенной памяти одинаково (быстро). Детки читают из mallocом разпределенной памяти с той же скоростью, а из шары -- в 2 раза медленнее(удивительно, что не в 4).

Собственно, что и следовало ожидать.

Но вот теперь вызываем malloc ДО форка.

результат: у папы -- все то же, но у деток маллочная память читается в 2 раза медленнее, чем раньше, и с той же скоростью, что и шара.

Выводы: COW сработал в блок, где был папа :(

Да, прогу я запускал с помощью dplace:

dplace -e -c0,1,2,3,4,5,6,7,8,9,10 ./trymmap_forks

Die-Hard ★★★★★
() автор топика

Если интересно, приведу тексты и результаты.

Проги я запускал dplace'ом:

dplace -e -c0,1,2,3,4,5,6,7,8,9,10 ./trymmap_forks

вот выдержка из мана:

dplace - a tool for controlling placement of processes onto cpus

-c Cpu number(s). Specified as a list of cpu ranges: Example: "-c1", "-c2-4", "-c1,4-8,3". Cpu numbers are NOT physical cpu numbers. They are logical cpu number that are relative to the cpus that are in the set of allowed cpus as specified by the current cpumemset or "runon" command. Cpu numbers start at 0. If this option is not specified, all cpus of the current cpumemset are available. Note that a previous "runon" command may be used to restrict the available cpus.

-e Exact placement. As proceses are created, they are bound to cpus in the exact order that the cpus are specified in the cpu list. Cpu num╜ bers may appear multiple times in the list. A cpu value of "x" indi╜ cates that binding should not be done for that the process. If the end of the list is reached, binding starts over at the beginning of the list.

Die-Hard ★★★★★
() автор топика

local malloc AFTER fork:

**************************************************************/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/mman.h>
#include <fcntl.h>
#include <signal.h>
#include <time.h>
#include <unistd.h>
#include <sys/wait.h>


#define KB 1024
#define MB 1024*KB

#define SIZE 400*MB

#define NUM_THREADS     10

void BusyWork(int threadid,char* cc)
{
    int i;
    double result=0.0;
    long t;
    char *c =NULL;
    fprintf(stderr,"%d start...",threadid);

    c=(char*)malloc(SIZE);

    for (i=0; i<SIZE-1; i++)
            c[i] = (char)i;

    t = clock ();

    for (i=0; i < SIZE-1; i++)
    {
      result = result + (double)(cc[i]);
    }

    fprintf(stderr,"%d: share,res= %e(%f)\n",
       threadid,result,((double)(clock()-t))/CLOCKS_PER_SEC);

    result=0.0;
    t = clock ();
    for (i=0; i < SIZE-1; i++)
    {
      result = result + (double)(c[i]);
    }

   fprintf(stderr,"%d: local, res = %e(%f)\n",
      threadid,result,((double)(clock()-t))/CLOCKS_PER_SEC);

}
int main() {

    char* cc = mmap (0, SIZE, PROT_READ|PROT_WRITE,
    MAP_SHARED|MAP_ANONYMOUS, 0, 0);
    char* cond = cc+SIZE-1;
    int i, status, rc;
    long t;

    for (i=0; i<SIZE-1; i++)
            cc[i] = (char)i;

    BusyWork(-1,cc);

    for(i=0; i<NUM_THREADS; i++){
       rc = fork ();

       if( rc<0){
          printf("ERROR: %d\n",rc);
          exit(1);
       }
       if (rc == 0) {
          BusyWork(i,cc);
          return 0;
       }
    }
    while(wait(NULL)>0);
    return 0;
}
/**************************************************************/
Результат:

dplace -e -c0,1,2,3,4,5,6,7,8,9,10 ./trymmap_forks
-1 start...-1: share,res= -2.097152e+08(1.994944)
-1: local, res = -2.097152e+08(1.993968)
0 start...1 start...3 start...5 start...6 start...4 start...9
start...7 start...8 start...2 start...1: share,res=
-2.097152e+08(3.273504)
7: share,res= -2.097152e+08(3.593632)
1: local, res = -2.097152e+08(1.923696)
0: share,res= -2.097152e+08(3.255936)
2: share,res= -2.097152e+08(3.177856)
5: share,res= -2.097152e+08(3.936208)
7: local, res = -2.097152e+08(1.922720)
8: share,res= -2.097152e+08(3.511648)
4: share,res= -2.097152e+08(3.445280)
3: share,res= -2.097152e+08(3.504816)
9: share,res= -2.097152e+08(3.738080)
6: share,res= -2.097152e+08(3.711728)
2: local, res = -2.097152e+08(1.923696)
8: local, res = -2.097152e+08(1.942240)
3: local, res = -2.097152e+08(1.924672)
4: local, res = -2.097152e+08(1.929552)
0: local, res = -2.097152e+08(2.342400)
5: local, res = -2.097152e+08(1.930528)
6: local, res = -2.097152e+08(1.935408)
9: local, res = -2.097152e+08(1.925648)

Die-Hard ★★★★★
() автор топика

local malloc BEFORE fork:
/**************************************************************/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/mman.h>
#include <fcntl.h>
#include <signal.h>
#include <time.h>
#include <unistd.h>
#include <sys/wait.h>


#define KB 1024
#define MB 1024*KB

#define SIZE 400*MB

#define NUM_THREADS     10


char *c =NULL;

void BusyWork(int threadid,char* cc)
{
    int i;
    double result=0.0;
    long t;

    fprintf(stderr,"%d start...",threadid);

    for (i=0; i<SIZE-1; i++)
            c[i] = (char)i;

    t = clock ();
    for (i=0; i < SIZE-1; i++)
    {
      result = result + (double)(cc[i]);
    }

    fprintf(stderr,"%d: share,res=%e(%f)\n",
      threadid,result,((double)(clock()-t))/CLOCKS_PER_SEC);

    result=0.0;
    t = clock ();
    for (i=0; i < SIZE-1; i++)
    {
      result = result + (double)(c[i]);
    }

    fprintf(stderr,"%d: local, res =%e(%f)\n",
       threadid,result,((double)(clock()-t))/CLOCKS_PER_SEC);

}

int main() {

    char* cc = mmap (0, SIZE, PROT_READ|PROT_WRITE,
    MAP_SHARED|MAP_ANONYMOUS, 0, 0);
    char* cond = cc+SIZE-1;
    int i, status, rc;
    long t;

    c=(char*)malloc(SIZE);

    for (i=0; i<SIZE-1; i++)
            cc[i] = (char)i;
   for (i=0; i<SIZE-1; i++)
            c[i] = (char)i;

   BusyWork(-1,cc);

    for(i=0; i<NUM_THREADS; i++){
       rc = fork ();

       if( rc<0){
          printf("ERROR: %d\n",rc);
          exit(1);
       }
       if (rc == 0) {
          BusyWork(i,cc);
          return 0;
       }
    }
    while(wait(NULL)>0);
    return 0;
}

/**************************************************************/

При зауске я скипнул процессоры 1 и 2, поскольку у меня 
довольно хитрая топология: каждый узел двухпроцессорный, но 
еще и между соседними узлами канал пошустрее.

dplace -e -c0,3,4,5,6,7,8,9,10,11,12 ./trymmap_forks2
-1 start...-1: share,res=-2.097152e+08(1.942240)
-1: local, res =-2.097152e+08(1.938336)
0 start...1 start...2 start...4 start...5 start...3 start...8
start...6 start...9 start...7 start...0:
share,res=-2.097152e+08(3.251056)
0: local, res =-2.097152e+08(1.948096)
3: share,res=-2.097152e+08(4.234864)
5: share,res=-2.097152e+08(4.249504)
1: share,res=-2.097152e+08(4.119696)
2: share,res=-2.097152e+08(3.908880)
3: local, res =-2.097152e+08(1.925648)
5: local, res =-2.097152e+08(1.919792)
4: share,res=-2.097152e+08(4.278784)
7: share,res=-2.097152e+08(3.917664)
9: share,res=-2.097152e+08(4.267072)
6: share,res=-2.097152e+08(3.595584)
4: local, res =-2.097152e+08(1.933456)
7: local, res =-2.097152e+08(1.919792)
8: share,res=-2.097152e+08(3.767360)
6: local, res =-2.097152e+08(1.916864)
1: local, res =-2.097152e+08(1.920768)
2: local, res =-2.097152e+08(1.925648)
9: local, res =-2.097152e+08(1.921744)
8: local, res =-2.097152e+08(1.925648)

Die-Hard ★★★★★
() автор топика

ВОТ ЭТО НОМЕР!!!!

Случайно моя предыдущая прога оказалась немного не такой, как я
описывал словами: в функцию BusyWork вкрались строчки
for (i=0; i<SIZE-1; i++)
   c[i] = (char)i;

И все стало работать , как мне надо: COW, действительно, 
бахнулся в локальный блок!

А вот если я из дочки ничего не писал в память, забитую до форка, а 
только читал из нее, то она оказывается не аффинной:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/mman.h>
#include <fcntl.h>
#include <signal.h>
#include <time.h>
#include <unistd.h>
#include <sys/wait.h>


#define KB 1024
#define MB 1024*KB

#define SIZE 400*MB

#define NUM_THREADS     10


char *c =NULL;

void BusyWork(int threadid,char* cc)
{
    int i;
    double result=0.0;
    long t;

    fprintf(stderr,"%d start...",threadid);

    t = clock ();
    for (i=0; i < SIZE-1; i++)
    {
      result = result + (double)(cc[i]);
    }

    fprintf(stderr,"%d: share,res=%e(%f)\n",
      threadid,result,((double)(clock()-t))/CLOCKS_PER_SEC);

    result=0.0;
    t = clock ();
    for (i=0; i < SIZE-1; i++)
    {
      result = result + (double)(c[i]);
    }

    fprintf(stderr,"%d: local, res =%e(%f)\n",
       threadid,result,((double)(clock()-t))/CLOCKS_PER_SEC);

}

int main() {

    char* cc = mmap (0, SIZE, PROT_READ|PROT_WRITE,
    MAP_SHARED|MAP_ANONYMOUS, 0, 0);
    char* cond = cc+SIZE-1;
    int i, status, rc;
    long t;

    c=(char*)malloc(SIZE);

    for (i=0; i<SIZE-1; i++)
            cc[i] = (char)i;
   for (i=0; i<SIZE-1; i++)
            c[i] = (char)i;

   BusyWork(-1,cc);

    for(i=0; i<NUM_THREADS; i++){
       rc = fork ();

       if( rc<0){
          printf("ERROR: %d\n",rc);
          exit(1);
       }
       if (rc == 0) {
          BusyWork(i,cc);
          return 0;
       }
    }
    while(wait(NULL)>0);
    return 0;
}

/**************************************************************/
dplace -e -c0,3,4,5,6,7,8,9,10,11,12 ./trymmap_forks2
-1 start...-1: share,res=-2.097152e+08(1.967616)
-1: local, res =-2.097152e+08(1.964688)
0 start...3 start...1 start...6 start...2 start...5 start...7
start...4 start...8 start...9 start...0:
share,res=-2.097152e+08(2.880176)
9: share,res=-2.097152e+08(3.599488)
2: share,res=-2.097152e+08(3.770288)
1: share,res=-2.097152e+08(3.620960)
3: share,res=-2.097152e+08(3.836656)
0: local, res =-2.097152e+08(2.905552)
4: share,res=-2.097152e+08(3.794688)
8: share,res=-2.097152e+08(3.887408)
5: share,res=-2.097152e+08(3.802496)
6: share,res=-2.097152e+08(3.801520)
9: local, res =-2.097152e+08(3.670736)
1: local, res =-2.097152e+08(3.799568)
2: local, res =-2.097152e+08(3.782976)
7: share,res=-2.097152e+08(3.736128)
8: local, res =-2.097152e+08(4.079680)
5: local, res =-2.097152e+08(3.867888)
3: local, res =-2.097152e+08(4.001600)
6: local, res =-2.097152e+08(3.398432)
4: local, res =-2.097152e+08(3.760528)
7: local, res =-2.097152e+08(3.739056)

И что теперь делать?


Die-Hard ★★★★★
() автор топика
Ответ на: комментарий от Die-Hard

> Меня интересуют исключительно результаты COW. И разница
> оказывается вполне ощутима: доступ к памяти далекого ЦПУ
> в 4 раза дольше, чем к своей памяти!

на то и numa. я имел в виду, что setaffinity() может сделать
child _после_ fork(), едва ли разница будет заметна.

idle ★★★★★
()
Ответ на: комментарий от Die-Hard

То есть. я понял, в чем проблема -- copy - то "on write", а не "on read"!

Если я распределяю память в папе, а читаю ее из дочки, то копирования страниц не происходит, и физически память принадлежит папе.

Если я хоть один байт потом изменю, то страница сразу скопируется.

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

Die-Hard ★★★★★
() автор топика
Ответ на: комментарий от idle

idle (*) (26.06.2004 1:16:09):

> setaffinity() может сделать child _после_ fork(), едва ли разница будет заметна.

Да, я это понял, спасибо.

Die-Hard ★★★★★
() автор топика
Ответ на: комментарий от Die-Hard

> Я вот такой тест провел:

ничего не соображаю уже, спать хочется

> выделяет память malloc'ом (ПОСЛЕ форка) того же размера, что и шара.

malloc на 400M это все тот же mmap(), не очень понимаю,
почему вы эти два случая отдельно рассматриваете

idle ★★★★★
()
Ответ на: комментарий от idle

idle (*) (26.06.2004 1:50:33):

> malloc на 400M это все тот же mmap(), не очень понимаю, почему вы эти два случая отдельно рассматриваете

Потому, что "шара" -- то, что разделяется после форка всеми дочками (из-за MAP_SHARED),а маллочный mmap идет с MAP_PRIVATE, чтобы COW данные сдублировал.

Поэтому у меня имеется только ОДИН экземпляр "шары", распределенный в блоке памяти родительского ЦПУ, а от маллочной памяти я хочу, чтобы она COWнулась в память, аффинную дочкам.

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

Меня спасут чисто внешние решения:dplace заставляет fork() вести себя так, как мне надо. А проблему с отсутствием copy без write тоже просто решить: я могу форкнуться до выделения какой-либо памяти (мне это не критично).

Die-Hard ★★★★★
() автор топика
Ответ на: комментарий от idle

> malloc на 400M это все тот же mmap(), не очень понимаю,
> почему вы эти два случая отдельно рассматриваете

иногда я такую чушь несу, что самому весело. mmap() тот же,
но уже MAP_PRIVATE.

> Если я хоть один байт потом изменю, то страница сразу скопируется.

только для MAP_PRIVATE (malloc), для MAP_SHARED (char* cc) этого,
конечно, не будет.

на всякий случай. в 2.4. vm_area_struct (структура, контролирующая
сегмент памяти, полученный mmap) не имеет "адреса" в numa топологии,
и не является аргументом alloc_page() в do_anonymous_page()/shmem_getpage().
поэтому абсолютно все равно, когда был сделан mmap/malloc - до или после
fork(). имеет значение current->processor в тот момент, когда происходит
COW и вызывается alloc_page() (пока вы только читаете, во всех этих 400Mb
одна и та же ZERO_PAGE).

так что, вроде бы, все три теста дают ожидаемый результат.

> А нельзя ли мне принудительно все страницы скопировать, т.е. нет ли какого
> сисколла, чтобы разом скопировать всю память, не дожидаясь записи?

такого вызова нет, но mlock()/mlockall() дадут этот результат как
побочный эффект.

idle ★★★★★
()
Ответ на: комментарий от Die-Hard

> Потому, что "шара" -- то, что разделяется после форка всеми дочками

:)) похоже, мы просыпаемся в одно время :)

кстати, попробуйте 2.6. кроме всяких numa улучшений, там
еще есть hugetlb mappings. думаю, на таких об'емах это
сыграет.

idle ★★★★★
()
Ответ на: комментарий от idle

idle (*) (26.06.2004 14:18:17):

Во-первых, большое спасибо за конструктивную беседу. Вы мне сэкономили (по крайней мере) несколько дней разборок с очевидными вещами.

> :)) похоже, мы просыпаемся в одно время :)

Не совсем: я после субботнего шей^H^Hоппинга вспомнил, что мне надо было кое-что вчера отослать, о чем я забыл, и пришлось зайти в оффис. А то б я тут часам к 8 вечера (по Москве) объявился :-)

> кстати, попробуйте 2.6. кроме всяких numa улучшений, там еще есть hugetlb mappings. думаю, на таких об'емах это сыграет.

Ну, у меня компъютер "на гарантии" -- еще 2 года поддержки от SGI. Ессно, при условии, что я их решениями пользуюсь -- а у них пока 2.6 только в testing значится.

И, потом, я не могу его так просто перегрузить -- на нем юзера по месяцам задачи гоняют.

Конечно, я могу отлаживаться на своем десктопе -- но тогда оно никому не нада будет :-)

Die-Hard ★★★★★
() автор топика
Вы не можете добавлять комментарии в эту тему. Тема перемещена в архив.