Влияние развертывания цикла на данные, связанные с памятью

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

Я собираю с 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]));

        }

    }
   }

 }

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

Я даже пытался профилировать свой код с Intel Vtune. Казалось, что узкие места возникли в основном из-за пропусков 1.LLC и 2. Пропусков загрузки LLC, обслуживаемых удаленным DRAM.

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

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

1 ответ

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

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

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

"Несоответствия загрузки, обслуживаемые удаленной DRAM" означает, что вы распределили память на одном узле NUMA, но теперь вы работаете на другом. Настройка процесса или сходства потоков на основе NUMA - это ответ.

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

Другие вопросы по тегам