2012-06-13 6 views
1

Я работал с фрагментом кода, который интенсивно связан с памятью. Я пытаюсь оптимизировать его в одном ядре, вручную применяя блокировку кэша, sw preetetching, разворачивание циклов и т. Д. Несмотря на то, что блокирование кеша дает значительное улучшение производительности. Однако, когда я запускаю разворот цикла, я получаю огромную деградацию производительности.Влияние развертки Loop на данные с привязкой к памяти

Я компилирую с Intel icc с флагами компилятора -O2 и -ipo во всех моих тестовых случаях.

Мой код похож на этот (3D 25-балльной трафарета):

void stencil_baseline (double *V, double *U, int dx, int dy, int dz, double c0, double c1,  double c2, double c3, double c4) 
    { 
    int i, j, k; 

    for (k = 4; k < dz-4; k++) 
    { 
    for (j = 4; j < dy-4; j++) 
    { 
     //x-direction 
      for (i = 4; i < dx-4; i++) 
     { 
      U[k*dy*dx+j*dx+i] = (c0 * (V[k*dy*dx+j*dx+i]) //center 
       + c1 * (V[k*dy*dx+j*dx+(i-1)] + V[k*dy*dx+j*dx+(i+1)])     
       + c2 * (V[k*dy*dx+j*dx+(i-2)] + V[k*dy*dx+j*dx+(i+2)])  
       + c3 * (V[k*dy*dx+j*dx+(i-3)] + V[k*dy*dx+j*dx+(i+3)]) 
       + c4 * (V[k*dy*dx+j*dx+(i-4)] + V[k*dy*dx+j*dx+(i+4)])); 

     } 

     //y-direction 
     for (i = 4; i < dx-4; i++) 
     { 
      U[k*dy*dx+j*dx+i] += (c1 * (V[k*dy*dx+(j-1)*dx+i] + V[k*dy*dx+(j+1)*dx+i]) 
       + c2 * (V[k*dy*dx+(j-2)*dx+i] + V[k*dy*dx+(j+2)*dx+i]) 
       + c3 * (V[k*dy*dx+(j-3)*dx+i] + V[k*dy*dx+(j+3)*dx+i]) 
       + c4 * (V[k*dy*dx+(j-4)*dx+i] + V[k*dy*dx+(j+4)*dx+i])); 
     } 

     //z-direction 
     for (i = 4; i < dx-4; i++) 
     { 
      U[k*dy*dx+j*dx+i] += (c1 * (V[(k-1)*dy*dx+j*dx+i] + V[(k+1)*dy*dx+j*dx+i]) 
       + c2 * (V[(k-2)*dy*dx+j*dx+i] + V[(k+2)*dy*dx+j*dx+i]) 
       + c3 * (V[(k-3)*dy*dx+j*dx+i] + V[(k+3)*dy*dx+j*dx+i]) 
       + c4 * (V[(k-4)*dy*dx+j*dx+i] + V[(k+4)*dy*dx+j*dx+i])); 

     } 

    } 
    } 

} 

Когда я петля разворачивая на внутреннем цикле (размер я) и раскатать в направлениях х, у, г отдельно от Раскатайте коэффициент 2,4,8 соответственно, я получаю ухудшение производительности во всех 9 случаях, т. е. разворачивается на 2 по направлению x, разворачивается на 2 по направлению y, разворачивается на 2 в направлении z, разворачивается на 4 в направлении x ... и т.д. Но когда я выполняю цикл разворачивания по самому внешнему циклу (размерность k) в 8 раз (2), я получаю хорошее улучшение производительности, которое даже лучше блокировки кеша.

Я даже пробовал профилировать свой код с помощью Intel Vtune. Это было похоже на узкие места, в основном из-за 1.LLC Miss и 2. Недостатки загрузки LLC, обслуживаемые Remote DRAM.

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

Я не уверен, как интерпретировать эти статистические данные. Может кто-то помочь пролить свет на это.

+0

как в сторону: предположительно компилятор оптимизирует k * dy * dx во временную точку? –

+0

@Mitch Wheat: Я использую общее исключение подвыражения, принимая во внимание k * dx * dy и j * dx в моем исходном коде. Это скорее пример кода – Anusuya

+0

Можете ли вы использовать инструкции SSE для вычисления некоторых из этих данных? С быстрым взглядом это выглядит подходящим ... – sarnold

ответ

1

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

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

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

«Загрузка промахов, обслуживаемых удаленной DRAM» означает, что вы выделили память на одном узле NUMA, но теперь вы работаете на другом. Ответ на вопрос о настройке процесса или сходстве потоков на основе NUMA.

Удаленная DRAM занимает почти в два раза больше, чтобы читать как локальную DRAM на компьютерах Intel, которые я использовал.

 Смежные вопросы

  • Нет связанных вопросов^_^