C-адрес оператора очень медленный - PullRequest
2 голосов
/ 28 января 2012

Я реализую программу MPI на C, которая выполняет SOR (последовательное перенапряжение) в сетке. При тестировании я обнаружил нечто совершенно неожиданное, а именно, что адрес оператора & выглядит очень медленным. Я не могу показать весь код здесь, и он также слишком длинный, но соответствующие части следующие.

double maxdiff, diff;

do {
    maxdiff = 0.0;

    /* inner loops updating maxdiff a lot */

    /* diff is used as a receive buffer here */
    MPI_Allreduce(&maxdiff, &diff, 1, MPI_DOUBLE, MPI_MAX, my_comm);
    maxdiff = diff;
} while(maxdiff > stopdiff);

Здесь stopdiff - это магическая ценность. Медленное поведение появляется в операции MPI_Allreduce(). Странно то, что эта операция даже очень медленная при работе только на одном узле, хотя в этом случае связь не требуется. Когда я закомментирую операцию, время выполнения определенной проблемы на одном узле уменьшается с 290 секунд до всего 225 секунд. Кроме того, когда я заменяю операцию вызовом MPI_Allreduce(), используя другие фиктивные переменные, я также получаю 225 секунд. Таким образом, похоже, что он специально получает адреса maxdiff и diff, что вызывает замедление.

Я обновил программу, сделав две дополнительные double переменные, используемые в качестве временных буферов отправки / получения, следующим образом.

send_buf = maxdiff;
MPI_Allreduce(&send_buf, &recv_buf, 1, MPI_DOUBLE, MPI_MAX, my_comm);
maxdiff = recv_buf;

Это также заставило программу работать за 225 секунд вместо 290. Мой вопрос, очевидно, как это может быть?

У меня есть подозрение: программа компилируется с использованием gcc с уровнем оптимизации O3, поэтому я подозреваю, что компилятор выполняет некоторую оптимизацию, которая делает опорную операцию очень медленной. Например, возможно, переменные хранятся в регистрах процессора, потому что они так часто используются в цикле, и из-за этого они должны быть сброшены обратно в память всякий раз, когда запрашивается их адрес. Тем не менее, я не могу выяснить через поиск в Google, какая оптимизация может вызвать эту проблему, и я хотел бы быть уверен в этой проблеме. У кого-нибудь есть идея, что может быть причиной этого?

Заранее спасибо!

Я должен добавить здесь другую важную информацию. Конкретная проблема при запуске заполняет память довольно плохо. Он использует 3 ГБ памяти, а узлы имеют 4 ГБ ОЗУ. Я также наблюдаю, что замедление ухудшается при больших размерах проблем, так как ОЗУ заполняется, поэтому величина нагрузки на ОЗУ, по-видимому, является фактором проблемы. Кроме того, как ни странно, когда я добавляю MPI_Allreduce() только один раз после цикла, а не внутри цикла, замедление все еще наблюдается в неоптимизированной версии программы, и это все равно так же плохо. Программа не запускается так быстрее.

Как указано ниже, это часть вывода сборки gcc. К сожалению, у меня недостаточно опыта сборки, чтобы решить эту проблему. Это версия с добавленными буферами отправки и получения, поэтому версия работает за 225 секунд, а не за 290.

    incl    %r13d
    cmpl    $1, %r13d
    jle     .L394
    movl    136(%rsp), %r9d
    fldl    88(%rsp)
    leaq    112(%rsp), %rsi
    leaq    104(%rsp), %rdi
    movl    $100, %r8d
    movl    $11, %ecx
    movl    $1, %edx
    fstpl   104(%rsp)
    call    MPI_Allreduce
    fldl    112(%rsp)
    incl    84(%rsp)
    fstpl   40(%rsp)
    movlpd  40(%rsp), %xmm3
    ucomisd 96(%rsp), %xmm3
    jbe     .L415
    movl    140(%rsp), %ebx
    xorl    %ebp, %ebp
    jmp     .L327

Вот то, что я считаю соответствующей частью в программе без дополнительных буферов отправки и получения, поэтому версия, которая запускается за 290 секунд.

    incl    %r13d
    cmpl    $1, %r13d
    jle     .L314
    movl    120(%rsp), %r9d
    leaq    96(%rsp), %rsi
    leaq    88(%rsp), %rdi
    movl    $100, %r8d
    movl    $11, %ecx
    movl    $1, %edx
    call    MPI_Allreduce
    movlpd  96(%rsp), %xmm3
    incl    76(%rsp)
    ucomisd 80(%rsp), %xmm3
    movsd   %xmm3, 88(%rsp)
    jbe     .L381
    movl    124(%rsp), %ebx
    jmp     .L204

Ответы [ 4 ]

3 голосов
/ 28 января 2012

Это звучит немного маловероятно для меня. Получение адреса некоторого двойника на самом деле должно быть довольно быстрым.

Если вы все еще подозреваете это, как насчет получения адресов только один раз?

double maxdiff, diff;
double *pmaxdiff = &maxdiff, *pdiff = &diff;

...

MPI_Allreduce(pmaxdiff, pdiff, 1, MPI_DOUBLE, MPI_MAX, my_comm);

...

В целом, я подозреваю, что замедление произойдет где-то еще, но попробуйте.

0 голосов
/ 02 февраля 2012

У меня есть подозрение, что проблема связана не с адресом, а с тем, что этот адрес в итоге оказывается. Вот что я имею в виду.

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

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

Попробуйте следующее: замените

MPI_Allreduce(&maxdiff, &diff, 1, MPI_DOUBLE, MPI_MAX, my_comm);
maxdiff = diff;

с

MPI_Allreduce(MPI_IN_PLACE, &maxdiff, 1, MPI_DOUBLE, MPI_MAX, my_comm);

Хотя это всего лишь теория.

0 голосов
/ 30 января 2012

Я рекомендую использовать инструмент анализа производительности [1] для программ MPI, чтобы лучше понять, что происходит. Я бы предположил, что это фактический MPI_Allreduce вызов, который продолжается в разных версиях вашего кода, а не вычисление адреса. Как вы упомянули, здесь очень важна память - поэтому счетчики PAPI (ошибки в кэше и т. Д.) Могут дать подсказку для решения проблемы.

[1], например:

0 голосов
/ 28 января 2012

Я бы посоветовал посмотреть на сгенерированную сборку и / или опубликовать ее здесь.

Вы можете получить сборку, используя gcc -S <source>

...