目前我正在对两个 32 位微控制器进行性能比较。我使用 Dhrystone 基准在两个微控制器上运行。一个微控制器有 4KB I-cache,而第二个控制器有 8KB I-cache。两个微控制器都使用相同的工具链。我尽可能在两个微控制器上保持相同的静态和运行时设置。但是具有 4KB 缓存的微控制器比 8KB 缓存的微控制器快。两个微控制器都来自同一供应商并基于相同的 CPU。
谁能提供一些信息,为什么具有 4KB 缓存的微控制器比其他微控制器更快?
目前我正在对两个 32 位微控制器进行性能比较。我使用 Dhrystone 基准在两个微控制器上运行。一个微控制器有 4KB I-cache,而第二个控制器有 8KB I-cache。两个微控制器都使用相同的工具链。我尽可能在两个微控制器上保持相同的静态和运行时设置。但是具有 4KB 缓存的微控制器比 8KB 缓存的微控制器快。两个微控制器都来自同一供应商并基于相同的 CPU。
谁能提供一些信息,为什么具有 4KB 缓存的微控制器比其他微控制器更快?
基准通常是无用的。dhrystone 是最古老的之一,在当时,在管道和过多的编译器优化之前,它可能有一点价值。我想我大约在 15 年前开始使用 dhrystone 的时候就放弃了。
证明这段代码很简单
.globl ASMDELAY
ASMDELAY:
sub r0,r0,#1
bne ASMDELAY
bx lr
如果您了解现代处理器的工作原理,这主要是两条指令,它们在同一芯片上的执行时间可能会有很大差异。看到这一点的简单技巧是关闭缓存和预取器等,并将此代码放置在偏移量 0x0000 处,并使用一些值调用它。将它放在 0x0004 重复,然后在 0x0008,重复。继续这样做。您可以在减法和分支之间放置一、二等 nop。在各种偏移处尝试。
然后,为这些对齐中的每一个打开和关闭缓存,如果您在处理器外部有一个用于闪存的预取,请打开和关闭它。
然后,改变你的时钟,尤其是那些你必须根据时钟速率调整等待状态的 MCU。
在单个 MCU 上,您将看到这两条本质上的指令在执行时间上的差异非常大。让我们说在某些情况下比其他情况长 20 倍。
现在采取一个小程序或 dhrystone 程序的一小部分。为你的单片机编译它,你看到多少条指令?在编译命令行上进行从小到大的优化和其他变化,代码改变了多少。如果两条指令可以在执行时间上调用它 20 次,那么 200 条指令或 2000 条指令会有多糟糕?它会变得非常糟糕。
如果您使用您现在拥有的编译器选项来使用您现在拥有的 dhrystone 程序,请进入您的引导程序,添加一个 nop(导致整个二进制文件在闪存中移动一条指令)再次运行。加二、三、四。您仍然没有比较不同的 mcus,只是在一个系统上运行您的基准测试。
使用和不使用 d 缓存运行,使用和不使用 i 缓存(如果您拥有其中的每一个)。如果您有闪存预取,则打开和关闭闪存预取,并且如果您有写缓冲区,则可以打开并尝试一下。仍然保留在相同的编译器相同的选项相同的 mcu。
取dhrystone源代码中的不同功能,在源代码中重新排列。而不是 Proc_1、Proc_2、Proc_3 使其成为 Proc_1、Proc_3、Proc_2。再次执行以上所有操作。重新排列,重复。
在离开这个 mcu 之前,您现在应该看到完全未修改的相同源代码的执行时间(除了可能重新排列的函数)可以并且将会有很大不同的执行时间。
如果您然后开始更改编译器选项或保持相同的源代码并更改编译器,您将看到执行时间的更大差异。
今天或很久以前的 dhrystone 基准测试怎么可能对每个平台都有一个结果?很简单,这个结果只是一个广泛的范围之一,并不能真正代表平台。
因此,如果您尝试比较两个不同的硬件平台,无论是来自同一供应商或不同供应商的不同 mcu 内的相同 arm 核心。arm核心,假设(这不是一个安全的假设)它是具有相同编译/构建选项的相同源,即使假设使用了相同的verilog编译和综合。您可以根据 arm 提供的选项进行相同的核心更改。无论如何,供应商是在两个实例中是同一个供应商,还是两个不同的供应商包装同一个核心,你会看到变化。然后采用一个完全不同的核心,无论是另一个 arm 还是 mips 等。与使用这样的程序在每个平台上本身差异很大的程序相比,您如何获得任何价值?
你不能。您可以做的是使用基准测试来产生一种错觉,即一种东西比另一种更好,一台计算机比另一台计算机快,一种编译器比另一种更快。为了销售计算机或编译器。Sprint 的覆盖率在 Verizon 的百分之一之内……这能告诉我们任何有用的信息吗?没有。
如果您要从等式中消除编译器,并且如果它们确实是相同的“CPU”,来自 ARM 的相同版本的源代码,以相同的方式构建,那么它们应该获取相同的内容,但缓存的大小是因此它可能已经是不同的 cpu 实现,因为缓存的宽度或深度会影响事物。在软件中,它就像需要一个 32 位指针而不是 16 位指针(17 位而不是 16 位,但在逻辑上通常不能有 17 位)。
无论如何,如果您为两个平台共同的地址空间编译一次被测代码,则为该空间完全使用相同的二进制文件,可以根据需要附加不同的引导代码,注意 C 库调用 strcpy 等也必须在平台之间的相同空间中保持相同,以消除编译器和对齐方式的困扰。这可能会或可能不会公平竞争环境。
如果您想相信这些是相同的 cpu,则关闭缓存,通过执行上述操作消除编译器变化。看看他们是否执行相同的操作。将程序复制到内存并在内存中运行,消除闪存问题。我假设您在闪存中以相同的等待状态使它们的时钟相同?
如果它们是相同的 cpu 并且芯片供应商使用这两个芯片使内存系统占用相同数量的时钟,例如用于 ram 访问,并且它实际上是相同的 cpu,您应该能够通过消除优化来获得相同的时间(缓存、闪存预取、对齐)。
您可能看到的是与编译器与缓存行的代码在内存中的对齐方式的某种形式,或者它可能更简单,它可能只是缓存中的差异、命中和未命中的工作方式以及 4KB只是比这个特定程序的 8KB 更幸运,以某种方式编译,以某种方式在内存中对齐,等等。
通过上面简单的两条指令循环,很容易看出同一系统上性能变化的一些原因,如果您的现代 cpu 一次获取 8 条指令并且您的循环太接近该提取的尾端,则预取可能认为它需要再获取 8 个超出这些时钟周期的成本。当然,当您使用这两条指令准确地跨越两条“获取线”时,即使使用缓存,每个循环也会花费您更多的周期。当这两条指令接近高速缓存行时(因为您在每次测试中改变它们的对齐方式)也会发生同样的问题,最终它需要两次高速缓存行读取而不是一次来获取这两条指令。至少第一次有额外的缓存行读取。
Michael Abrash,汇编语言之禅。您可以从本书的 github 构建一个 epub/etc。当这本书出版时,8088 已经过时了,如果你只看到 8088 的东西,那么你就完全没有抓住重点。它适用于当今最现代的处理器,如何查看问题、如何测试、如何计时、如何解释结果。到目前为止我提到的所有东西,以及我所知道的所有我没有提到的东西,都来自我几十年来一直在做这件事的书本知识。
因此,如果您真的消除了编译器、对齐、cpu、与该 cpu 相关的内存系统等,那么它只是缓存的大小会有所不同。然后它可能与缓存行如何根据代码相对于两个缓存的缓存行的对齐方式不同地获取命中和未命中有关。对于这个特定的二进制文件,一个是击中更多而丢失更少和/或驱逐更好。您可以尝试重新排列函数,可以添加 nops,或者如果您无法进入引导程序,则在二进制文件的较低地址添加整个函数或更多代码(另一个 printf 等),导致链接器将被测代码滑动到不同的地址会改变程序与缓存线的排列方式。
如果您调整对齐和/或根据重新排列的功能重新排列二进制文件,您绝对应该看到两个平台上的执行时间差异。
底线基准并没有真正告诉你太多,结果对他们来说更多的是负面的恶臭,而不是正面的喜悦。如果不重新编写特定的基准测试或应用程序,可能只会在一个平台上做得更好(无论是一切都相同,但缓存的大小还是两个完全不同的架构),即使您尝试因对齐而改变结果,打开并关闭预取、写入缓冲、分支预测等。抽出所有你能想出的技巧,其中一个可能从 x 到 y 不等,另一个从 n 到 m 变化,并且范围内可能存在一些重叠。对于您所说的这两个平台,除了缓存大小之外,我希望您能找到一种组合,有时 A 比 B 快,并且至少有一个组合 B 比 A 快,并且两者都打开和关闭相同的功能在那个比较中。如果/当您切换到其他应用程序/基准测试时,这一切都重新开始,没有理由假设 dhrystone 结果预测任何其他正在测试的代码。唯一重要的程序,尤其是 MCU 上的程序是应用程序的最终构建。请记住,更改一行代码,甚至在引导程序中添加一个 nop 都会对性能产生显着影响,有时会比单个 nop 慢几倍或快几倍。