SSE: reinterpret_cast‹__m128*› вместо _mm_load_ps

В процес съм на кодиране на проста функция за навиване в C++, започвайки от най-елементарната навивка с "плъзгащ се прозорец" с обикновени продукти (без FFT неща засега), до SEE, AVX и евентуално OpenCL. Все пак срещнах проблем със SSE. Моят код изглежда така:

for (x = 0; x < SIZEX - KSIZEX + 1; ++x)
{
    for (y = 0; y < SIZEY - KSIZEY + 1; ++y)
    {           
        tmp = 0.0f;

        float fDPtmp = 0.0f;
        float *Kp = &K[0];


        for (xi = 0; xi < KSIZEX; ++xi, Kp=Kp+4)
        {                               
            float *Cp = &C[(x+xi)*SIZEY + y];

            __m128 *KpSSE = reinterpret_cast<__m128*>(&K);
            __m128 *CpSSE = reinterpret_cast<__m128*>(&C[(x + xi)*SIZEY + y]);
            __m128 DPtmp = _mm_dp_ps(*KpSSE, *CpSSE, 0xFF);
            _mm_store_ss(&fDPtmp, DPtmp);

            tmp += fDPtmp;
        }

        R[k] = tmp;
        ++k;
    }
}

Необходимите матрици се инициализират по този начин (размерът на тези се счита за добър, защото по-простите реализации работят добре):

__declspec(align(16)) float *C = ReadMatrix("E:\\Code\\conv\\C.bin");
__declspec(align(16)) float *K = ReadMatrix("E:\\Code\\conv\\K.bin");
__declspec(align(16)) float *R = new float[CSIZEX*CSIZEY];

Кодът се срива при y=1, така че смятам, че може да има грешка в начина, по който боравя с указателите. Интересното е, че ако заменя reinterpret_casts с _mm_set_ps, т.е.

__m128 KpSSE = _mm_set_ps(Kp[0], Kp[1], Kp[2], Kp[3]);
__m128 CpSSE = _mm_set_ps(Cp[0], Cp[1], Cp[2], Cp[3]);
__m128 DPtmp = _mm_dp_ps(KpSSE, CpSSE, 0xFF);
_mm_store_ss(&fDPtmp, DPtmp);

всичко работи добре, макар и по-бавно, за което обвинявам всички операции за копиране.

Може ли някой да ме насочи какво точно правя грешно тук?

Благодаря ти много

потупване

Актуализация: Добре, както посочи Пол, проблемът е в ReadMatrix (или друго решение би било да се използва _mm_loadu_ps). Що се отнася до ReadMatrix(), изглежда така:

__declspec(align(16)) float* ReadMatrix(string path)
{
    streampos size;

    ifstream file(path, ios::in | ios::binary | ios::ate);

    if (file.is_open())
    {
        size = file.tellg();
        __declspec(align(16)) float *C = new float[size];
        file.seekg(0, ios::beg);
        file.read(reinterpret_cast<char*>(&C[0]), size);
        file.close();

        return C;
    }
    else cout << "Unable to open file" << endl;
}

Не върши работа. Има ли някакъв друг начин да направите това елегантно, вместо да бъдете принудени да четете файла част по част и да изпълнявате memcpy, което предполагам, че трябва да работи?!

Актуализация:

Все още не изглежда да иска да работи след това

__declspec(align(16)) float* ReadMatrix(string path)
{
    streampos size;

    ifstream file(path, ios::in | ios::binary | ios::ate);

    if (file.is_open())
    {
        size = file.tellg();
        __declspec(align(16)) float *C = static_cast<__declspec(align(16)) float*>(_aligned_malloc(size * sizeof(*C), 16));
        file.seekg(0, ios::beg);
        file.read(reinterpret_cast<char*>(&C[0]), size);
        file.close();

        return C;
    }
    else cout << "Unable to open file" << endl;
}

Добавих static_cast там, тъй като изглеждаше необходимо да се компилира кодът на Paul (т.е. _aligned_malloc връща празен указател). Приближавам се просто да прочета парчета от файла с fread и да ги memcpy в подреден масив. :/ Пак се улавям, че искам съвет. Благодаря много на всички.

потупване

PS: Кодът без SSE работи добре с тези структури от данни. _mm_loadu_ps е по-бавен от използването на не-SSE код.


person pAt84    schedule 24.01.2014    source източник
comment
не можете да имате показалец върху __m128. това няма смисъл, защото __m128 карта към някой от регистрите XMM[0-7].   -  person UmNyobe    schedule 24.01.2014
comment
reinterpret_cast<__m128*> е грешен, трябва да използвате _mm_loadu_ps.   -  person user541686    schedule 24.01.2014
comment
@UmNyobe: не, кодът е ОК и ще работи добре, ако данните са правилно подравнени.   -  person Paul R    schedule 24.01.2014


Отговори (3)


Това не прави това, което си мислите, че прави:

