如何减少阶乘循环的执行时间和周期数?和/或代码大小?

时间:2019-04-09 18:40:24

标签: assembly arm execution-time micro-optimization cortex-m3

基本上,我很难使执行时间比实际时间要短,并且要减少时钟周期和内存大小。有谁知道我该怎么做吗?代码工作正常,我只想稍作更改。

编写了有效的代码,但又不想弄乱代码,也不知道要进行哪些更改。

; Calculation of a factorial value using a simple loop

; set up the exception addresses
THUMB
AREA RESET, CODE, READONLY
EXPORT  __Vectors
EXPORT Reset_Handler
__Vectors 
DCD 0x00180000     ; top of the stack 
DCD Reset_Handler  ; reset vector - where the program starts

AREA 2a_Code, CODE, READONLY
Reset_Handler
ENTRY
start   
MOV r1,#0    ; count the number of multiplications performed 
MOV r2,#3    ; the final value in the factorial calculation
MOV r3,#1    ; the factorial result will be stored here

; loop r2 times forming the product  
fact
ADD r1,r1,#1  ; find the next multiplicand
MUL r3,r1,r3  ; form the next product - note that MUL r3,r3,r1 gives unpredictable output
CMP r1,r2     ; check if the final value has been reached
BMI fact      ; continue if all products have not been formed

exit    ; stay in an endless loop 
B exit
END

当前结果是: 内存大小:0x00000024 时钟周期:22 总执行时间:1.1微秒

我们正在使用Cortex M3

我只需要减少其中的任何一个,只要代码产生不同的结果,对代码的更改就可以很小。

4 个答案:

答案 0 :(得分:5)

通常,代码大小和性能是一个折衷。展开循环通常有助于提高性能(至少对于大型输入而言),但在循环外部需要额外的逻辑来处理清除操作,等等。


大部分答案是假设使用诸如Cortex-A9或Cortex-A53之类的高性能CPU,其中通过软件流水线创建指令级并行性将有所帮助。 Cortex M3是标量的,具有单周期乘法指令,因此优化起来要简单得多。

(最初的问题没有指定内核,我期望即使是低端CPU也将具有多周期mul延迟。我在编写它后才发现Cortex-M3编号。)

您的代码可能会成为整数乘法延迟的瓶颈。与add不同,mul在下一个周期将准备好结果,而mul则很复杂,需要多个周期才能产生结果。

(除非在某些时钟非常慢的芯片上使用,例如Cortex-M3显然具有1周期1 * 2 * 3指令。但是Cortex-M0/M0+/M23 are available with a choice的指令具有1个周期或32个周期的性能!缓慢的迭代=较小的硅。)


