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

Я нахожусь в процессе кодирования простой функции свертки на С++, начиная с самой простой свертки "скользящего окна" с обычными продуктами (пока без БПФ), вплоть до 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, так как казалось необходимым заставить код Пола компилироваться (т.е. _aligned_malloc возвращает указатель void). Я приближаюсь к тому, чтобы просто прочитать фрагменты файла с помощью 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