Лабораторная работа
В данном случае порождается гораздо больше кода, чем при уровне оптимизаций -O1.
Сначала проверяется случай n == 0 (строка 3), если это так, регистр, через который происходит возврат результата, инициализируется нулём и происходит возврат из функции dot_product (строки 15-16).
Затем проверяется случай, когда n >= 8 (строки 5 и 7), если это не так (n < 8), происходит переход к строкам 57-117. Эти строки содержат 7 блоков, в каждом происходит вычисление r += a[i] * b[i] и проверяется, нужно ли закончить и вернуть результат.
Если же n >= 8, то вычисления производятся блоками по 8 операций (строки 25-51):
r += a[i + 0] * b[i + 0]; r += a[i + 1] * b[i + 1]; ... r += a[i + 7] * b[i + 7];
То есть компилятор произвёл "раскрутку цикла". Важно отметить, что в таком случае проверять, нужно ли остановиться, достаточно один раз на весь блок (строка 52), а не на каждую операцию r += a[i] * b[i] . Это положительно сказывается на производительности, так как условные переходы-"дорогая" операция. В момент, когда осталось выполнить меньше 8 операций, проверяется, выполнены ли все вычисления (строки 54-55), если да, происходит возврат из функции dot_product (строка 13), иначе-происходит переход к случаю, когда надо вычислить менее 8 операций (строки 57-117).
Поскольку блоки вычислений в строках 25-51 однотипны и данные лежат в памяти упорядоченно, вычисления могут быть векторизованы. Однако на момент создания этой лабораторной работы версия компилятора в Syntacore Kit не векторизует вычисления с числами с плавающей запятой.
Немного изменим пример, чтобы продемонстрировать, как компилятор оптимизирует код с помощью векторных инструкций.
Измените следующие строки в main.c:
- 10-typedef int cell_t;
- 43-printf("%i\n%.3g\n"
Чтобы получить векторные инструкции, необходимо указать векторное расширение в архитектуре, передав опцию -march=rv64gcv. Таким образом, получаем следующую команду:
clang -target riscv64-unknown-linux-gnu --sysroot="$GCC_ROOT/sysroot" --gcc-toolchain="$GCC_ROOT" -S -o main-O3v.s main.c -march=rv64gcv -O3
с опциями
и
dot_product: # @dot_product # %bb.0: beqz a2, .LBB0_3 # %bb.1: csrr a3, vlenb srli t0, a3, 1 bgeu a2, t0, .LBB0_4 # %bb.2: li a7, 0 li a3, 0 j .LBB0_7 .LBB0_3: li a0, 0 ret .LBB0_4: addi a4, t0, -1 slli t1, a3, 1 and a6, a2, a4 add t2, a0, a3 add t3, a1, a3 vsetvli a3, zero, e32, m1, ta, ma sub a7, a2, a6 li a5, 0 vmv.v.i v8, 0 mv a3, a7 vmv.v.i v9, 0 .LBB0_5: # =>This Inner Loop Header: Depth=1 add a4, a0, a5 add t4, t2, a5 vl1re32.v v10, (a4) add a4, a1, a5 vl1re32.v v11, (a4) add a4, t3, a5 vl1re32.v v12, (t4) vl1re32.v v13, (a4) sub a3, a3, t0 add a5, a5, t1 vmacc.vv v8, v11, v10 vmacc.vv v9, v13, v12 bnez a3, .LBB0_5 # %bb.6: vadd.vv v8, v9, v8 vmv.s.x v9, zero vredsum.vs v8, v8, v9 vmv.x.s a3, v8 beqz a6, .LBB0_9 .LBB0_7: slli a4, a7, 2 sub a2, a2, a7 add a1, a1, a4 add a0, a0, a4 .LBB0_8: # =>This Inner Loop Header: Depth=1 lw a4, 0(a0) addi a2, a2, -1 lw a5, 0(a1) addi a1, a1, 4 addi a0, a0, 4 mulw a4, a5, a4 addw a3, a3, a4 bnez a2, .LBB0_8 .LBB0_9: mv a0, a3 ret
В данном ассемблерном коде инструкции и регистры, начинающиеся с буквы v относятся к векторному расширению (V) RISC?V.
Сначала на основании соотношения длины векторных регистров в байтах и величины n принимается решение об использовании векторов (строки 5-7).
Если векторы не используются, то вычисления производятся с помощью обычного цикла (строки 53-60).
Если надо использовать векторы, то в 21 строке выставляется максимальная длина для используемых векторов, а в строках 24 и 26 инициализируются векторные регистры, в которых будет аккумулироваться вычисляемые значения. Далее в цикле происходят вычисления с использованием векторов (строки 28-40). После окончания цикла накопленные в векторных регистрах результаты складываются и записываются в обычный регистр (строки 42-45). Если ещё остались необработанные элементы исходных массивов, то они обрабатываются обычным циклом (строки 48-60).
Попробуйте скомпилировать программу теми же опциями (-O3 и -march=rv64gcv), используя систему компиляции GCC.
с опциями
и
dot_product: beq a2,zero,.L4 slli a2,a2,2 mv a5,a0 add a2,a0,a2 li a0,0 .L3: lw a3,0(a5) lw a4,0(a1) addi a5,a5,4 addi a1,a1,4 mulw a4,a4,a3 addw a0,a4,a0 bne a2,a5,.L3 ret .L4: li a0,0 ret
GCC не породил векторизованный код. Различные системы компиляции имеют неодинаковую степень поддержки различных расширений RISC-V. Обратите на это внимание при выборе системы компиляции для своих проектов.