У меня есть следующий код C ++ 17, который я компилирую с VS 2019 (версия 16.8.6) в режиме x64: struct __declspec (align (16)) Vec2f {float v [2]; }; struct __declspec (выровнять (16)) Vec4f {float v [4]; }; ...

4
François Beaune 2 Мар 2021 в 14:36

1 ответ

Лучший ответ

Я тестирую это на процессоре Intel Haswell, но результаты производительности аналогичны, и я думаю, причина также аналогична, но относитесь к этому с недоверием. Конечно, между Haswell и Zen 2 есть различия, но, насколько я знаю, эффект, который я виню в этом, должен относиться к ним обоим.

Проблема в следующем: виртуальный метод / функция, вызываемая через указатель / что бы это ни было, выполняет 4 скалярных сохранения, но затем основной цикл выполняет векторную загрузку той же самой памяти. Перенаправление от магазина к загрузке может обрабатывать различные случаи, когда значение сохраняется, а затем сразу загружается, но, как правило, это не тот случай, когда загрузка зависит от нескольких хранилищ (в более общем смысле: загрузка, которая зависит от магазина, который только частично предоставляет данные, которые нагрузка пытается загрузить). Гипотетически это возможно, но это не особенность современных микроархитектур.

В качестве эксперимента измените код в виртуальном методе, чтобы использовать хранилище векторов. Например:

__declspec(noinline) virtual Vec4f eval(const Vec2f& p) const noexcept
{
    Vec4f r;
    auto pv = _mm_load_ps(p.v);
    _mm_store_ps(r.v, _mm_shuffle_ps(pv, pv, _MM_SHUFFLE(1, 0, 1, 0)));
    return r;
}

На моем ПК это возвращает время в соответствие с быстрой версией, которая поддерживает гипотезу о том, что проблема вызвана несколькими скалярными хранилищами, подающимися в векторную нагрузку.

Загрузка 16 байтов из 8 байтов Vec2f не является полностью законной, при необходимости это можно обойти. Имея только SSE (1), это немного раздражает, SSE3 был бы хорош для _mm_loaddup_pd (он же movddup).

Этой проблемы не существовало бы, если бы MSVC возвращал результат Vec4f через регистр, а не через выходной указатель, но я не знаю, как убедить его сделать это, кроме изменения типа возвращаемого значения на { {X1}}. __vectorcall также помогает, но заставляет MSVC возвращать структуру в нескольких регистрах , которые затем повторно объединяются в вызывающей программе с дополнительным перемешиванием. Это немного беспорядочно и медленнее, чем любой из быстрых вариантов, но все же быстрее, чем версия с ошибкой переадресации магазина.

6
harold 2 Мар 2021 в 12:46