function c1()
x::UInt64 = 0
while x<= (10^8 * 10)
x+=1
end
end
function c2()
x::UInt64 = 0
while x<= (10^9)
x+=1
end
end
function c3()
x::UInt64 = 0
y::UInt64 = 10^8 * 10
while x<= y
x+=1
end
end
function c4()
x::UInt64 = 0
y::UInt64 = 10^9
while x<= y
x+=1
end
end
应该一样吧?
@time c1()
0.019102 seconds (40.99 k allocations: 2.313 MiB)
@time c1()
0.000003 seconds (4 allocations: 160 bytes)
@time c2()
9.205925 seconds (47.89 k allocations: 2.750 MiB)
@time c2()
9.015212 seconds (4 allocations: 160 bytes)
@time c3()
0.019848 seconds (39.23 k allocations: 2.205 MiB)
@time c3()
0.000003 seconds (4 allocations: 160 bytes)
@time c4()
0.705712 seconds (47.41 k allocations: 2.719 MiB)
@time c4()
0.760354 seconds (4 allocations: 160 bytes)
答案 0 :(得分:5)
这是关于Julia使用逐次幂运算对字面量进行编译时的优化。 Julia可以优化是否可以仅通过平方幂或幂为0、1、2、3来达到指数。我相信这是通过将整数x^p
的{{1}}降低到x^Val{p}
并使用编译器专业化(或内联以及某种元编程来完成的,我不确定这里的正确术语是什么,但是就像您在Lisp中会发现的一样;在Julia中使用相似的技术进行源到源的自动区分,请参见Zygote.jl)技术,以在p
为0,1时将代码降低为常数,2,3或2的幂。
Julia将p
降低到内联 10^8
(然后是literal_pow
),然后降低到一个常数,然后julia将power_by_squaring
降低到得到另一个常量,然后意识到所有的while循环都是不必要的,并删除循环,等等,全部在编译时。
如果将constant * 10
中的10^8
更改为10^7
,您将看到它将在运行时评估数字和循环。但是,如果将c1
替换为10^8
或10^4
,您会发现它将在编译时处理所有计算。我认为,如果指数是2的幂,julia并没有专门针对编译时优化进行设置,而是证明编译器能够针对这种情况优化代码(将代码降低为常数)。
在10^2
是1,2,3的情况下,Julia进行了硬编码。通过将代码降低到p
的内联版本,然后进行编译专用化,再次对此进行了优化。
您可以使用literal_pow
和@code_llvm
宏来查看发生了什么。试试吧。
@code_native
看到julia> f() = 10^8*10
julia> g() = 10^7*10
julia> @code_native f()
.text
; Function f {
; Location: In[101]:2
movl $1000000000, %eax # imm = 0x3B9ACA00
retq
nopw %cs:(%rax,%rax)
;}
julia> @code_native g()
.text
; Function g {
; Location: In[104]:1
; Function literal_pow; {
; Location: none
; Function macro expansion; {
; Location: none
; Function ^; {
; Location: In[104]:1
pushq %rax
movabsq $power_by_squaring, %rax
movl $10, %edi
movl $7, %esi
callq *%rax
;}}}
; Function *; {
; Location: int.jl:54
addq %rax, %rax
leaq (%rax,%rax,4), %rax
;}
popq %rcx
retq
;}
只是一个常数,而f()
将在运行时评估内容。
我想,如果您想进一步挖掘,茱莉亚(Julia)在this commit附近开始了这种整数幂运算。
编辑:让我们在编译时优化g()
我还准备了一个计算整数整数指数的函数,julia还将使用该函数针对非2幂幂指数进行优化。不过,我不确定在所有情况下都正确。
c2
现在将@inline function ipow(base::Int, exp::Int)
result = 1;
flag = true;
while flag
if (exp & 1 > 0)
result *= base;
end
exp >>= 1;
base *= base;
flag = exp != 0
end
return result;
end
中的10^9
替换为c2
,并享受编译时优化的强大功能。
另请参见this question进行逐次平方运算。
请不要按原样使用此函数,因为它会尝试内联所有指数,无论它是否包含文字。您不会想要的。
答案 1 :(得分:1)
第二次更新:查看hckr答案。比我好得多。
更新:这不是一个全面的答案。尽我所能解决,但由于时间限制,我不得不暂时放弃。
我可能不是回答这个问题的最佳人选,因为就编译器优化而言,我知道足够危险。希望能更好地了解Julia的编译器的人会偶然发现这个问题,并能给出更全面的答复,因为从我所看到的情况来看,您的c2
函数正在做很多不必要的工作。
因此,这里至少有两个问题在起作用。首先,按现状,c1
和c2
都将始终返回nothing
。由于某种原因,我不明白,对于c1
,编译器可以解决此问题,而对于c2
,则无法解决。因此,在编译之后,c1
几乎立即运行,因为从未真正执行算法中的循环。确实:
julia> @btime c1()
1.535 ns (0 allocations: 0 bytes)
您还可以使用@code_native c1()
和@code_native c2()
看到它。前者只有几行,而后者则包含更多指令。同样值得注意的是,前者不包含对函数<=
的任何引用,表明while
循环中的条件已被完全优化。
我们可以通过在两个函数的底部添加一个return x
语句来处理第一个问题,这将迫使编译器实际解决x
的最终值是什么的问题。 。
但是,如果这样做,您会注意到c1
仍然比c2
快10倍,这是有关示例的第二个令人困惑的问题。
在我看来,即使使用return x
,一个足够聪明的编译器仍具有完全跳过循环所需的所有信息。也就是说,它在编译时就知道x
的起始值,循环内转换的确切值以及终止条件的确切值。出乎意料的是,如果运行@code_native c1()
(在底部添加return x
之后),您会注意到它确实已经在本地代码(cmpq $1000000001
中计算出了函数返回值。 ):
julia> @code_native c1()
.text
; Function c1 {
; Location: REPL[2]:2
movq $-1, %rax
nopw (%rax,%rax)
; Location: REPL[2]:3
; Function <=; {
; Location: int.jl:436
; Function <=; {
; Location: int.jl:429
L16:
addq $1, %rax
cmpq $1000000001, %rax # imm = 0x3B9ACA01
;}}
jb L16
; Location: REPL[2]:6
retq
nopl (%rax)
;}
所以我不太确定为什么它仍在做任何工作!
作为参考,以下是@code_native c2()
的输出(在添加return x
之后):
julia> @code_native c2()
.text
; Function c2 {
; Location: REPL[3]:2
pushq %r14
pushq %rbx
pushq %rax
movq $-1, %rbx
movabsq $power_by_squaring, %r14
nopw %cs:(%rax,%rax)
; Location: REPL[3]:3
; Function literal_pow; {
; Location: none
; Function macro expansion; {
; Location: none
; Function ^; {
; Location: intfuncs.jl:220
L32:
addq $1, %rbx
movl $10, %edi
movl $9, %esi
callq *%r14
;}}}
; Function <=; {
; Location: int.jl:436
; Function >=; {
; Location: operators.jl:333
; Function <=; {
; Location: int.jl:428
testq %rax, %rax
;}}}
js L59
cmpq %rax, %rbx
jbe L32
; Location: REPL[3]:6
L59:
movq %rbx, %rax
addq $8, %rsp
popq %rbx
popq %r14
retq
nopw %cs:(%rax,%rax)
;}
显然,c2
上还有很多其他工作对我来说没有多大意义。希望更熟悉Julia内部的人能对此有所启发。