5

现代硬件算法[2.3]: 循环与条件

 11 months ago
source link: https://no5-aaron-wu.github.io/2023/05/16/HPC-2-3-AMH-LoopsAndConditionals/
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

旭穹の陋室

现代硬件算法[2.3]: 循环与条件

发表于2023-05-16|更新于2023-05-24|高性能计算
阅读量:2

循环与条件

让我们考虑一个稍微复杂一点的例子:

loop:
add edx, DWORD PTR [rax]
add rax, 4
cmp rax, rcx
jne loop

它会对32位整数数组进行求和,就像简单的for循环一样。

循环体是add edx, DWORD PTR [rax]:该指令根据迭代器rax加载数据,并将其累加到累加器edx。接下来,我们通过add rax, 4将迭代器向前移动4个字节。然后,一件稍微复杂一些的事情发生了。

汇编语言没有高级语言所具有的if-s、for-s、函数或其他流控制结构。它所拥有的是goto,在低级编程世界中,它的另一个名字跳转(Jump)更为出名。

跳转将指令指针移动到由其操作数指定的位置。这个位置可以是内存中的绝对地址,相对于当前地址的相对地址,甚至可以是在运行时计算的地址。为了避免直接管理这些地址的麻烦,你可以用后面跟着:的字符串来标记任何指令,然后将此字符串用作标签,当转换为机器代码时,该标签将被该指令的相对地址所取代。

标签可以是任何字符串,但编译器没有创造性,在为标签选择名称时,通常只使用源代码中的行号和函数名。

无条件跳转jmp只能用于实现while(true)类型的循环或将程序的几个部分拼在一起。使用一系列条件跳转来实现实际的流控制。

在软件角度看,条件跳转的工作方式是:条件比较在某个地方被计算为bool值,并作为操作数传递给条件跳转。但在硬件中并不是这样实现的,条件运算会使用一个特殊的FLAGS寄存器,该寄存器首先需要通过执行某种检查的指令来填充。

在我们的示例中,cmp rax, rcx将迭代器rax与数组结束指针rcx进行比较。这会更新FLAGS寄存器,然后该寄存器可以被用于jne loopjne指令在该寄存器查找一个特定的位,该位表示这两个值是否相等,基于此位,决定是跳回到开始,还是继续到下一条指令,从而跳出循环。

你可能已经注意到了,上面循环的例子中,处理单个元素会有很多额外开销。在每个周期中,只执行了一条有用的指令,其他3条指令都在递增迭代器,并判断循环终止条件。

我们可以做的是通过将迭代操作分组来展开循环——相当于用C语言写这样的东西:

for (int i = 0; i < n; i += 4) {
s += a[i];
s += a[i + 1];
s += a[i + 2];
s += a[i + 3];
}

在汇编中,它看起来是这样的:

loop:
add edx, [rax]
add edx, [rax+4]
add edx, [rax+8]
add edx, [rax+12]
add rax, 16
cmp rax, rsi
jne loop

现在我们只需要为每4条有效指令提供3条循环控制指令(从1/4到4/7的效率提升),并且循环展开可以一直做,直到额外开销几乎减少为零。

在实践中,展开循环并不总是能提高效率,因为现代处理器实际上并不是一个接一个地执行指令,而是维护一个挂起指令的队列,这样两个独立的操作就可以同时执行,而无需等待彼此完成。

我们的例子也是如此:展开后的真正加速不会是四倍,因为递增计数器和循环结束检查的操作独立于循环体,可以调度成与循环体并行运行。但在某些情况下,要求编译器做循环展开可能仍然是有益的。

另一种方法

不必显式地使用cmp或类似的指令来进行条件跳转。许多其他指令也会读取或修改FLAGS寄存器,有时使能可选的异常检查也会修改FLAGS寄存器。

例如,add会设置多个标志,表示结果是否为零、是否为负、是否发生上溢或下溢等。利用这种机制,编译器通常会生成这样的循环汇编代码:

    mov  rax, -100  ; replace 100 with the array size
loop:
add edx, DWORD PTR [rax + 100 + rcx]
add rax, 4
jnz loop ; checks if the result is zero

这段代码对一般人来说可读性稍差,但它在循环部分减少了一条指令,这可能有助于提升性能。


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK