Комментарии 30
Подтверждаю — не смог заставить clang сгенерировать похожий код. На O2 задействуется SSE, без оптимизации — просто вызов memset. Так что это какая-то проблема VС.
+1
Кстати, GCC 6.1 что с оптимизациями, что без использует rep stosq
, который быстрее movaps
, выдаваемого clang.
+1
Хм, по ссылке что вы дали, movaps на некоторых процах отрабатывает быстрее…
0
Это если movaps не потребует переключения контекста SSE. Или его теперь принудительно меняют при переключении на другой процесс?
0
Переключение режимов SSE-AVX занимает около 70 тактов. При обнулении малого массива и переключении SSE-AVX на каждой итерации разница во времени выполнения будет отличаться в разы, чего не наблюдается.
0
Переключение контекста при исключении #NM выполняет операционная система. Это не 70 тактов.
+1
Я боюсь спросить, что вы такое делаете, что знаете подробности таких деталей?
0
Я один из разработчиков ОС Колибри и как раз делал переключение контекста fpu/sse. Поэтому знаю как всё работает. Обычно применяют отложенное переключение контекста. При установленном в 1 бите TS регистра cr0 первая команда сопроцессора вызывает исключение 7(#NM). Ядро сбрасывает бит TS и производит переключение контекста сопроцессора. Поэтому сложно сказать сколько тактов на самом деле займёт команда. На мой взгляд применять simd нет смысла, если объём данных меньше пары килобайт. С учётом длинных регистров avx планка ещё выше. В принципе ядро может отслеживать количество исключений для каждого процесса, и если сопроцессор активно используется переключать контекст сразу. Не знаю, используется такая схема или нет.
0
Подождите. Речь не о FPU/SSE, а о SSE/AVX, при переключении которого не происходит никаких исключений. Просто fallback YMM->XMM реализован довольно костыльно, и требует теневого копирования регистров при смене набора команд. На это и уходит около 70 тактов — точное количество зависит от реального числа «физических» регистров.
0
А при чём здесь AVX? Первоначально речь шла о movaps которая к AVX не относится. Это команда SSE. А внутренняя кухня процессора при использовании разных наборов команд дело тёмное. Откуда эта информация про теневое копирование? И для чего? Регистры ymm удваивают xmm и совпадают по формату. Это же не альяс fpu/mmx.
0
При том, что пользовательский код может содержать инструкции AVX, а тут опа — обнуление переменной через SSE. Эта ситуация не такая уж и редкая, на первый взгляд. Вот здесь чуть подробнее:
https://software.intel.com/en-us/articles/intel-avx-state-transitions-migrating-sse-code-to-avx
Информация про теневое копирование следует из технических спецификаций. Причина этому — аппаратная реализация регистров (маппинг), операций с ним на низком уровне и упрощение архитектуры процессора (реализация AVX команд через существующие блоки SSE и отказ от блоков копирования) за счёт потери некоторой функциональности.
https://software.intel.com/en-us/articles/intel-avx-state-transitions-migrating-sse-code-to-avx
Информация про теневое копирование следует из технических спецификаций. Причина этому — аппаратная реализация регистров (маппинг), операций с ним на низком уровне и упрощение архитектуры процессора (реализация AVX команд через существующие блоки SSE и отказ от блоков копирования) за счёт потери некоторой функциональности.
0
Ага. Уже разобрался. Это особенность двух близких микроархитектур Sandy Bridge и Ivy Bridge в отличие от исключения при установленном бите cr0.ts, которое всегда работает. Кроме того компиляторы стараются защитить программистов от выстрела в ногу и не генерируют sse и avx код в одном объектном файле.
0
Компилятор генерирует адекватный код ровно до того момента, как программист начинает эти инструкции использовать. Самая неприятная для компилятора ситуация — это когда программист начинает вручную использовать avx и делать в коде две ветки: legacy и avx.
Решение простое: просто генерить два комплекта бинарников, чтобы не допустить смешения инструкций.
Решение простое: просто генерить два комплекта бинарников, чтобы не допустить смешения инструкций.
0
А xor eax, eax
обнуляет весь rax?
обнуляет весь rax?
+2
Может это на случая, если по какой-то ну очень важной причине программист напишет так:
char buffer[32] = { 1 };
и хочет получить
1 0 0 0 0 ...
Хотя я не знаю, зачем это может понадобиться.
0
Как я понял, проблема актуальна только для типов, размер которых меньше чем sizeof(void*)? Т.е. для массива double всей этой свистопляски с выравниванием не будет в принципе и будет одна лишняя операция, если компилятор не догадается всё в один memset впихнуть?
+2
SSE регистры имеют размер 16 байт, AVX — 32 байта. Их запись также должна быть выровнена.
0
SSE регистры не имеют никакого отношения к функции memset.
0
SSE регистры имеют прямое отношение к memset, т.к. компиляторы бывают умные и могут его соптимизировать до пары ассемблерных инструкций, не используя библиотечные вызовы. И даже просто выкинуть обнуление, если после инициализации присваиваются другие значения.
0
В статье речь о memset, а не про то, как компилятор может оптимизировать SSE инструкции. В контексте SSE память должна быть выровнена на 16 байт, с массивами вообще веселуха получается с «лишними» байтами в начале массива. Думаю, про такие специфичные случаи можно вообще не говорить.
0
НЛО прилетело и опубликовало эту надпись здесь
Не осталось, если не считать следующие две:
1) Когда критично быстродействие и размер использованной памяти.
2) Когда массивы живут не только «внутри» чистого C++, где мы можем обмазываться контейнерами сколько угодно, но и активно куда-то передаются или откуда-то получаются. Я имею в виду связки C++ с высокоуровневыми ЯП, а также различные прикладные расширения C++ типа Cuda.
1) Когда критично быстродействие и размер использованной памяти.
2) Когда массивы живут не только «внутри» чистого C++, где мы можем обмазываться контейнерами сколько угодно, но и активно куда-то передаются или откуда-то получаются. Я имею в виду связки C++ с высокоуровневыми ЯП, а также различные прикладные расширения C++ типа Cuda.
+2
std::array ведь не умеет выводить размер из списка инициализации?..
0
Как минимум, локальные «сырые» массивы выделяются на стеке, а не в хипе, что намного быстрее.
0
Зарегистрируйтесь на Хабре, чтобы оставить комментарий
Не так-то просто обнулять массивы в VC++ 2015