乘法执行单元本身通常是流水线式的,因此可以一次运行多个独立乘法,但是阶乘循环需要将每个乘法结果作为下一次迭代的输入。 (仅适用于高性能内核,而不是Cortex-M系列。慢速Cortex-M芯片上的32周期乘法是迭代的,并且可能没有流水线,因此在运行时无法启动另一个乘法,因此没有任何好处。除了减少循环开销之外,还可以公开任何指令级并行性。

请注意,乘法是关联的:3 * 2 * 1 = n,因此@ensc的答案指出,我们可以从(1*2) * (3*4)开始倒数。或1*2*3*4 = 1 * 2 * ... * (n/2)

我们可以与n/2+1 * n/2+2 * n/2+3 * ... * n并行执行1 * 3 * 5 * ... * n,在这两个依赖项链上进行交织。或者我们可以将2 * 4 * 6 * ... n-1与{{1}进行交织},在执行n -= 2并由此计算出n+1的循环中。 (然后,最后将这两个乘积相乘。)

这显然将需要更多的代码大小,但可以大大提高性能。


当然,查找表是另一个解决方法。如果您只关心不会溢出32位结果的输入,那将是一个很小的表。但这会带来巨大的尺寸成本。


即使在有序CPU(指令执行必须按程序顺序启动)上,长时间运行的指令(如高速缓存未命中负载或乘法)也可能会被允许完成乱序,例如在启动add之后但回写mul结果之前,某些mul指令可能会运行。甚至在更早mul的等待时间的阴影下开始另一个独立的mul指令。

我搜索了一些ARM性能数字,以了解典型的性能。

例如,Cortex-A9是一个较旧的,相当常见的高端ARMv7 CPU,它具有超标量(每个周期多条指令),并且具有乱码执行。

mul "takes" 2 cycles, and has 4 cycle result latency。他们没有解释非延迟成本的含义。也许这就是执行单元的互惠吞吐量,例如您可以多久启动一次新的独立操作。这是一个乱序的CPU,因此将其他指令停顿2个周期没有任何意义。在NEON SIMD instruction section中,他们解释了看起来像相同的“周期”数字:

  

这是特定指令消耗的发布周期数,如果没有操作数互锁,则是每条指令的绝对最小周期数。

(操作数互锁=如果较早的指令尚未产生结果,则等待输入操作数就绪)。

(Cortex-A9确实支持压缩整数乘法,因此对于大型阶乘,您可以考虑使用vmul.32 q1, q1, q2每4个周期从一个向量开始并行进行4次乘法。或者使用64位{每2个周期2个。 {1}}寄存器,但是您需要更多的d指令,并且与乘法不同,vadd与128位vadd.32寄存器一样快,与64位向量一样快。如果您使用足够多的寄存器来隐藏较大的延迟,则SIMD可以为您提供Cortex-A9上标量的两倍的吞吐量,但SIMD可能仅对q有用,以至于n会溢出32位整数,因此结果取模2 ^ 32。)


更低延迟的ARM乘法指令:

mul是32x32 => 32位乘法。在Cortex-A9上,它具有2c的吞吐量和4c的延迟。

({n!是Thumb模式下的16位指令,除非您不需要破坏标志,否则应优先使用。muls仅在ARMv6T2及更高版本中可用。) / p>

smulbb是一个16x16 => 32位有符号乘法,仅读取其输入的下半部分,但在A9上具有 1c吞吐量和3c延迟 >。 (BB =底部,底部。还可以使用其他组合,以及乘累加和各种时髦的东西。)

没有mul的2字节Thumb版本,因此对于代码大小而言,此版本比smulxy更糟。

不幸的是,muls在无符号版本中不可用,因此将我们可以使用的输入范围限制为正数smulxy,而不是int16_t

但是,如果我们只关心最终的32位结果不会溢出的情况,我们可以安排操作顺序,以便最后的乘法具有2个大小相似的输入(均为大的16位数字) 。即尽可能接近uint16_t。所以例如赔率和偶数的乘积是合理的,但是sqrt(n!)将是最坏的情况,因为这需要(n-1)! * n才能容纳16位。实际上,最坏的情况是从(n-1)!开始倒数,因此最后一个是乘以3再乘以2。我们可以将乘以2的特殊情况乘以左移...


将这些部分放在一起,请注意乘以n是无操作的(1除外,它将输入缩减为16位)。因此,我们可以根据输入是奇数还是偶数来展开,直到展开1或2后停止。

因此,我们只知道lo(以smulbb开头)和hi(以n-1开头)才是未知数。

n

((标签名称中的.L使其成为本地标签,至少在GAS语法中没有显示在目标文件中。如果使用的是汇编器,则可能不在ARMASM中。)

ARM汇编使您可以忽略目的地(与第一个来源相同),例如;; UNTESTED, but it does assemble with the GNU assembler, after sed -i 's/;/@/' arm-fact.S ;; and replacing THUMB with ; .thumb ; .syntax unified THUMB ;; Input: n in r0. (n is signed positive, otherwise we return n.) ;; Output: n! in r0. ;; clobbers: r1, r2, r3 ;; pre-conditions: n! < 2^31. Or maybe slightly lower. fact: subs r3, r0, #3 ; r3 = lo = n-3 (first multiplier for loprod) bls .Ltiny_input subs r2, r0, #2 ; r2 = hi = n-2 (first multiplier for hiprod) subs r1, r0, #1 ; r1 = loprod = n-1 ; r0 = hiprod = n .Lloop: ; do { smulbb r0,r0, r2 ; hiprod *= hi subs r2, #2 ; hi -= 2 for next iter smulbb r1,r1, r3 subs r3, #2 ; lo -= 2 for next iter bgt .Lloop ; while((lo-=2) > 0); signed condition ; r3 = 0 or -1, r2 = 1 or 0. The last multiplies were: ; hiprod *= 2 and loprod *= 1 for even n ; or hiprod *= 3 and loprod *= 2 for odd n ; muls r0, r1 smulbb r0,r0, r1 ; return hiprod *= loprod bx lr ; or inline this .Ltiny_input: ; alternate return path for tiny inputs ; r0 = n. flags still set from n - 3 IT eq ; GAS insists on explicit IT for thumb mode moveq r0, #6 ; 3! = 6, else n! = n for smaller n=1 or 2. ; 0! = 1 case is not handled, nor are negative inputs bx lr ,而不是subs。如果需要,您每次都可以像smulbb一样写出来。

您可以将subs r2, r2, #2用于最终产品,因为最终的muls r0, r1hiprod高一点。即使loprod> max int16_t,产品也可能不会溢出。这样也可以节省2个字节的代码大小,但是在Cortex-A9上增加了1个周期的延迟。 (顺便说一句,ARMv6用hiprod来解决“不可预测的结果”,并且您的代码使用32位Thumb2指令,因此无论如何它仅适用于ARMv6T2及更高版本。)


对于产品有2个累加器,在Cortex-A9上,它可能每3个周期以2倍的速度运行,这在很大程度上取决于CPU微体系结构及其前端是否可以保持。在有序ARM上,我担心它能否在乘法完成之前启动其他指令。

最好在mul d,d, src上多花2个字节,而不是在sub上花一些,以便我们可以在分支之前对指令进行计算 ,也许可以减少分支的错误预测惩罚并避免顺序CPU上的停顿。 subs不会触摸标志,因此我们可以先做smulbb并让loprod东西不触摸标志。

hi

请注意,我们在 .loop: ; do { smulbb r1, r3 ; loprod *= lo subs r3, #2 ; lo -= 2 for next iter, and set flags smulbb r0, r2 ; hiprod *= hi sub r2, #2 ; hi -= 2 for next iter (no flags) bgt .loop ; while((lo-=2) >= 0); 读取它们之后立即修改了r3r2,避免为有序芯片上的数据依赖性造成停顿


您正在使用Thumb模式并针对代码大小进行了优化,因此了解哪些形式的指令可以使用2字节/ 16位编码以及哪些仅作为32位Thumb2可用非常重要。编码。

smulbb can be encoded as a 16-bit Thumb instruction for imm=0..7(3位立即数)。或使用与src和目标相同的寄存器,用于imm = 0..255。因此,我的复制和订阅说明紧凑。

无标志设置subs Rd, Rn, #imm不能是16位指令,除非在IT块内部或以sub作为操作数。

拇指模式下的谓词指令(例如SP)要求汇编器使用IT instruction为下一个最多4条指令引入谓词。在ARM模式下,每个指令的前4位表示预测。 (如果您不使用后缀,则汇编程序会将其编码为始终(即不带谓词)。)

我们可以用moveq r0, #6 / n==0处理另外4或6个字节的cmp r0,#0情况。如果我们将tst / mov放在同一IT块中,则可能将其减少到4个字节。 IT不会快照实际的标志情况,而是快照谓词,因此IT块中的标志设置指令可能会对同一块中的后续指令产生影响。 (我认为这是正确的,但我不确定100%)。

moveq r0, #1

或者有16-bit cbnz有条件地跳过tiny_input: ; r0 = n, flags set according to n-3 ITET EQ moveq r0, #6 cmpne r0, #0 moveq r0, #1 。但是分支目标必须在mov r0, #1之后从4到130个字节,因此,显然我们不能仅跳过一条16位指令!


我的版本的代码大小:

cbnz

因此,此功能的大小为0x22字节。 (如果要处理$ arm-none-eabi-gcc -g -c -mcpu=cortex-a9 arm-fact.S $ arm-none-eabi-objdump -drwC arm-fact.o arm-fact.o: file format elf32-littlearm Disassembly of section .text: 00000000 <fact>: 0: 1ec3 subs r3, r0, #3 2: d90b bls.n 1c <.tiny_input> 4: 1e82 subs r2, r0, #2 6: 1e41 subs r1, r0, #1 00000008 <.loop>: 8: fb10 f002 smulbb r0, r0, r2 c: 3a02 subs r2, #2 e: fb11 f103 smulbb r1, r1, r3 12: 3b02 subs r3, #2 14: dcf8 bgt.n 8 <.loop> 16: fb10 f001 smulbb r0, r0, r1 1a: 4770 bx lr 0000001c <.tiny_input>: 1c: bf08 it eq 1e: 2006 moveq r0, #6 20: 4770 bx lr ,则为0x26。)

它比您的版本大(您的字节数包括内存中的一些常量以及用于生成输入的0! = 1指令),但是从理论上讲,对于具有流水线乘法器的CPU,大输入的速度可能要快两倍。 。对于从1到3的输入,也许只需要分支一次就可以产生结果。


您可能没有像Cortex-A9那样的东西,因为1.1微秒= 22个时钟周期意味着20MHz的时钟速度,而Cortex-A9的可用频率为0.8至2GHz。

因此,也许您有一个更简单的有序核心,例如Cortex M3? M3确实支持mov指令和Thumb2模式。维基百科说它的乘法是1个周期!所以这很奇怪,我很惊讶它具有如此高效的乘数。或者只是时钟太慢,以至于在一级中会有很多门延迟,而这只是三级流水线。


Cortex-M3版本:

subs和muls在Cortex-M3上是单周期的。我没有在树枝上找到性能数字,但是它们很常见,所以我假设它可能是1个周期,并且不会引起较大的读取泡沫(如果正确预测……)。 Cortex-M3 HTML手册的Branch target forwarding上有一节似乎是关于减少获取气泡的。

instruction timing table显示mul的费用为1个周期(未使用)或2个周期(使用)。 (1表示分支,1表示在立即移位后重新加载管道。)。因此,与sub / mul相比,采用的分支,展开很有价值,因此上面的代码仍然可以正常工作。 (但是不需要多个产品累加器,因此可以简化。)

优化代码大小:

b<cond>

我认为这是我们可以管理的最小事务。该循环包含3条指令,每次迭代可能花费4个周期(1 + 1 + 2,所采用的分支花费2个周期)。

;; UNTESTED
THUMB

;; Input: n in r0.   (n is signed positive, otherwise we return n.)
;; Output: n! in r0.
;; clobbers: r1
fact:
    subs   r1, r0, #1     ; i = n-1
    bls   .Ltiny_input    ; jump if n<=1

.Lloop:                 ; do {
    muls    r0, r1         ; prod *= i
    subs    r1, #1         ; --i
    bgt     .Lloop      ; while(--i > 0);  signed condition
    ; r1 = 0, r0 = n! 
    ; last multiply was a redundant prod *= 1 but avoiding that would take a cmp
.Ltiny_input:   ; alternate return path for tiny inputs
    ; 0! = 1 case is not handled, nor are negative inputs


    bx lr    ; or inline this

因此,这是0xa = 10个字节,不计算00000000 <fact>: 0: 1e41 subs r1, r0, #1 2: d902 bls.n a <fact+0xa> 4: 4348 muls r0, r1 6: 3901 subs r1, #1 8: dcfc bgt.n 4 <fact+0x4> a: 4770 bx lr # don't count this if inlining 返回指令。

我们可以在分支的第一个bx lr之后的0! = 1处,在分支之前 处理IT,因此我们循环后仍可以跳到右侧(而不是像我的Cortex-A9版本那样跳到单独的块)。您也可以使用此技巧。

subs

如果分支需要更大的范围,则可以使用 subs r1, r0, #1 ; i = n-1 it lt movlt r0, #1 ; n = 1 for n<1 bls .Ltiny_input ; return n if n was <=1 / itt ls,因此分支位于IT块内部(分支指令可以使用在位移和移位上花费更多位的编码)谓词上没有。但是在这种情况下,范围很短,因此在movls r0, #1情况下,我选择不更改r0。我不知道是否有任何CPU可以使谓词指令变为NOP而不是运行来提高效率或降低延迟,但是可能会有。


如果不展开,将r0 == 1放入循环中以避免最后一次cmp迭代将使我们每次迭代花费一个额外的周期(4个周期,而不是3个周期),因此只能使用{{ 1}}或*=1

展开可帮助大幅提高较大输入的速度,从每3个周期1 mul到每2周期渐近1 mul (sub + mul +摊销循环开销)。除了用硬编码每个{{1}的特殊情况序列之外,我看不出有什么方法可以避免像n=2n=3这样的指令为每个sub生成一个单独的输入。 }(例如mov = mul =左移3),而您可以硬编码答案。

答案 1 :(得分:2)

r1r2组合在一起是显而易见的解决方案,当您使用c编译器作弊时,也会得到同样的解决方案...

unsigned int foo(unsigned int a)
{
        unsigned int    res = 1;

        while (a > 0) {
                res *= a;
                --a;
        }

        return res;
}

翻译为

    subs    r3, r0, #0
    mov     r0, #1
    bxeq    lr
1:  mul     r0, r3, r0
    subs    r3, r3, #1
    bne     1b
    bx      lr

答案 2 :(得分:2)

如果为TL; DR,则跳至结尾处。

在STM32蓝色药丸STM32F103C8T6上使用它

即使处理器具有相同的cortex-m3转速,也可以肯定结果会因不同的芯片而改变,这是一回事,但进给的又是另一回馈的,这是特定于厂商的。同样,有时芯片供应商可以以不同的方式编译内核,有时他们可以进行多周期乘法以节省芯片空间,某些内核可以在一次获取16位或32位之间选择。选择基准通常很容易,因此用一粒盐。

我已经看到在sram中执行通常比从flash中执行更快。 ST虽然有时不是,但我不认为在这些古老的cortex-m3上它们的(指令)缓存带有一些奇特的名称。较新的版本可以关闭它。
其他芯片供应商则没有,并且愿意为其支持的内核实现武器缓存,而不是自己(或两者都不拥有)。也许为什么下面的前两个实验在不同的时间运行(前面的两位数字是十六进制,systick计时器计数,systick cvr地址在r0中传递。您可以看到我使用了nop来改变循环的对齐方式。 arm文档没有说明cortex-m3通常会获取半个单词或单词,但是ST文档在谈论其他内容时会说明单词获取。您的四个指令循环为2个单词,但不在单词边界上对齐意味着它需要每个循环取3个字。如果这4个字对齐,则需要每个循环取2个字,这将使Peter或其他人为这个/您的代码计数指令。我确定这是一个因素,但也许还有其他因素,可能不是。

从闪存运行该芯片要快得多。您会看到关闭ST预取和添加等待状态的影响。

000 Zero wait state, if 0 < SYSCLK≤ 24 MHz
001 One wait state, if 24 MHz < SYSCLK ≤ 48 MHz
010 Two wait states, if 48 MHz < SYSCLK ≤ 72 MHz

因此,当我使用内部8mhz时钟时,这里有两个测量值:一个是执行某项操作所需的时钟数,如果我们将sysclk增大三倍至24mhz,则时钟数不应更改。每个sysclk周期的挂钟持续时间是时间的三分之一,因此挂钟时间更快。实时性能更好。遵循这些规则,在24Mhz以上迈出一步,现在您添加了一个等待状态,代码现在又变慢了。由于运行系统时钟的数量现在变慢了。现在,如果您将其倍增至48Mhz,是否可以克服等待状态?可能但是,对于每个程序/循环,在24Mhz + smidge之间会有一个点,而48Mhz在24Mhz的性能上追上了正轨。 48Mhz加上一个小标记,现在您又放慢了速度,在48Mhz加上一个72Mhz的标记之间,我们希望能够赶上并通过48Mhz的性能。

就像闪存不能跟上一样,其他外设也有规定,尤其是像许多基于cortex-m3的芯片那样使用这些较旧的芯片,还有其他一些性能障碍,某些外设无法像sysclk一样快地运行因此,您可能还有其他速度X,而您在其中一个/某些外围设备或外围总线的最大速度是X,而X +杂项则使时钟减半,因为这是最小的除数,现在外围设备和/或它们的总线现在速度是一半,因此您的代码性能可能会下降一半。您的此代码与外围设备无关。它确实使用乘法运算,这对性能有风险,但是对于cortex-m3,我没有看到单周期与其他周期相比有编译时选项,只是说单周期。

Peter涵盖了显而易见的优化方法,只要您要对某个数字进行计数(如果指令集允许)以及您的代码,在这种情况下,因为a * b * c = c * b * a,所以这样做很合理倒数,并使用标志与零或负号进行比较(如果这使您的船浮起),而不是增加,然后必须在有条件之前进行比较。当您跳到最后时,您会发现速度更快(时钟更少)。

M3没有高速缓存,M4和M7没有。因此,以小循环运行此代码,可能希望通过多次运行循环和时间来包装它们,以查看缓存和缓存行对齐等的影响。但是对于m3,一次通过就可以了(如果芯片没有隐藏的缓存,则无法控制)。

我只对这里的循环真正感兴趣,因为这最有可能成为自行车盗窃者。验证/限制输入,检查快捷方式,在相乘时查找溢出等,而这个答案不必担心。

我建议您在Google上查找Michael Abrash的书。例如Assembly Zen,您可以在github上构建副本。当它问世时,我就读了它,从那时起,我就几乎用了我学到的东西,调试了芯片,工具,打破了东西,提高了性能等等。8088/86出版时就过时了,如果您认为它是一本x86书籍您完全忘记了要点。例如,我对sram的假设将会更快,但这里没有发生。我还尝试了诸如在循环内添加nops(额外指令)之类的事情,不管是否相信,有时候可以使循环的性能更快。这些短管线,小的预取处理器通常不是这种情况。

有时您可以循环获得免费指令,即使有更多指令,时钟数也相同。例如,如果有一个多时钟乘法,则取决于循环中有多少个时钟以及您触摸的寄存器/资源的数量,您可能会得到一些免费的指令。这似乎是一个单周期乘积,所以在这里不能指望。

然后是您在Patterson和Hennessy教科书中阅读的管道内容。选择哪个寄存器会影响性能。如果可以在功能上重新排列说明等,说明的顺序。

通过简单实验记录的笔记

15
20000018 <fact>:
20000018:   b430        push    {r4, r5}
2000001a:   2100        movs    r1, #0
2000001c:   2203        movs    r2, #3
2000001e:   2301        movs    r3, #1
20000020:   6804        ldr r4, [r0, #0]

20000022 <fact_loop>:
20000022:   3101        adds    r1, #1
20000024:   434b        muls    r3, r1
20000026:   4291        cmp r1, r2
20000028:   d4fb        bmi.n   20000022 <fact_loop>
2000002a:   6805        ldr r5, [r0, #0]
2000002c:   1b60        subs    r0, r4, r5
2000002e:   bc30        pop {r4, r5}
20000030:   4770        bx  lr



12
20000018 <fact>:
20000018:   b430        push    {r4, r5}
2000001a:   2100        movs    r1, #0
2000001c:   2203        movs    r2, #3
2000001e:   2301        movs    r3, #1
20000020:   46c0        nop         ; (mov r8, r8)
20000022:   6804        ldr r4, [r0, #0]

20000024 <fact_loop>:
20000024:   3101        adds    r1, #1
20000026:   434b        muls    r3, r1
20000028:   4291        cmp r1, r2
2000002a:   d4fb        bmi.n   20000024 <fact_loop>
2000002c:   6805        ldr r5, [r0, #0]
2000002e:   1b60        subs    r0, r4, r5
20000030:   bc30        pop {r4, r5}
20000032:   4770        bx  lr





15
20000018 <fact>:
20000018:   b430        push    {r4, r5}
2000001a:   2100        movs    r1, #0
2000001c:   2203        movs    r2, #3
2000001e:   2301        movs    r3, #1
20000020:   46c0        nop         ; (mov r8, r8)
20000022:   46c0        nop         ; (mov r8, r8)
20000024:   6804        ldr r4, [r0, #0]

20000026 <fact_loop>:
20000026:   3101        adds    r1, #1
20000028:   434b        muls    r3, r1
2000002a:   4291        cmp r1, r2
2000002c:   d4fb        bmi.n   20000026 <fact_loop>
2000002e:   6805        ldr r5, [r0, #0]
20000030:   1b60        subs    r0, r4, r5
20000032:   bc30        pop {r4, r5}
20000034:   4770        bx  lr
20000036:   46c0        nop         ; (mov r8, r8)


12
20000018 <fact>:
20000018:   b430        push    {r4, r5}
2000001a:   2100        movs    r1, #0
2000001c:   2203        movs    r2, #3
2000001e:   2301        movs    r3, #1
20000020:   46c0        nop         ; (mov r8, r8)
20000022:   46c0        nop         ; (mov r8, r8)
20000024:   46c0        nop         ; (mov r8, r8)
20000026:   6804        ldr r4, [r0, #0]

20000028 <fact_loop>:
20000028:   3101        adds    r1, #1
2000002a:   434b        muls    r3, r1
2000002c:   4291        cmp r1, r2
2000002e:   d4fb        bmi.n   20000028 <fact_loop>
20000030:   6805        ldr r5, [r0, #0]
20000032:   1b60        subs    r0, r4, r5
20000034:   bc30        pop {r4, r5}
20000036:   4770        bx  lr





55
20000018 <fact>:
20000018:   b430        push    {r4, r5}
2000001a:   2100        movs    r1, #0
2000001c:   220b        movs    r2, #11
2000001e:   2301        movs    r3, #1
20000020:   6804        ldr r4, [r0, #0]

20000022 <fact_loop>:
20000022:   3101        adds    r1, #1
20000024:   434b        muls    r3, r1
20000026:   4291        cmp r1, r2
20000028:   d4fb        bmi.n   20000022 <fact_loop>
2000002a:   6805        ldr r5, [r0, #0]
2000002c:   1b60        subs    r0, r4, r5
2000002e:   bc30        pop {r4, r5}
20000030:   4770        bx  lr
20000032:   bf00        nop




42
20000018 <fact>:
20000018:   b430        push    {r4, r5}
2000001a:   2100        movs    r1, #0
2000001c:   220b        movs    r2, #11
2000001e:   2301        movs    r3, #1
20000020:   46c0        nop         ; (mov r8, r8)
20000022:   6804        ldr r4, [r0, #0]

20000024 <fact_loop>:
20000024:   3101        adds    r1, #1
20000026:   434b        muls    r3, r1
20000028:   4291        cmp r1, r2
2000002a:   d4fb        bmi.n   20000024 <fact_loop>
2000002c:   6805        ldr r5, [r0, #0]
2000002e:   1b60        subs    r0, r4, r5
20000030:   bc30        pop {r4, r5}
20000032:   4770        bx  lr


41
20000018 <fact>:
20000018:   b430        push    {r4, r5}
2000001a:   210b        movs    r1, #11
2000001c:   2301        movs    r3, #1
2000001e:   6804        ldr r4, [r0, #0]

20000020 <fact_loop>:
20000020:   434b        muls    r3, r1
20000022:   3901        subs    r1, #1
20000024:   d1fc        bne.n   20000020 <fact_loop>
20000026:   6805        ldr r5, [r0, #0]
20000028:   1b60        subs    r0, r4, r5
2000002a:   bc30        pop {r4, r5}
2000002c:   4770        bx  lr
2000002e:   bf00        nop



42
20000018 <fact>:
20000018:   b430        push    {r4, r5}
2000001a:   210b        movs    r1, #11
2000001c:   2301        movs    r3, #1
2000001e:   46c0        nop         ; (mov r8, r8)
20000020:   6804        ldr r4, [r0, #0]

20000022 <fact_loop>:
20000022:   434b        muls    r3, r1
20000024:   3901        subs    r1, #1
20000026:   d1fc        bne.n   20000022 <fact_loop>
20000028:   6805        ldr r5, [r0, #0]
2000002a:   1b60        subs    r0, r4, r5
2000002c:   bc30        pop {r4, r5}
2000002e:   4770        bx  lr



41
20000018 <fact>:
20000018:   b430        push    {r4, r5}
2000001a:   210b        movs    r1, #11
2000001c:   2301        movs    r3, #1
2000001e:   46c0        nop         ; (mov r8, r8)
20000020:   46c0        nop         ; (mov r8, r8)
20000022:   6804        ldr r4, [r0, #0]

20000024 <fact_loop>:
20000024:   434b        muls    r3, r1
20000026:   3901        subs    r1, #1
20000028:   d1fc        bne.n   20000024 <fact_loop>
2000002a:   6805        ldr r5, [r0, #0]
2000002c:   1b60        subs    r0, r4, r5
2000002e:   bc30        pop {r4, r5}
20000030:   4770        bx  lr
20000032:   bf00        nop





FLASH ACR 0x30

2d

08000020 <fact>:
 8000020:   b430        push    {r4, r5}
 8000022:   210b        movs    r1, #11
 8000024:   2301        movs    r3, #1
 8000026:   6804        ldr r4, [r0, #0]

08000028 <fact_loop>:
 8000028:   434b        muls    r3, r1
 800002a:   3901        subs    r1, #1
 800002c:   d1fc        bne.n   8000028 <fact_loop>
 800002e:   6805        ldr r5, [r0, #0]
 8000030:   1b60        subs    r0, r4, r5
 8000032:   bc30        pop {r4, r5}
 8000034:   4770        bx  lr


2d

08000020 <fact>:
 8000020:   b430        push    {r4, r5}
 8000022:   210b        movs    r1, #11
 8000024:   2301        movs    r3, #1
 8000026:   46c0        nop         ; (mov r8, r8)
 8000028:   6804        ldr r4, [r0, #0]

0800002a <fact_loop>:
 800002a:   434b        muls    r3, r1
 800002c:   3901        subs    r1, #1
 800002e:   d1fc        bne.n   800002a <fact_loop>
 8000030:   6805        ldr r5, [r0, #0]
 8000032:   1b60        subs    r0, r4, r5
 8000034:   bc30        pop {r4, r5}
 8000036:   4770        bx  lr



 FLASH_ACR 0x00

2d

08000020 <fact>:
 8000020:   b430        push    {r4, r5}
 8000022:   210b        movs    r1, #11
 8000024:   2301        movs    r3, #1
 8000026:   46c0        nop         ; (mov r8, r8)
 8000028:   6804        ldr r4, [r0, #0]

0800002a <fact_loop>:
 800002a:   434b        muls    r3, r1
 800002c:   3901        subs    r1, #1
 800002e:   d1fc        bne.n   800002a <fact_loop>
 8000030:   6805        ldr r5, [r0, #0]
 8000032:   1b60        subs    r0, r4, r5
 8000034:   bc30        pop {r4, r5}
 8000036:   4770        bx  lr


FLASH_ACR 0x02


5e
08000020 <fact>:
 8000020:   b430        push    {r4, r5}
 8000022:   210b        movs    r1, #11
 8000024:   2301        movs    r3, #1
 8000026:   6804        ldr r4, [r0, #0]

08000028 <fact_loop>:
 8000028:   434b        muls    r3, r1
 800002a:   3901        subs    r1, #1
 800002c:   d1fc        bne.n   8000028 <fact_loop>
 800002e:   6805        ldr r5, [r0, #0]
 8000030:   1b60        subs    r0, r4, r5
 8000032:   bc30        pop {r4, r5}
 8000034:   4770        bx  lr

5f
08000020 <fact>:
 8000020:   b430        push    {r4, r5}
 8000022:   210b        movs    r1, #11
 8000024:   2301        movs    r3, #1
 8000026:   46c0        nop         ; (mov r8, r8)
 8000028:   6804        ldr r4, [r0, #0]

0800002a <fact_loop>:
 800002a:   434b        muls    r3, r1
 800002c:   3901        subs    r1, #1
 800002e:   d1fc        bne.n   800002a <fact_loop>
 8000030:   6805        ldr r5, [r0, #0]
 8000032:   1b60        subs    r0, r4, r5
 8000034:   bc30        pop {r4, r5}
 8000036:   4770        bx  lr


FLASH_ACR 0x32

41


08000020 <fact>:
 8000020:   b430        push    {r4, r5}
 8000022:   210b        movs    r1, #11
 8000024:   2301        movs    r3, #1
 8000026:   6804        ldr r4, [r0, #0]

08000028 <fact_loop>:
 8000028:   434b        muls    r3, r1
 800002a:   3901        subs    r1, #1
 800002c:   d1fc        bne.n   8000028 <fact_loop>
 800002e:   6805        ldr r5, [r0, #0]
 8000030:   1b60        subs    r0, r4, r5
 8000032:   bc30        pop {r4, r5}
 8000034:   4770        bx  lr

 41

08000020 <fact>:
 8000020:   b430        push    {r4, r5}
 8000022:   210b        movs    r1, #11
 8000024:   2301        movs    r3, #1
 8000026:   46c0        nop         ; (mov r8, r8)
 8000028:   6804        ldr r4, [r0, #0]

0800002a <fact_loop>:
 800002a:   434b        muls    r3, r1
 800002c:   3901        subs    r1, #1
 800002e:   d1fc        bne.n   800002a <fact_loop>
 8000030:   6805        ldr r5, [r0, #0]
 8000032:   1b60        subs    r0, r4, r5
 8000034:   bc30        pop {r4, r5}
 8000036:   4770        bx  lr


    PUT32(FLASH_ACR,0x3A);



41
08000020 <fact>:
 8000020:   b430        push    {r4, r5}
 8000022:   210b        movs    r1, #11
 8000024:   2301        movs    r3, #1
 8000026:   6804        ldr r4, [r0, #0]

08000028 <fact_loop>:
 8000028:   434b        muls    r3, r1
 800002a:   3901        subs    r1, #1
 800002c:   d1fc        bne.n   8000028 <fact_loop>
 800002e:   6805        ldr r5, [r0, #0]
 8000030:   1b60        subs    r0, r4, r5
 8000032:   bc30        pop {r4, r5}
 8000034:   4770        bx  lr
    ...

41
08000020 <fact>:
 8000020:   b430        push    {r4, r5}
 8000022:   210b        movs    r1, #11
 8000024:   2301        movs    r3, #1
 8000026:   46c0        nop         ; (mov r8, r8)
 8000028:   6804        ldr r4, [r0, #0]

0800002a <fact_loop>:
 800002a:   434b        muls    r3, r1
 800002c:   3901        subs    r1, #1
 800002e:   d1fc        bne.n   800002a <fact_loop>
 8000030:   6805        ldr r5, [r0, #0]
 8000032:   1b60        subs    r0, r4, r5
 8000034:   bc30        pop {r4, r5}
 8000036:   4770        bx  lr







flash acr 0x32


4c
08000020 <fact>:
 8000020:   b430        push    {r4, r5}
 8000022:   210b        movs    r1, #11
 8000024:   2301        movs    r3, #1
 8000026:   6804        ldr r4, [r0, #0]

08000028 <fact_loop>:
 8000028:   46c0        nop         ; (mov r8, r8)
 800002a:   434b        muls    r3, r1
 800002c:   3901        subs    r1, #1
 800002e:   d1fb        bne.n   8000028 <fact_loop>
 8000030:   6805        ldr r5, [r0, #0]
 8000032:   1b60        subs    r0, r4, r5
 8000034:   bc30        pop {r4, r5}
 8000036:   4770        bx  lr



4c

08000020 <fact>:
 8000020:   b430        push    {r4, r5}
 8000022:   210b        movs    r1, #11
 8000024:   2301        movs    r3, #1
 8000026:   46c0        nop         ; (mov r8, r8)
 8000028:   6804        ldr r4, [r0, #0]

0800002a <fact_loop>:
 800002a:   46c0        nop         ; (mov r8, r8)
 800002c:   434b        muls    r3, r1
 800002e:   3901        subs    r1, #1
 8000030:   d1fb        bne.n   800002a <fact_loop>
 8000032:   6805        ldr r5, [r0, #0]
 8000034:   1b60        subs    r0, r4, r5
 8000036:   bc30        pop {r4, r5}
 8000038:   4770        bx  lr


flash acr 0x30


38
08000020 <fact>:
 8000020:   b430        push    {r4, r5}
 8000022:   210b        movs    r1, #11
 8000024:   2301        movs    r3, #1
 8000026:   6804        ldr r4, [r0, #0]

08000028 <fact_loop>:
 8000028:   46c0        nop         ; (mov r8, r8)
 800002a:   434b        muls    r3, r1
 800002c:   3901        subs    r1, #1
 800002e:   d1fb        bne.n   8000028 <fact_loop>
 8000030:   6805        ldr r5, [r0, #0]
 8000032:   1b60        subs    r0, r4, r5
 8000034:   bc30        pop {r4, r5}
 8000036:   4770        bx  lr


3b
0800002c <fact_loop>:
 800002c:   d002        beq.n   8000034 <fact_done>
 800002e:   434b        muls    r3, r1
 8000030:   3901        subs    r1, #1
 8000032:   e7fb        b.n 800002c <fact_loop>

08000034 <fact_done>:
 8000034:   6805        ldr r5, [r0, #0]
 8000036:   1b60        subs    r0, r4, r5
 8000038:   bc30        pop {r4, r5}
 800003a:   4770        bx  lr






38

08000020 <fact>:
 8000020:   b430        push    {r4, r5}
 8000022:   2100        movs    r1, #0
 8000024:   220b        movs    r2, #11
 8000026:   2301        movs    r3, #1
 8000028:   6804        ldr r4, [r0, #0]

0800002a <fact_loop>:
 800002a:   3101        adds    r1, #1
 800002c:   434b        muls    r3, r1
 800002e:   4291        cmp r1, r2
 8000030:   d4fb        bmi.n   800002a <fact_loop>
 8000032:   6805        ldr r5, [r0, #0]
 8000034:   1b60        subs    r0, r4, r5
 8000036:   bc30        pop {r4, r5}
 8000038:   4770        bx  lr



38
08000020 <fact>:
 8000020:   b430        push    {r4, r5}
 8000022:   2100        movs    r1, #0
 8000024:   220b        movs    r2, #11
 8000026:   2301        movs    r3, #1
 8000028:   46c0        nop         ; (mov r8, r8)
 800002a:   6804        ldr r4, [r0, #0]

0800002c <fact_loop>:
 800002c:   3101        adds    r1, #1
 800002e:   434b        muls    r3, r1
 8000030:   4291        cmp r1, r2
 8000032:   d4fb        bmi.n   800002c <fact_loop>
 8000034:   6805        ldr r5, [r0, #0]
 8000036:   1b60        subs    r0, r4, r5
 8000038:   bc30        pop {r4, r5}
 800003a:   4770        bx  lr





2d


08000020 <fact>:
 8000020:   b430        push    {r4, r5}
 8000022:   210b        movs    r1, #11
 8000024:   2301        movs    r3, #1
 8000026:   6804        ldr r4, [r0, #0]

08000028 <fact_loop>:
 8000028:   434b        muls    r3, r1
 800002a:   3901        subs    r1, #1
 800002c:   d1fc        bne.n   8000028 <fact_loop>
 800002e:   6805        ldr r5, [r0, #0]
 8000030:   1b60        subs    r0, r4, r5
 8000032:   bc30        pop {r4, r5}
 8000034:   4770        bx  lr

跳到此处:

请注意,我将循环数(输入值从3更改为11)。

在闪存上启用零等待状态并启用预取后,您的循环:

38
08000020 <fact>:
 8000020:   b430        push    {r4, r5}
 8000022:   2100        movs    r1, #0
 8000024:   220b        movs    r2, #11
 8000026:   2301        movs    r3, #1
 8000028:   6804        ldr r4, [r0, #0]

0800002a <fact_loop>:
 800002a:   3101        adds    r1, #1
 800002c:   434b        muls    r3, r1
 800002e:   4291        cmp r1, r2
 8000030:   d4fb        bmi.n   800002a <fact_loop>
 8000032:   6805        ldr r5, [r0, #0]
 8000034:   1b60        subs    r0, r4, r5
 8000036:   bc30        pop {r4, r5}
 8000038:   4770        bx  lr

这意味着两个ldr指令之间的0x38操纵杆时钟。对齐并不会影响这一点。

如果您使用Peter或其上的变体(对我来说,比加减号YMMV更有意义)

2d
08000020 <fact>:
 8000020:   b430        push    {r4, r5}
 8000022:   210b        movs    r1, #11
 8000024:   2301        movs    r3, #1
 8000026:   6804        ldr r4, [r0, #0]

08000028 <fact_loop>:
 8000028:   434b        muls    r3, r1
 800002a:   3901        subs    r1, #1
 800002c:   d1fc        bne.n   8000028 <fact_loop>
 800002e:   6805        ldr r5, [r0, #0]
 8000030:   1b60        subs    r0, r4, r5
 8000032:   bc30        pop {r4, r5}
 8000034:   4770        bx  lr

对齐方式也不影响此循环。它的指令更少,而且更快。

因此,从另一个答案和文档中的mul和sub一个时钟每个分支取时,根据该答案为2个时钟,因此,每循环4个时钟乘以11是44个时钟或0x2C。毫无疑问,这两个ldrs的成本也许就是另外两个时钟的来源。或者可能是预取单元的工作方式或其他。

您的循环是5个时钟或55或0x37,对于要测量的另外两个时钟,答案是相同的。

因此,我使其中的一些实验复杂化了,来自ST的预取单元以及在零等待状态下运行使我们能够看到ARM文档中显示的性能。递减计数而不是递增计数在循环中保存了一条指令,该指令既小又快,这正是您要的。

您的每个循环5个时钟乘以3个阶乘表示14个时钟(5 + 5 + 4),您的22个时钟(检查您的测量方式,标尺经常是基准测试的问题而不是代码的问题)在其他地方有8个时钟如果要计算,请减去设置说明的3。如果使用倒数解决方案,无论使用哪种尺子,都可以在系统上进行比较。保存一些指令,一个在循环中一个在循环外。

-------编辑

我很惊讶gcc没有将其优化为倒计时循环。我只尝试了一个版本,可能是较旧的3.x或4.x。同样,如果您是为cortex-m3生成的,它会使用thumb2指令而不是thumb指令。

unsigned int fact ( unsigned int x )
{
    unsigned int a;
    unsigned int rb;
    a=1;
    for(rb=1;rb<=x;rb++)
    {
        a*=rb;
    }
    return(a);
}
unsigned int fact2 ( unsigned int x )
{
    unsigned int a;
    a=1;
    while(x)
    {
        a*=x--;
    }
    return(a);
}

是的,我可以进一步优化C代码。...

Disassembly of section .text:

00000000 <fact>:
   0:   b140        cbz r0, 14 <fact+0x14>
   2:   2301        movs    r3, #1
   4:   461a        mov r2, r3
   6:   fb03 f202   mul.w   r2, r3, r2
   a:   3301        adds    r3, #1
   c:   4298        cmp r0, r3
   e:   d2fa        bcs.n   6 <fact+0x6>
  10:   4610        mov r0, r2
  12:   4770        bx  lr
  14:   2201        movs    r2, #1
  16:   4610        mov r0, r2
  18:   4770        bx  lr
  1a:   bf00        nop

0000001c <fact2>:
  1c:   4603        mov r3, r0
  1e:   2001        movs    r0, #1
  20:   b123        cbz r3, 2c <fact2+0x10>
  22:   fb03 f000   mul.w   r0, r3, r0
  26:   3b01        subs    r3, #1
  28:   d1fb        bne.n   22 <fact2+0x6>
  2a:   4770        bx  lr
  2c:   4770        bx  lr
  2e:   bf00        nop

我忘了cbz,除非有必要,否则我不会使用thumb2,而不是像一般的Thumb指令一样普遍移植...

更多便携式版本:

Disassembly of section .text:

00000000 <fact>:
   0:   2800        cmp r0, #0
   2:   d007        beq.n   14 <fact+0x14>
   4:   2301        movs    r3, #1
   6:   2201        movs    r2, #1
   8:   435a        muls    r2, r3
   a:   3301        adds    r3, #1
   c:   4298        cmp r0, r3
   e:   d2fb        bcs.n   8 <fact+0x8>
  10:   0010        movs    r0, r2
  12:   4770        bx  lr
  14:   2201        movs    r2, #1
  16:   e7fb        b.n 10 <fact+0x10>

00000018 <fact2>:
  18:   0003        movs    r3, r0
  1a:   2001        movs    r0, #1
  1c:   2b00        cmp r3, #0
  1e:   d003        beq.n   28 <fact2+0x10>
  20:   4358        muls    r0, r3
  22:   3b01        subs    r3, #1
  24:   2b00        cmp r3, #0
  26:   d1fb        bne.n   20 <fact2+0x8>
  28:   4770        bx  lr
  2a:   46c0        nop         ; (mov r8, r8)

嗯:

  20:   4358        muls    r0, r3
  22:   3b01        subs    r3, #1
  24:   2b00        cmp r3, #0
  26:   d1fb        bne.n   20 <fact2+0x8>

哇。

arm-none-eabi-gcc --version
arm-none-eabi-gcc (GCC) 8.3.0
Copyright (C) 2018 Free Software Foundation, Inc.
This is free software; see the source for copying conditions.  There is NO
warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.

答案 3 :(得分:1)

可能会使用类似这样的东西:(假设32位寄存器,其中12!是最大可能的值),但是Peter Cordes对ARM更加熟悉(自从我使用ARM已有10年了),他的代码基于答案是好的。我在下面显示的表查找应该是最快的,并且它需要更多的空间,但由于范围为0,所以不需要太多!到12!表示32位无符号整数。

        mov     r2,#3      ;r2 = n
;       ...
        mov     r3,#1
        sub     r2,#2
        blo     factx
        mov     r1,#(fact11-fact12)
        mul     r1,r2,r1          ; or better, use a left-shift by 2 or 3 and an assemble time static assert that fact11-fact12 == 4 or 8
        adr     r2,fact2
        sub     r2,r2,r1
        mov     r1,#2
        b       r2            

fact12  mul     r3,r1,r3
        add     r1,r1,#1
fact11  mul     r3,r1,r3
        add     r1,r1,#1
        mul     r3,r1,r3
        add     r1,r1,#1
        mul     r3,r1,r3
        add     r1,r1,#1
        mul     r3,r1,r3
        add     r1,r1,#1
        mul     r3,r1,r3
        add     r1,r1,#1
        mul     r3,r1,r3
        add     r1,r1,#1
        mul     r3,r1,r3
        add     r1,r1,#1
        mul     r3,r1,r3
        add     r1,r1,#1
        mul     r3,r1,r3
        add     r1,r1,#1
fact2   mul     r3,r1,r3
factx   ...                  ;r3 = n!

或更简单的是,一个表查找:

tblfac  dcd     1,1,2,6,24,120,720,5040
        dcd     40320,362880,3628800,39916800
        dcd     479001600 
;       ...
        mov     r2,#3                    ;r2 = n

        adr     r3,tblfac
        ldr     r3,[r3, r2, lsl #2]      ;r3 = n!