Комментарии 26
Вы использовали godbolt[dot]org . А пробовали ли дизассемблировать intel-овский или Visual C++ компилятор?
Это перевод.
Интеловский компилятор на годболте есть.
Есть-то он есть, но я вот всегда верю только своим глазам, это на самом деле проще простого получить листинг на асме прямо там, где оно компилялось из бинарников, иструменты есть и не один. Там есть ещё нюансы, скажем то, как функция компиляется может зависть от контекста того, куда она инлайнится, даже с интринсиками могут быть нюансы. Годболт хорош для "пристрелок" или просто для общего понимания, но для анализа реального проекта я всегда ратую за декомпиляцию, запуск релизного кода под бинарным отладчиком или листинг из компилятора на месте. Короче, "доверяй, но проверяй".
Я что-то не понял первый пример, как data может указывать на factor, если factor - локальная для этой функции переменная? Или компилятор исходит из того, что мы в вызывающей функции можем сформировать указатель на локальную переменную вызываемой?
Factor передается по значению, но ты мог передать в data указатель на адрес, где этот factor лежит на стеке вызывающей функции
И? Какая разница, функция оперирует своей локальной копией, которая по соглашению вызовов вообще могла прийти в регистре.
ну, чисто теоретически, указатель может указывать куда угодно, в том числе и на ту область памяти, в которой находится локальная копия.
Указатель на регистр сделать сложно. Даже если как то зафорсить размещение в стеке, сомневаюсь, что можно в вызывающей функции получить указатель на аргумент без UB.
А на практике это UB, которого пугаются сишники и хотят избавиться от самого понятия, хотя оптимизация в принципе возможна именно потому что он есть.
Не может, restrict работает только для поинтеров. Возможно это нейрослоп.
Да, там чушь написана, 1 вариант легко векторизуется (сам сходил проверил). factor передается по значению в регистре, он никак не может алиаситься с массивом.
В примере 4 никакого UB нет, там же знаковые инты, они могут быть меньше нуля и приводить к сумме меньше нуля безо всякого переполнения.
Векторизация ломается не только от ветвлений, иногда хватает просто неудачного выравнивания данных. SoA вместо AoS для геймдева база, но в обычном энтерпрайзе переписывать структуры ради simd мало кто даст
Не понятно, как в первом примере возможна векторизация, если размер данных не определен? Почему компилятор при оптимизации смело думает, что размерность кратна 8 foat?
Помню, приходилось в цикле раскладывать на 8 строк или с помощью pragma гарантировать, что размер (n) кратен 8.
Тут не весь код. Компилятор векторизует с шагом 8, а после сделает 0-7 итераций хвоста цикла.
В современных векторных ISA (AVX512, SVE, RVV) есть предикаты/маски, позволяющие естественным образом обработать часть массива, не полностью влезающую в вектор. Если этого нет - то, как уже написали, после основного цикла идёт явная обработка хвоста.
Вот Intel OneAPI со всеми настройками по умолчанию, в релиз на хасвелл, цикл скачет по восемь:

Можно и разворот добавить прагмой или опцией:
// Версия 1: безобидный цикл
GBDLL_API void scale(float* data, float factor, int n) {
#pragma unroll 4
for (int i = 0; i < n; i++) {
data[i] *= factor;
}
}Тогда будет так:

Версия с __restrict вообще отличий никаких не имеет, более того, если в файле и так и сяк, то компилятор фолдит обе в одну функцию, они для него строго одинаковы.
А вот студия 2026 предпоследняя, в релиз с О2, тут цикл сразу развёрнут

Годболт, кстати, имеет лёгкие отличия, если очень дотошно сравнить:

Какой компилятор выбирал автор оригинала и что у него там не очень поддаётся векторизации — не очень понятно.
Чё-то мне кажется, что автор оригинала в первом примере напортачил, не разобрался или взял непонятно какой компилятор, всё там векторизуется и вроде ничего не мешает.
Вот примерно как выглядет минимальный пример, где компилятору надо помогать с restrict:
void saxpy(float* x, float* y, float a, int n) {
for (int i = 0; i < n; i++) {
y[i] = a * x[i] + y[i];
}
}
void saxpy_r(float* __restrict x,
float* __restrict y,
float a, int n)
{
for (int i = 0; i < n; i++) {
y[i] = a * x[i] + y[i];
}
}Тогда да.
Было:

Стало с __restrict:

И, кстати, godbolt показывает это дело вот так:

Так что заметно проще и надёжнее анализировать ассемблерный листинг на местах, а не возиться с выставлением компилятора и всех опций в онлайн годболте.

Что именно делал компилятор: как ассемблер помогает разобраться в производительности кода на C++