最近在www.example.com上发表了一篇文章nature.com,Faster sorting algorithms discovered using deep reinforcement learning,其中谈到AlphaDev发现了一种更快的排序算法。这引起了我的兴趣,我一直试图理解这个发现。
关于这个主题的其他文章是:
- AlphaDev discovers faster sorting algorithms
- Understanding DeepMind's AlphaDev Breakthrough in Optimizing Sorting Algorithms
下面是原始sort3算法的伪代码,与AlphaDev发现的改进算法相比。
原始伪码
Memory [0] = A
Memory [1] = B
Memory [2] = C
mov Memory[0] P // P = A
mov Memory[1] Q // Q = B
mov Memory[2] R // R = C
mov R S
cmp P R
cmovg P R // R = max(A, C)
cmovl P S // S = min(A, C)
mov S P // P = min(A, C)
cmp S Q
cmovg Q P // P = min(A, B, C)
cmovg S Q // Q = max(min(A, C), B)
mov P Memory[0] // = min(A, B, C)
mov Q Memory[1] // = max(min(A, C), B)
mov R Memory[2] // = max(A, C)
AlphaDev伪码
Memory [0] = A
Memory [1] = B
Memory [2] = C
mov Memory[0] P // P = A
mov Memory[1] Q // Q = B
mov Memory[2] R // R = C
mov R S
cmp P R
cmovg P R // R = max(A, C)
cmovl P S // S = min(A, C)
cmp S Q
cmovg Q P // P = min(A, B)
cmovg S Q // Q = max(min(A, C), B)
mov P Memory[0] // = min(A, B)
mov Q Memory[1] // = max(min(A, C), B)
mov R Memory[2] // = max(A, C)
改进集中在省略了单个移动命令mov S P
。为了帮助理解,我编写了以下汇编代码。然而,我的测试表明,排序算法在 A=3,B=2和C=1时不工作,但在 A=3,B=1和C=2 时工作。
这是在Ubuntu 20.04 Desktop上编写、编译和运行的。
$ lsb_release -a
No LSB modules are available.
Distributor ID: Ubuntu
Description: Ubuntu 20.04.6 LTS
Release: 20.04
Codename: focal
$ nasm -v
NASM version 2.14.02
$ ld -v
GNU ld (GNU Binutils for Ubuntu) 2.34
我的汇编代码测试...
; -----------------------------------------------------------------
;
; sort_test.asm
;
; Test for AlphaDev sorting algorithm
;
; My findings are that AlphaDev's removal of 'mov S P' doesn't work when:
; a = 3, b = 2, c = 1
; But it does work with:
; a = 3, b = 1, c = 2
;
; Output: The sorted values of a, b & c printed to stdout with spaces
;
; Compile & run with:
;
; nasm -f elf32 sort_test.asm && ld -m elf_i386 sort_test.o -o sort_test && ./sort_test
;
; -----------------------------------------------------------------
global _start
section .data
a equ 3
b equ 2
c equ 1
section .bss
buffer resb 5
section .text
_start:
; ------------------- ; AlphaDev pseudo-code
mov eax, a ; P = A
mov ecx, b ; Q = B
mov edx, c ; R = C
mov ebx, edx ; mov R S
cmp eax, edx ; cmp P R
cmovg edx, eax ; cmovg P R // R = max(A, C)
cmovl ebx, eax ; cmovl P S // S = min(A, C)
; The following line was in original sorting algorithm,
; but AlphaDev determined it wasn't necessary
; mov eax, ebx ; mov S P // p = min(A, C)
cmp ebx, ecx ; cmp S Q
cmovg eax, ecx ; cmovg Q P // P = min(A, B)
cmovg ecx, ebx ; cmovg S Q // Q = max(min(A, C), B)
; add values to buffer with spaces
add eax, 30h
mov [buffer], eax
mov [buffer+1], byte 0x20
add ecx, 30h
mov [buffer+2], ecx
mov [buffer+3], byte 0x20
add edx, 30h
mov [buffer+4], edx
; write buffer to stdout
mov eax, 4 ; sys_write system call
mov ebx, 1 ; stdout file descriptor
mov ecx, buffer ; buffer to write
mov edx, 5 ; number of bytes to write
int 0x80
mov eax, 1 ; sys_exit system call
mov ebx, 0 ; exit status 0
int 0x80
我已经在命令行上运行了这个测试,以打印排序的结果,但是我也使用了gdb
逐行遍历这个可执行文件。在调试过程中,我清楚地看到,“A”(又名 “P”,又名 “eax”)的寄存器在 A=3、B=2和C=1 时从未更新,但在 A=3、B=1和C=2 时更新。
*******************************我也不精通任何其他特定的语言,但我尝试过C,C++,JavaScript,PHP和HTML来完成小项目。基本上,我自学了我所知道的。为了达到编写这个测试的目的,我必须学习相当多的东西。因此,我肯定会犯错误或不理解问题。
无论如何,请帮助我理解为什么我在观察我是什么。
- 我误解了问题吗?
- 我是不是误解了伪代码?
- 将伪代码转换为汇编是不是犯了一个错误?
- 我的汇编代码有错误吗?
- 伪代码是错的吗?
- 还有别的吗
1条答案
按热度按时间goucqfw61#
我会使用AT & T语法(就像用GCC组装的
.s
文件中的cmovg %ecx, %eax
),这样操作数顺序就可以匹配伪代码,目标在左边。你是对的,我看了这篇文章,当
C
是最小的元素时,3元素伪代码不能正确排序。我知道x86 - 64 asm向后可以向前,我不只是指英特尔与。AT & T语法:P即使查看实际代码,而不仅仅是注解,如果最小的元素以R = memory[2] = C
开头,那么它也不可能以memory[0] = P
结尾。我在真正阅读你的问题之前打开了这篇文章,在浏览了这篇文章之后,我自己也注意到了这个问题,直到看到关于实际改进的部分,所以我没有看你试图复制它。但我没有任何偏见,看到一个问题,我只是想了解它自己。没有任何写
P
的指令可以从可能包含起始R
值的值中读取,因此它无法获得该值。这篇文章间接链接了他们发表在《自然》杂志上的论文(Faster sorting algorithms discovered using deep reinforcement learning by Daniel J. Mankowitz等人全文在Nature链接中:)
他们在实际的论文中使用了相同的代码图像,但在3元素排序网络方面有一些解释性文本。
图3a示出了用于三个元件的最佳分选网络(参见分选网络概述的方法)。我们将解释AlphaDev如何改进圈出的网段。在各种规模的排序网络中,可以找到这种结构的许多变体,并且在每种情况下都适用相同的论点。
网络的圆圈部分(最后两个比较器)可以看作是一个指令序列,它接受一个输入序列> A,B,C>,并转换每个输入,如表2a所示(左)。* * 然而,线路B和C上的比较器在此运算符之前,因此保证了B ≤ C的输入序列。**这意味着计算min(A,B)而不是min(A,B,C)作为第一个输出就足够了,如表2a所示(右)。图1和图2之间的伪代码差异。图3b、c演示了AlphaDev交换移动如何在每次应用时保存一条指令。
此外,在3元素排序中,仅仅一条
mov
指令不太可能带来巨大的加速。Can x86's MOV really be "free"? Why can't I reproduce this at all?-它从来不是免费的(它会消耗前端带宽),但它在除Ice Lake以外的大多数最新微架构上都具有零延迟。我猜这不是他们得到了70%的加速!使用像
vpminsd dst, src1, src2
(https://www.felixcloutier.com/x86/pminsd:pminsq)/vpmaxsd
这样的AVX SIMD指令,使用非破坏性的单独目的地执行最小和最大的有符号双字(32位)元素,除了关键路径延迟外,没有任何节省。min(B, prev_result)
仍然只是一条指令,不需要单独的寄存器拷贝(vmovdqa xmm0, xmm1
),就像SSE4.1中那样,如果你正在做一个排序网络。但是当用shuffle和SIMD最小/最大比较器构建排序网络时,延迟可能会很大,我最后一次听说的是在x86 - 64上对大整数或FP数组进行整数排序的最新技术,而不仅仅是在标量cmov
代码中保存mov
!但是,许多程序的编译并没有假设AVX可用,因为不幸的是,它并没有得到普遍支持,在过去几年中,一些低功耗的x86 CPU以及Ice Lake之前的Pentium/Celeron CPU上都缺少AVX(因此,对于低预算的桌面CPU来说,可能是2018年左右)。
他们在Nature上的论文提到了SIMD排序网络,但指出libc ++
std::sort
没有利用它,即使输入是float
或int
的数组,而不是class
es和重载的operator <
。这个3元素调整是一个微优化,而不是“新的排序算法”。在AArch64上,它可能仍然可以节省延迟,但只能节省x86上的指令
人工智能可以找到这些微优化是很好的,但是如果在
min(A,C)
或min(B,C)
之间进行选择,它们就不会那么令人惊讶了,因为后者是B
在这一点上的实际情况。通过仔细选择更高级别的源代码来避免寄存器复制指令是人类可以做的事情,例如。
_mm_movehl_ps
合并目的地(第一个源操作数)的选择在我2016年关于 * Fastest way to do horizontal SSE vector sum (or other reduction) * 的回答中-请参阅编译器生成的asm# note the reuse of shuf, avoiding a movaps
的评论。以前在自动微优化方面的工作包括STOKE,这是一个随机超级优化器,它随机尝试指令序列,希望找到与测试函数输出匹配的廉价序列。搜索空间是如此之大,以至于当它需要超过3或4个指令时,它往往会错过可能的序列(斯托克自己的页面说它还没有生产就绪,只是一个研究原型)。AI是有帮助的。手工查看asm可能遗漏的优化是一项很大的工作,可以通过调整源代码来修复。
但至少对于这个3元素子问题,它只是一个微优化,而不是真正的 * 算法 * 新。它仍然只是一个3比较器排序网络。一个编译更便宜的x86-64,这是很好的。但是在一些3操作数ISA上,它们的等价物
cmov
(如AArch64'scsel dst, src1, src2, flag_condition
conditional-select)具有单独的目的地,没有mov
需要保存。不过,它仍然可以保存关键路径上的延迟。他们在《自然》杂志上的论文还展示了对可变数量的元素进行排序的算法差异,其中>= 3的情况都是从前3个元素开始排序的。也许这有助于分支预测,因为当
len > 3
上的最后一个分支正在解析时,这项工作可能正在进行中,以确定是否需要进行简化的4元素排序,该排序可以假设前3个元素已排序。他们说:“正是这部分程序大大节省了延迟时间。”(他们还称之为“全新”算法,我认为这对于在短的未知长度输入上使用排序网络的问题是正确的。)