Хороший оптимизирующий компилятор должен генерировать одинаковый машинный код для обеих версий.Просто определите ваши векторные константы как локальные или используйте их анонимно для максимальной читабельности;позвольте компилятору позаботиться о распределении регистров и выберите самый дешевый способ справиться с исчерпанием регистров, если это произойдет.
Лучше всего помогать компилятору, если это возможно, использовать меньше различных констант.например, вместо _mm_and_si128
с set1_epi16(0x00FF)
и 0xFF00
, используйте _mm_andn_si128
, чтобы замаскировать другой способ.Обычно вы ничего не можете сделать, чтобы повлиять на то, что он решает хранить в регистрах, а не на, но, к счастью, компиляторы довольно хороши в этом, потому что это также важно для скалярного кода.
Компилятор подниметконстанты вне цикла (даже встраивая вспомогательную функцию, содержащую константы) или, если они используются только в одной стороне ветви, перенести установку в эту сторону ветви.
Исходный код вычисляет точно то же самоебез каких-либо различий в видимых побочных эффектах, поэтому правило «как будто» дает компилятору свободу делать это.
Я думаю, что компиляторы обычно регистрируют распределение и выбирают, что разлить / перезагрузить (или простоиспользовать векторную константу только для чтения) после выполнения CSE (исключение общих подвыражений) и определения инвариантов и констант цикла, которые можно поднять.
Когда он обнаруживает, что не имеет достаточного количества регистров для хранения всех переменных и констант вregs внутри цикла, первый выбор для чего-то, чтобы не keep в регистре обычно является вектором, инвариантным к циклу, либо константой времени компиляции, либо чем-то, вычисляемым до цикла.
Дополнительная загрузка, которая попадает в кэш L1d, дешевле, чем хранение (или разлив) / перезагрузка переменной внутри цикла.Таким образом, компиляторы выбирают загрузку констант из памяти независимо от того, где вы поместили определение в исходном коде.
Частью написания на C ++ является то, что у вас есть компилятор, который примет это решение за вас.Поскольку разрешено делать одно и то же для обоих источников, выполнение разных действий будет пропущенной оптимизацией по крайней мере для одного из случаев.(Лучшее, что можно сделать в каждом конкретном случае, зависит от окружающего кода, но обычно использование векторных констант в качестве операндов источника памяти хорошо, когда компилятору не хватает регистров.)
Это вопрос некоторыхКонцепция типа «ptr добавляет 2 дополнительные задержки»?
Микросинтезирование операнда источника памяти не удлиняет критический путь от непостоянного входа к выходу.Load uop может начаться, как только адрес будет готов, а для векторных констант это обычно либо режим относительной RIP, либо [rsp+constant]
адресации.Поэтому обычно загрузка готова к выполнению, как только она поступает в вышедшую из строя часть ядра.Предполагая попадание в кэш L1d (поскольку он будет оставаться горячим в кэше, если загружается при каждой итерации цикла), это всего ~ 5 циклов, поэтому он легко будет готов во времени, если на входе векторного регистра будет узкое место в цепочке зависимостей.
Это даже не влияет на пропускную способность внешнего интерфейса.Если вы не ограничены пропускной способностью порта загрузки (2 загрузки в такт на современных процессорах x86), это, как правило, не имеет значения.(Даже с высокоточными методами измерения.)