Индексы легче векторизовать, чем указатели?

Есть ли какой-нибудь пример (например, на https://godbolt.org/), где CLang генерирует худший код, когда алгоритм выражается итерациями указателя вместо индексов массива? Например, в одном случае он может векторизоваться / разворачиваться, а в другом - нет?

В простых примерах это не имеет значения. Вот стиль итерации указателя:

while (len-- > 0) {
  *dst++ = *src++;
}

Вот логически тот же код в стиле индекса:

while (idx != len) {
  dst[idx] = src[idx];
  idx++;
}

Не обращайте внимания на любые ошибки UB и / или off by one здесь.

Изменить: аргумент о том, что индексы являются сахаром, не имеет значения, поскольку удаление серы не меняет стиль алгоритма. Таким образом, следующий код, основанный на указателе, все еще находится в стиле индекса:

while (idx != len) {
  *(dst + idx) = *(src + idx);
  idx++;
}

Обратите внимание, что цикл на основе индекса имеет только 1 изменяющуюся переменную, в то время как цикл на основе указателя имеет 2, и компилятор должен сделать вывод, что они всегда изменяются вместе.

Вы должны смотреть на это в контексте https://en.wikipedia.org/wiki/Induction_variable и https://en.wikipedia.org/wiki/Strength_reduction. Стиль указателя по сути является индексным стилем с уменьшенной прочностью, поскольку добавление заменяется приращениями. И это снижение было полезно для производительности в течение некоторого времени, но не больше.

Итак, мой вопрос сводится к тому, есть ли ситуации, когда это снижение силы не может быть выполнено или отменено компилятором.

Другой возможный случай - это когда индексы не являются индукционными переменными. Таким образом, соответствующий код указателя включает "произвольные переходы", и как-то сложнее преобразовать цикл из-за "истории" прошлых итераций.

1 ответ

Пока не перегружен operator [], выражение нижнего индекса буквально определяется как идентичное арифметике указателя, за которым следует разыменование результата [expr.sub] / 1. Таким образом, до тех пор, пока обе версии действительно эквивалентны, компиляторы, как правило, должны иметь возможность оптимизировать обе версии одинаково хорошо (я бы, вероятно, зашел так далеко, что считаю отказ компилятора оптимизировать одну, но не другую ошибку производительности). При этом обратите внимание, что существует множество тонкостей, таких как циклическое поведение беззнаковой арифметики, которое может сделать итерацию по индексу не совсем эквивалентной итерации по указателю...

Другие вопросы по тегам