Инструкция SSE внутри вложенных циклов - PullRequest
0 голосов
/ 05 декабря 2011

В моем коде есть несколько вложенных циклов for, и я пытаюсь использовать инструкции Intel SSE на ядре Intel i7 для ускорения работы приложения. Структура кода следующая (val задается в более высоком цикле for):

_m128 in1,in2,tmp1,tmp2,out;
float arr[4] __attribute__ ((aligned(16)));
val = ...;

... several higher for loops ...
for(f=0; f<=fend; f=f+4){
    index2 = ...;
    for(i=0; i<iend; i++){
        for(j=0; j<jend; j++){
            inputval = ...;
            index = ...;
            if(f<fend-4){
                arr[0] = array[index];
                arr[1] = array[index+val];
                arr[2] = array[index+2*val];
                arr[3] = array[index+3*val];
                in1  = _mm_load_ps(arr);
                in2  = _mm_set_ps1(inputval);
                tmp1 = _mm_mul_ps(in1, in2);
                tmp2 = _mm_loadu_ps(&array2[index2]);
                out  = _mm_add_ps(tmp1,tmp2);
                _mm_storeu_ps(&array2[index2], out);
            } else {
                //if no 4 values available for SSE instruction execution execute serial code
                for(int u = 0; u < fend-f; u++ ) array2[index2+u] += array[index+u*val] * inputval;
            }
        }
    }
}

Я думаю, что есть две основные проблемы: буфер, используемый для выравнивания значений из 'массива', и тот факт, что когда не осталось 4 значения (например, когда fend = 6, осталось два значения, которые должны быть выполнены с помощью последовательный код). Есть ли другой способ загрузки значений из in1 и / или выполнения операций SSE с 3 или 2 значениями?


Спасибо за ответы. Я думаю, что загрузка так же хороша, как и получается, но есть ли обходной путь для части 'Остаток' внутри оператора else, который можно решить с помощью инструкций SSE?

Ответы [ 3 ]

2 голосов
/ 05 декабря 2011

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

arr[0] = array[index];                   //  Data Movement
arr[1] = array[index+val];               //  Data Movement
arr[2] = array[index+2*val];             //  Data Movement
arr[3] = array[index+3*val];             //  Data Movement
in1  = _mm_load_ps(arr);                 //  Data Movement
in2  = _mm_set_ps1(inputval);            //  Data Movement
tmp1 = _mm_mul_ps(in1, in2);             //  Computation
tmp2 = _mm_loadu_ps(&array2[index2]);    //  Data Movement
out  = _mm_add_ps(tmp1,tmp2);            //  Computation
_mm_storeu_ps(&array2[index2], out);     //  Data Movement

Хотя это «возможно» можно упростить. Я совсем не уверен, что в этой ситуации векторизация вообще будет полезна.

Вам придется изменить макет данных, чтобы избежать быстрого доступа index + n*val.

Или вы можете подождать, пока инструкции AVX2 по сбору / рассеянию станут доступны в 2013 году?

1 голос
/ 05 декабря 2011

Вы можете выразить это:

            arr[0] = array[index];
            arr[1] = array[index+val];
            arr[2] = array[index+2*val];
            arr[3] = array[index+3*val];
            in1  = _mm_load_ps(arr);

более кратко, как:

            in1  = _mm_set_ps(array[index+3*val], array[index+2*val], array[index+val], array[index]);

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

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

0 голосов
/ 05 декабря 2011

если вы хотите получить полную выгоду от SSE (в 4 раза или более быстрее, чем оптимизированный код без явного использования SSE), вы должны убедиться, что ваш макет данных таков, что вам когда-либо понадобятся только выровненные загрузки и сохранения , Хотя использование _mm_set_ps (w, z, y, x) в вашем фрагменте кода может помочь, вам следует избегать необходимости в этом, то есть избегать пошагового доступа (они менее эффективны, чем один _mm_load_ps).

Что касается проблемы последних нескольких элементов <4, я обычно гарантирую, что все мои данные не только выровнены по 16 байтам, но и размеры массивов кратны 16 байтам, так что у меня никогда не было таких свободных оставшихся элементов. Конечно, настоящая проблема может иметь запасные элементы, но эти данные обычно могут быть установлены так, чтобы они не вызывали проблемы (установите нейтральные элементы, то есть ноль для аддитивных операций). В редких случаях вам нужно работать только с подмножеством массива, который начинается и / или заканчивается в невыровненной позиции. В этом случае можно использовать побитовые операции (_mm_and_ps, _mm_or_ps) для подавления операций с нежелательными элементами. </p>

...