一个快速编码技巧以某种方式最终使Julia中的代码变慢
我听说意识到类型稳定性对 Julia 编程的高性能有很大贡献,所以我尝试测量将类型不稳定的函数重写为类型稳定版本时可以节省多少时间。正如很多人所说,我认为类型稳定的编码当然比类型不稳定的编码具有更高的性能。然而,结果却相反:
# type-unstable vs type-stable
#?type-unstable
function positive(x)
if x < 0
return 0.0
else
return x
end
end
# type-stable
function positive_safe(x)
if x < 0
return zero(x)
else
return x
end
end
@time for n in 1:100_000_000
a = 2^( positive(-n) + 1 )
end
@time for n in 1:100_000_000
b = 2^( positive_safe(-n) + 1 )
end
结果:
0.040080 seconds
0.150596 seconds
我无法相信这。我的代码中是否有一些错误?或者这是事实?
任何信息,将不胜感激。
语境
- 操作系统和版本:Windows 10
- 浏览器和版本:Google Chrome 90.0.4430.212?官方版本??64 位)
- JupyterLab 版本:3.0.14
@btime 结果
只是将@time 替换为@btime 为我上面的代码
@btime for n in 1:100_000_000
a = 2^( positive(-n) + 1 )
end
# -> 1.500 ns
@btime for n in 1:100_000_000
b = 2^( positive_safe(-n) + 1 )
end
# -> 503.146 ms
还是很奇怪。
DNF 给我看的完全相同的代码
using BenchmarkTools
@btime 2^(positive(-n) + 1) setup=(n=rand(1:10^8))
# -> 32.435 ns (0 allocations: 0 bytes)
@btime 2^(positive_safe(-n) + 1) setup=(n=rand(1:10^8))
#-> 3.103 ns (0 allocations: 0 bytes)
按预期工作。
我仍然不明白发生了什么。我觉得我必须更好地了解@btime基准测试过程的使用。
顺便说一句,正如我上面所说,我正在 Jupyterlab 上尝试这种基准测试。
回答
您的基准测试的问题,您测试不同的逻辑代码:
2 ^ (integer value)
和
2 ^ (float value)
但最关键的部分,如果a和b没有在循环之前定义,Julia 编译器可能会删除该块。你的表现非常依赖是a和b定义,并且在全球范围内或没有被定义。
电源是代码中耗时的核心部分(不是类型不稳定的部分)。
positive函数Float在您的情况下positive_safe返回,返回 Int)
类似于您的案例(按逻辑)的代码可能如下所示:
# type-unstable
function positive(x)
if x < 0
return 0.0
else
return x
end
end
# type-stable
function positive_safe(x)
if x < 0
return 0.0
else
return Float64(x)
end
end
function test1()
a = 0.0
for n in 1:100_000_000
a += 2^( positive(-n) + 1 )
end
a
end
function test2()
b = 0.0
for n in 1:100_000_000
b += 2^( positive_safe(-n) + 1 )
end
b
end
@btime test1()
@btime test2()
98.045 ms (0 allocations: 0 bytes)
2.0e8
97.948 ms (0 allocations: 0 bytes)
2.0e8
结果几乎相同,因为您的类型不稳定不是这种情况的瓶颈。
如果要测试函数(这与未定义 a/b 时的情况类似):
function test3()
b = 0.0
for n in 1:100_000_000
b += 2^( positive_safe(-n) + 1 )
end
nothing
end
@btime test3()
基准测试将显示结果:
1.611 ns
这不是因为我的笔记本电脑每 1.611 ns 进行了 100_000_000 次迭代,而是因为 Julia 编译器足够聪明,可以理解该test3函数可能被替换为nothing.