__declspec(align(16)) float *C = ReadMatrix("E:\\Code\\conv\\C.bin");

Всичко, което директивата за подравняване постига тук, е да подравни самия указател (т.е. C) към граница от 16 байта, а не съдържанието на указателя.

Или трябва да поправите ReadMatrix, така че да връща подходящо подравнени данни, или да използвате _mm_loadu_ps, както други вече предложиха.

Не използвайте _mm_set_ps, тъй като това ще генерира много инструкции под капака, за разлика от _mm_loadu_ps, което се преобразува в една инструкция.

АКТУАЛИЗИРАНЕ

Повторихте същата грешка в ReadMatrix:

__declspec(align(16)) float *C = new float[size];

отново това не гарантира подравняването на данните, а само на самия указател C. За да коригирате това разпределение, можете да използвате _mm_malloc или _aligned_malloc:

float *C = _mm_malloc(size * sizeof(*C), 16); 

or

float *C = _aligned_malloc(size * sizeof(*C), 16); 
person Paul R    schedule 24.01.2014
comment
Здравей Пол. Много благодаря за твоята помощ. Все още не работи напълно (вижте оригиналната публикация). Ако не ви натоварва, можете ли (и другите, разбира се), да погледнете още веднъж? - person pAt84; 24.01.2014
comment
Разбира се - вижте АКТУАЛИЗАЦИЯ в отговора по-горе. - person Paul R; 24.01.2014
comment
Пол, изглежда объркано. Добавих промените (моля, вижте актуализацията по-горе), но все още не работи. Сривове на y=1 при __m128 DPtmp = _mm_dp_ps(*KpSSE, *CpSSE, 0xFF);, което е странно, тъй като отстраняването на грешки показва, че тези два __mm128 са добре запълнени. Бихте ли, моля, погледнете още веднъж? - person pAt84; 24.01.2014
comment
Изглежда, че CpSSE ще бъде неправилно подравнен при втората итерация на y, защото y = 1. - person Paul R; 25.01.2014
comment
@PaulR, Как може човек да обработва данни, които не са умножение на 8/16 и да използва SSE? - person Royi; 19.07.2016
comment
@Drazick: в зависимост от случая на използване обикновено бихте използвали неподравнени зареждания, маскиране или скаларен цикъл за обработка на неподравнения/непълен вектор в началото/края на всеки ред. - person Paul R; 19.07.2016
comment
Къде може да се прочете за техника за справяне? Например върху изображения с произволен размер? Споменавате маскиране. - person Royi; 19.07.2016
comment
@Drazick: има някои въпроси и отговори, които случайно обхващат някои аспекти на това, но може би е добра идея да публикувате нов въпрос по тази конкретна тема, тъй като има редица различни техники за различни случаи на употреба и би било добре е да ги съберем на едно място. - person Paul R; 19.07.2016
comment
Какво трябва да търся, за да намеря тези въпроси? Благодаря ти. - person Royi; 19.07.2016
comment
@Drazick: използвайте маркерите [simd] или [sse], когато търсите и преглеждайте отговорите, особено тези с изходен код. Ако обаче зададете нов въпрос, ще отговоря с някои от по-често срещаните техники и без съмнение други също ще се присъединят. - person Paul R; 19.07.2016

В ReadMatrix нямате никаква гаранция, че изразът new връща правилно подравнен указател. Няма значение, че присвоявате на подравнен указател (и дори не съм сигурен дали вашият синтаксис означава, че самият указател е подравнен или към какво сочи).

Трябва да използвате _mm_align, или _mm_malloc, или някакво друго подравнено средство за разпределение.

person Sebastian Redl    schedule 24.01.2014

Не можете да използвате reinterpret_cast тук и разбирам, че _mmloadu_ps е бавен. Но има и друг начин. Развийте цикъла си, прочетете подравнени данни и изместете и маскирайте новата стойност, преди да извършите операции върху нея. Това ще бъде бързо и правилно. Тоест можете да направите нещо като това във вашия вътрешен цикъл:

__m128i x = _mm_load_ps(p);
__m128i y = _mm_load_ps(p + sizeof(float));
__m128i z;

// do your operation on x 1st time this iteration here

z = _mm_slli_si128(y, sizeof(float) * 3);
x = _mm_srli_si128(x, sizeof(float));
x = _mm_or_si128(x, z);

// do your operation on x 2nd time this iteration here

z = _mm_slli_si128(y, sizeof(float) * 2);
x = _mm_srli_si128(x, sizeof(float) * 2);
x = _mm_or_si128(x, z);

// do your operation on x 3rd time this iteration here

z = _mm_slli_si128(y, sizeof(float));
x = _mm_srli_si128(x, sizeof(float) * 3);
x = _mm_or_si128(x, z);

// do your operation on x 4th time this iteration here

x = y; // don’t need to read in x next iteration, only y

loopCounter += 4 * sizeof(float);
person Apriori    schedule 06.02.2014