笑,滚动过去,然后我开始怀疑:“这种晦涩的操作码比优化的加载更快的机会是什么?”我意识到我只是被书呆子狙击了。因此,我决定总结一下关于如何对操作码进行基准测试的记忆,并深入研究此模因。
对于那些不记得英特尔梦dream以求的每个操作码的人,英特尔参考手册是一个有用的参考,并指出以下内容:
XLAT / XLATB-使用AL寄存器的内容作为表索引在内存中的表中找到字节条目,然后将表条目的内容复制回AL寄存器。 AL寄存器中的索引被视为无符号整数。 XLAT和XLATB指令从DS:EBX或DS:BX寄存器获取内存中表的基地址
我的猜测是等效的负载操作码版本将胜过xlatb。如果只是作为端到端微观基准测试的参考,那么我如何测试这个简单的问题就值得写。
如果您熟悉基准测试,CISC与RISC,以及为什么我猜到我猜到的原因,请随时跳过
我为检验自己的猜测所做的工作称为微基准测试。与常规基准测试的区别在于上下文的重点和最小化。它有助于回答非常小的问题,例如“用C对数字数组进行排序的最快方法是什么”,并且可以忽略内存压力,设计以及在基准测试现实世界中重要的其他考虑因素。
微型基准测试的挑战在于过程中存在很多噪音。编译器,操作系统,CPU温度和频率变化都可以使基准代码段的性能发生较大变化。
尽量减少硬件考虑。这意味着消除频率缩放,分支,从缓存中清除数据的任何可能性。
与系统无交互。这意味着没有系统调用,也没有内存分配,并且有望删除运行时所扮演的角色(例如,如果我们对.NET代码进行基准测试)
这些通常会导致微型基准测试以汇编代码编写并使用rdtsc操作码来测量运行时。
为了从基本的角度理解这一点,我确实推荐了一本书《机器内部》(乔恩·斯托克斯),另一种选择是在90分钟内使用现代微处理器,但除此之外,相对最新的参考是有关CPU体系结构的Real World Technologies文章。
Agners指南和uops.info几乎是与Intel和AMD CPU的操作码延迟有关的所有内容的参考。
如果要测量这些延迟以及执行操作码时CPU到底在做什么,则需要读取硬件中嵌入的性能测量计数器。这为我们提供了准确的数字,例如(例如)代码块中发生了多少次L1缓存命中和未命中,发生了多少分支错误预测等。
XLAT指令背后的基本思想是复杂指令集计算机(CISC)背后的思想,该思想指出,硬件应该使用共同的指令来实现通用指令序列,这些指令执行相同的任务但速度更快。这是许多奇怪的x86指令(或其他CPU)背后的想法。
但是,从那时起,所有现代CPU都通过内部使用精简指令集并将ISA操作码转换为内部命令(其中一些使用CPU微代码实现)而变得类似于RISC。这也允许进行不同的优化,例如微操作码融合。另一个要注意的是,CPU设计人员花费大量时间来优化常用指令,这使它们变得非常快(例如xor rax,rax是免费的),这几乎没有空间让xlatb等操作代码发光。
当我们灌输CISC时,我遇到了以下有关CISC好的部分和坏的部分的区别的摘要。
我们应该将CISC CPU的“复杂”指令(复杂,很少使用且普遍性能低下)与CISC和RISC CPU通用的“功能”指令区分开来,因为这些指令结合了少量的操作序列,并且使用率很高。性能。 -白炽灯
我最终要做的是在GitHub上的Andreas Abel的微型基准测试工具中运行不同的代码段。用于执行的机器是一台运行在Broadwell i7-5500U,Debian 9.9,内核版本4.9上的VM,其主机CPU频率调节器设置为禁用(始终保持最大时钟速度)。
为了尽可能消除差异,最好确保所有访问都保留在L1缓存(代码和数据)中。因此,需要以下初始化
将RBX对准RSP,确保所访问的阵列位于L1数据缓存内
此外,该操作码序列足够小,可以位于同一L1代码高速缓存行中,因此在那里无需任何操作(手动验证)。
对于此实验,重要的是执行所需的周期数(这是时间的近似值)和经过的周期(墙时间)。此外,我还将通过跟踪MEM_LOAD_UOPS_RETIRED.L1_HIT PMC来衡量执行此代码段时发生了多少负载。
其次,众所周知,内存负载是所有算法中最昂贵的部分,但是一旦数据存储在L1中,负载数量的差异似乎并不重要。 xlatb序列只有一个数据加载,但是比优化的x64版本慢。
其次,xlatb指令的核心周期计数与Agner表精确匹配(第229页)。很高兴知道我们的测量与其他产品一致:)
最后,请注意减少指令的使用量并不能线性减少所需的时间。为什么?指令的数量与处理器执行的动作没有线性关系。一个典型的例子是xor rcx,rcx。此操作码从不实际执行。相反,CPU只是更新寄存器重命名文件中的指针。
要检查这些类型的问题,需要使用另一种工具-英特尔架构代码分析器(IACA-现在已弃用)。该工具将查看机器代码,并告诉您如何在不同的Intel CPU中执行它们。让我们具体研究一种情况,以了解为什么删除说明并不能总是线性地节省时间。仅附带输出的相关部分,即微操作数。
重要的一点是,在代码段0中,操作码xor rcx,rcx; mov cl,al占用一个执行单元(xor rcx rcx未绑定到执行端口)。
在代码段1中,操作码movzx rcx,al根本不占用执行端口。
您也可以使用编译器资源管理器(代码段链接)来玩llvm-mca,它会执行类似的操作,并看到相同的结果。
我所有的机器都是Windows机器,访问PMC并不像在Linux上那样有趣或容易。正确的反应应该是“在虚拟机中运行”,但是显然,Hyper-V默认情况下不会将PMC暴露给来宾,而且我找不到轻松实现此目的的方法。
在寻找便宜的裸机云设备时,我遇到了各种可能的挫败感,包括Oracle在要求人看的请求表后面设置裸机实例。
最后,VMWare工作站将PMC暴露给来宾,因此有能力的朋友给了我一台VM进行测试。
该模因是错误的,不足为奇。优化的x64指令序列击败了一条复杂的指令。这并不总是正确的(以POPCNT为例),但是出于某些原因,有些说明是遗留的。如果LLVM不输出操作码,则可能是最好的选择。
这也是重新发现微基准测试的机会。我很可能在测量方面犯了一些错误,因此,如果发现任何错误,请随时与我联系。
@damageboy通常了解所有情况,在这种情况下,是因为撰写了有关微基准测试.NET的一系列博客文章(这里提供了相关说明),促使我去做这种事情
再次感谢Andreas Abel使用GitHub上的微基准测试工具为我节省了很多时间