TGViewer
Грокаем C++ Грокаем C++ @grokaemcpp · 9.36K subscribers
Post #1076 3.63K
​​Loop unrolling. Быстрая обработка хвостов
#новичкам

В прошлый раз мы пришли к важной мысли: нам нужно обрабатывать хвосты развернутых циклов и делать это быстро.

В С++ коде пока непонятно, как это сделать. Но в ассемблере есть довольно старая техника для этого.

Возьмем С++ код:

void output_squares(size_t count, size_t *output) {
for (size_t i = 0; i < count; ++i) {
*output++ = i * i;
}
}


В целом, мы хотим, чтобы хвосты обрабатывались примерно так:

#include <cstddef>

void output_squares(size_t count, size_t *output) {
size_t i = 0;
size_t remainder = count % 4;

// Эта часть назвается пролог
if (remainder == 3) {
output[2] = 2 * 2;
output[1] = 1 * 1;
output[0] = 0 * 0;
i = 3;
} else if (remainder == 2) {
output[1] = 1 * 1;
output[0] = 0 * 0;
i = 2;
} else if (remainder == 1) {
output[0] = 0 * 0;
i = 1;
}

for (; i < count; i += 4) {
output[i] = i * i;
output[i + 1] = (i + 1) * (i + 1);
output[i + 2] = (i + 2) * (i + 2);
output[i + 3] = (i + 3) * (i + 3);
}
}


Только без кучи повторяющегося кода, количество которого будет только увеличиваться с увеличением фактора разворачивания цикла.

Идея: на месте пролога напишем 4 инструкции присваивания, а между ними поставим метки. В начале мы узнаем остаток и прыгаем на нужную метку. После чего раз за разом проваливаемся в следующую метку и в итоге в развернутый цикл. То есть в начале обрабатываем хвост правильного размера с помощью меток, а потом уже попадаем в основной цикл. В переводе на С++ это выглядит так:

void output_squares(size_t count, size_t *output) {
size_t i = 0;
size_t r = count % 4;

switch (r) {
case 3: goto rest3;
case 2: goto rest2;
case 1: goto rest1;
default: goto main_loop;
}

rest3:
output[i] = i * i; ++i;
rest2:
output[i] = i * i; ++i;
rest1:
output[i] = i * i; ++i;

main_loop:
for (; i < count; i += 4) {
output[i] = i * i;
output[i + 1] = (i + 1) * (i + 1);
output[i + 2] = (i + 2) * (i + 2);
output[i + 3] = (i + 3) * (i + 3);
}
}


Выглядит адски: свитч, какие-то метки, на которых одинаковый код, goto. За такое сразу бы запретили человеку коммитить код, только документацию писать отныне и навсегда. Поэтому никто так не пишет.

Зато эту лапшу можно спрятать в скомпилированном ассемблере, который вряд ли кто-то будет читать. Компилятор превращает исходный С++ код из начала поста в ассемблер примерно такого же вида, как последний сниппет. Вот примерчик на годболте как это выглядит в оригинале.

Hide your impurities deep. Stay cool.

#performance #compiler
  • 🔥 15
  • ❤ 10
  • 👍 8
  • 😁 2
  • 🤯 1
More from @grokaemcpp
  1. Oct 8, 2026​​Strict weak ordering #опытным На первый взгляд, всё выглядит рабочим: мы создаём 40 зака…
  2. Oct 7, 2026​​Где-то баг... #опытным Вот вам код: struct Order { int price; int id; }; int main() { st…
  3. Oct 5, 2026Откуда spurious wakeup на кондваре? #опытным У кондваров есть метод std::condition_variabl…
  4. Oct 1, 2026​​Stacktrace. Tips #опытным Чтобы полноценно работать со стандартными трейсами, нужно знат…
  5. Sep 28, 2026​​Stacktrace #опытным Одна из проблема исключений - непонятно, откуда оно прилетело. Ну да…
  6. Sep 25, 2026​​std::spanstream #опытным Радостная весть для всех, кто пользуется iostreams! В C++23 доб…
Threads Profile ViewerView any public Threads profile without an account.Open ThreadLook →Writing with AI? Make it sound human.Metric37 rewrites AI drafts so they read naturally. Free AI detector, 1,500 words free.Try Metric37 →