- html - 出于某种原因,IE8 对我的 Sass 文件中继承的 html5 CSS 不友好?
- JMeter 在响应断言中使用 span 标签的问题
- html - 在 :hover and :active? 上具有不同效果的 CSS 动画
- html - 相对于居中的 html 内容固定的 CSS 重复背景?
基本上,我很难使执行时间比实际时间要短,并且要减少时钟周期和内存大小。有谁知道我该怎么做吗?该代码工作正常,我只想稍作更改。
编写了有效的代码,但是不想弄乱代码,也不想知道要进行哪些更改。
; 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
最佳答案
通常,代码大小和性能是一个折衷方案。展开循环通常有助于提高性能(至少对于大型输入而言),但是在循环外部需要额外的逻辑来处理清理等。
大多数答案是假设使用诸如Cortex-A9或Cortex-A53之类的高性能CPU,其中通过软件流水线创建指令级并行性会有所帮助。 Cortex M3是标量的,具有单周期乘法指令,因此优化起来要容易得多。
(最初的问题没有指定内核,我期望即使是低端CPU也会具有多周期mul
延迟。在编写它之后,我才发现Cortex-M3编号。)
您的代码可能会成为整数乘法延迟的瓶颈。与add
不同,在下一个周期将准备好结果,而mul
则很复杂,需要多个周期才能生成结果。
(除了在某些时钟非常慢的芯片上,例如Cortex-M3显然具有1周期的mul
指令。但该指令的Cortex-M0/M0+/M23 are available with a choice的性能为1周期或32周期性能!缓慢的迭代=更小的硅。)
乘法执行单元本身通常是流水线式的,因此可以一次运行多个独立的乘法,但是阶乘循环需要将每个乘法结果作为下一次迭代的输入。 (仅适用于高性能内核,而不是Cortex-M系列。慢速Cortex-M芯片上的32周期乘法是迭代的,并且可能没有流水线,因此在运行时无法启动另一个乘法,因此没有任何好处。除了减少循环开销之外,还公开任何指令级并行性。)
请注意,乘法是关联的:1 * 2 * 3
= 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
的循环中将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位d
寄存器每2个周期进行2次乘法,但是那么您需要更多的vadd
指令,而与乘法不同,vadd.32
与128位q
regs一样快,与64位向量一样快。因此,SIMD可以为您在Cortex- A9,如果您使用足够的寄存器来隐藏较大的延迟,但是SIMD可能仅对n
有用,因为n!
太大,以致mul
溢出32位整数,因此得到模2 ^ 32的结果。)
较低延迟的ARM乘法指令:muls
是32x32 => 32位乘法。在Cortex-A9上,它具有2c的吞吐量和4c的延迟。
(mul
是Thumb模式下的16位指令,除非不需要消除标志,否则应首选smulbb
。Thumb模式下的smulxy
仅在ARMv6T2和更高版本中可用。)muls
是16x16 => 32位有符号乘法,仅读取其输入的低半部分,但在A9上具有1c吞吐量和3c延迟。 (BB =底部,底部。也可以使用其他组合,以及乘累加和各种时髦的东西。)
没有2个字节的Thumb版本的smulxy
,因此对于代码大小而言,这要比int16_t
更糟糕。
不幸的是,uint16_t
在无符号版本中不可用,因此将我们可以使用的输入范围限制为正数sqrt(n!)
,而不是(n-1)! * n
。
但是,如果我们只关心最终的32位结果没有溢出的情况,我们可以安排操作顺序,以便最后一个乘法具有2个大小相似的输入(均为大的16位数字)。即尽可能接近(n-1)!
。所以赔率和偶数的乘积是合理的,但是n
是最坏的情况,因为这需要1
适应16位。实际上,最坏的情况是从smulbb
开始倒数,因此最后一个是乘以3再乘以2。
将这些部分放在一起,请注意,乘以n-1
是无操作的操作(n
除外,它将输入截断为16位)。因此,我们可以根据输入为奇数或偶数,以乘以1或2后停止的方式展开。
因此,我们只知道lo(以subs
开头)和hi(以smulbb
开头)而不是知道奇数和偶数。
;; 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 r2, r2, #2
却不是
muls r0, r1
。如果需要,您可以每次将其写为
hiprod
。
loprod
用于最终产品,因为最终的
hiprod
比
mul d,d, src
高一点。即使
sub
> max int16_t,产品也可能不会溢出。这也将节省2个字节的代码大小,但会在Cortex-A9上增加1个周期的延迟。 (顺便说一句,ARMv6用
subs
怪异修正了“不可预测的结果”,并且您的代码使用了32位Thumb2指令,因此无论如何它仅适用于ARMv6T2及更高版本。)
smulbb
而不是
loprod
上多花2个字节,以便我们可以在分支之前计算几个指令的标志,从而减少分支的错误预测损失并避免有序CPU停顿。
hi
不会触摸标志,因此我们可以先执行
r3
并使
r2
东西不触摸标志。
.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);
smulbb
读取它们之后,我们将立即修改
subs Rd, Rn, #imm
和
sub
,以避免为顺序芯片上的数据相关性造成停顿。
SP
can be encoded as a 16-bit Thumb instruction for imm=0..7(3位立即数)。或使用与src和目的地相同的寄存器,用于imm = 0..255。因此,我的复制和订阅说明非常紧凑。
moveq r0, #6
作为操作数外,无标志设置的
IT
不能是16位指令。
n==0
)要求汇编器使用
cmp r0,#0
instruction为下一个最多4条指令引入谓词。在ARM模式下,每个指令的高4位表示预测。 (如果您不使用后缀,则汇编程序会将其编码为始终(即不带谓词)。)
moveq r0, #1
/
cbnz
处理另外4或6个字节的
mov r0, #1
情况。如果我们将tst / mov放在同一IT块中,则可能将其减少到4个字节。 IT不会快照实际的标志条件,而是快照谓词,因此IT块中的标志设置指令可能会影响同一块中的后续指令。 (我认为这是正确的,但我不确定100%)。
tiny_input: ; r0 = n, flags set according to n-3
ITET EQ
moveq r0, #6
cmpne r0, #0
moveq r0, #1
cbnz
有条件地跳过
0! = 1
。但是分支目标必须在
mov
之后从4到130个字节,因此,显然我们不能仅跳过一条16位指令!
$ 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
mul
,则为0x26。)
b<cond>
指令来产生输入),但是从理论上讲,对于具有流水线乘法器的CPU,大输入的速度可能要快两倍。对于从1到3的输入来说,它可能要快得多,它只分支一次并产生结果。
bx lr
指令和Thumb2模式。维基百科说它的乘法是1个周期!所以这很奇怪,我很惊讶它具有如此高效的乘数。或仅仅是因为它的时钟太慢,以至于在1级中有很多门延迟的时间,而这只是3级流水线。
0! = 1
花费1个周期(未使用)或2个周期(使用)。 (1用于分支,1用于在立即移位后重新加载管道。)。因此,与sub / mul相比,采用分支的速度慢,展开很有价值,因此上面的代码仍然可以正常工作。 (但是不需要多个产品累加器,因此可以简化)。
;; 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
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
IT
返回指令。
subs
之后,在分支之前使用
itt ls
块来处理
movls r0, #1
情况,因此我们仍然可以在循环之后跳到右边(而不是像我的Cortex-A9版本那样跳到单独的块)。您也可以为此使用技巧。
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
r0
/
r0 == 1
,因此分支位于IT块内(分支指令可以使用一种编码,该编码在位移上花费更多的位,而在谓词上不花费任何位)。但是在这种情况下,它的范围很短,因此在
cmp
情况下,我选择不修改<cc>。我不知道是否有任何CPU使谓词指令成为NOP而不是运行它的效率更高或等待时间更短,但可能存在。
*=1
放入循环中以避免最后一次
n=2
迭代将使我们每次迭代花费一个额外的周期(4个周期而不是3个周期),因此只能通过
n=3
或
sub
来偿还费用。
mov
或
mul
这样的指令为每个
n
生成单独的输入,除非通过对每个
*2 * 4
的特殊情况序列进行硬编码(例如
*8
= < cc> =左移3),而您可以将答案硬编码。
关于assembly - 如何减少阶乘循环的执行时间和周期数?和/或代码大小?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/55599285/
我是 Bison 解析的新手,我无法理解它是如何工作的。我有以下语法,其中我保留了最低限度的语法来突出问题。 %left '~' %left '+' %token T_VARIABLE %% star
我链接了 2 个映射器和 1 个缩减器。是否可以将中间输出(链中每个映射器的 o/p)写入 HDFS?我尝试为每个设置 OutputPath,但它似乎不起作用。现在,我不确定是否可以完成。有什么建议吗
我正在编写一些代码来管理自定义磁盘文件结构并将其同步到未连接的系统。我的要求之一是能够在实际生成同步内容之前估计同步的大小。作为一个简单的解决方案,我整理了一个包含完整路径文件名的 map ,作为高效
我来自一个 SQL 世界,其中查找由多个对象属性(published = TRUE 或 user_id = X)完成,并且有 任何地方都没有加入 (因为 1:1 缓存层)。文档数据库似乎很适合我的数据
在 R 中,我有一个整数向量。从这个向量中,我想随机减少每个整数元素的值,以获得向量的总和,即初始总和的百分比。 在这个例子中,我想将向量“x”减少到向量“y”,其中每个元素都被随机减少以获得等于初始
我发现自己遇到过几次我有一个 reducer /组合 fn 的情况,如下所示: def combiner(a: String, b: String): Either[String, String]
Ubuntu 12.04 nginx 1.2.4 avconv版本 avconv version 0.8.10-4:0.8.10-0ubuntu0.12.04.1, Copyright (c) 200
我是 R 编程语言的新手。我有一个包含 2 列(ID 和 Num)的数据集,如下所示: ID Num 3 8 3 12 4 15 4 18 4
我正在使用高阶函数将函数应用于向量中的每个元素并将结果作为标量值返回。 假设我有: v = c(0, 1, 2, 3, 4, 5, 6, 7, 8) 我想计算以左边 5 个整数为中心的所有这些整数的总
关闭。这个问题需要debugging details .它目前不接受答案。 编辑问题以包含 desired behavior, a specific problem or error, and th
这个问题在这里已经有了答案: How to write the dataframes in a list to a single csv file (2 个回答) 5年前关闭。 我正在尝试使用 Red
刚开始学习CUDA编程,对归约有些迷茫。 我知道与共享内存相比,全局内存有很多访问延迟,但我可以使用全局内存来(至少)模拟类似于共享内存的行为吗? 例如,我想对长度恰好为 BLOCK_SIZE * T
我经常使用OptiPNG或pngcrush减小PNG图像的文件大小。 我希望能够从.NET应用程序中以编程方式执行此类操作。我正在动态生成要发送到移动设备的PNG,因此我想减小文件大小。 图像质量很重
减少和减少让您在序列上累积状态。 序列中的每个元素都会修改累积的状态,直到 到达序列的末尾。 在无限列表上调用reduce 或reductions 有什么含义? (def c (cycle [0]))
这与R: use the newly generated data in the previous row有关 我意识到我面临的实际问题比我在上面的线程中给出的示例要复杂一些 - 似乎我必须将 3 个
有什么办法可以减少.ttf字体的大小?即如果我们要删除一些我们不使用的glyps。 最佳答案 使用Google Web Fonts,您可以限制字符集,例如: //fonts.googleapis.co
我需要在iOS中制作一个应用程序,在她的工作过程中发出类似“哔”的声音。 我已经使用MPMusicPlayerController实现了与背景ipod的交互。 问题: 由于来自ipod的音乐音量很大,
我有一个嵌套 map m,如下所示: m = Map("电子邮件"-> "a@b.com", "背景"-> Map("语言"-> "英语")) 我有一个数组arr = Array("backgroun
有什么原因为什么不应该转发map / reduce函数中收到的可写内容? 我的意思是-每个map / reduce函数都有一个可写的键/值,并可能发出一个键/值对。如果我想执行一些过滤,我应该只发出接
假设我有一个数据列表 val data = listOf("F 1", "D 2", "U 1", "D 3", "F 10") 我想执行每个元素的给定逻辑。 我必须在外部添加 var acc2 =
我是一名优秀的程序员,十分优秀!