Julia vs Mathematica:数值积分性能
朱莉娅,我是新来的,有个问题要问你 我通过移植我的Mathematica和Python代码(主要是物理学中的科学计算等)来教自己一些Julia,看看是什么。 到目前为止,一切都很顺利。而且很快。直到现在 现在,我正在模拟一个基本的锁定放大器,本质上,它接受一个可能非常复杂的时间相关信号,Julia vs Mathematica:数值积分性能,julia,wolfram-mathematica,numerical-integration,Julia,Wolfram Mathematica,Numerical Integration,朱莉娅,我是新来的,有个问题要问你 我通过移植我的Mathematica和Python代码(主要是物理学中的科学计算等)来教自己一些Julia,看看是什么。 到目前为止,一切都很顺利。而且很快。直到现在 现在,我正在模拟一个基本的锁定放大器,本质上,它接受一个可能非常复杂的时间相关信号,Uin(t),并产生一个输出,Uout(t),在某个参考频率fref下锁相(也就是说,它突出显示了Uin(t)的分量),它与参考正弦波具有一定的相位关系)描述无关紧要,重要的是它基本上是通过计算积分来实现的(为了
Uin(t)
,并产生一个输出,Uout(t)
,在某个参考频率fref
下锁相(也就是说,它突出显示了Uin(t)的分量)
,它与参考正弦波具有一定的相位关系)描述无关紧要,重要的是它基本上是通过计算积分来实现的(为了清楚起见,我实际上省略了相位):
因此,我在Mathematica和Julia中对此进行了阐述和测试:
我在t=0
时,在Uin(t)
中定义一个模型,传递一些参数值,然后构建一个Uout(t)
数组,范围为fref
- 朱莉娅:我用这个软件包进行数值积分
T = 0.1 f = 100. Uin(t) = sin(2pi * f * t) + sin(2pi * 2f *t) Uout(t, fref)::Float64 = quadgk(s -> Uin(s) * sin(2pi * fref * s), t-T, t, rtol=1E-3)[1]/T frng = 80.:1.:120. print(@time broadcast(Uout, 0., frng))
- Mathematica
f
和T
设置为const
可以将Julia的时间减少到8-10秒,但是f
和T
在实际程序中不能是const
S。除此之外,我还有什么明显的遗漏吗
编辑2020年2月2日:
速度减慢似乎是由于当值接近零时,算法试图寻找精度,例如,见下:对于fref=95,计算需要1整秒(!),而对于相邻频率值,它会立即计算(返回的结果是(res,error)的元组)。似乎quadgk函数在非常小的值时暂停):
注:这与我要求的公差无关。此外,Mathematica通常会在默认情况下达到机器精度公差,而在接近零时速度会有所减慢,numpy/scipy只是在整个过程中快速运行,但产生的结果不如Mathematica精确(在默认设置下;没有对此进行太多研究)。我看到的代码的第一个问题是它的类型不稳定。这是因为您使用的是全局变量(请参见性能提示第一条): 编译器无法知道在函数中使用的
f
和T
的类型,因此无法进行有效编译。这也是为什么当您将它们标记为const时,性能会提高:现在编译器可以保证它们不会更改其类型,因此它可以高效地编译这两个函数
如何看到您的代码不稳定
如果使用宏@code\u warntype
运行第一个函数,如下所示:
@code_warntype Uin(0.1,f)
julia> @code_warntype Uin(0.1)
Variables
#self#::Core.Compiler.Const(Uin, false)
t::Float64
Body::Any
1 ─ %1 = (2.0 * Main.pi)::Core.Compiler.Const(6.283185307179586, false)
│ %2 = (%1 * Main.f * t)::Any
│ %3 = Main.sin(%2)::Any
│ %4 = (2.0 * Main.pi)::Core.Compiler.Const(6.283185307179586, false)
│ %5 = (2.0 * Main.f)::Any
│ %6 = (%4 * %5 * t)::Any
│ %7 = Main.sin(%6)::Any
│ %8 = (%3 + %7)::Any
└── return %8
您将看到如下输出:
@code_warntype Uin(0.1,f)
julia> @code_warntype Uin(0.1)
Variables
#self#::Core.Compiler.Const(Uin, false)
t::Float64
Body::Any
1 ─ %1 = (2.0 * Main.pi)::Core.Compiler.Const(6.283185307179586, false)
│ %2 = (%1 * Main.f * t)::Any
│ %3 = Main.sin(%2)::Any
│ %4 = (2.0 * Main.pi)::Core.Compiler.Const(6.283185307179586, false)
│ %5 = (2.0 * Main.f)::Any
│ %6 = (%4 * %5 * t)::Any
│ %7 = Main.sin(%6)::Any
│ %8 = (%3 + %7)::Any
└── return %8
所有这些any
都告诉您,编译器在任何步骤都不知道输出的类型
如何修复
您可以重新定义函数,将f
和T
作为变量:
Uin(t,f) = sin(2.0pi * f * t) + sin(2.0pi * 2.0f *t)
Uout(t, fref,f,T)::Float64 = quadgk(s -> Uin(s,f) * sin(2pi * fref * s), t-T, t, rtol=1E-3)[1]/T
通过这些重新定义,您的代码运行得更快。如果您尝试使用@code\u warntype
检查它们,您将看到编译器现在正确地推断出所有内容的类型
有关进一步的性能改进,您可以查看
特别是,通常建议的测量性能而不是使用
@time
的方法是来自包BenchmarkTools
的@btime
。这是因为当运行@time
时,您也在测量编译时间(另一个选项是运行@time两次-第二个测量值是正确的,因为所有函数都有机会编译)。您可以做很多事情来进一步加快编译速度。改变集成顺序确实有点帮助,使用Float32而不是Float64做了一个小改进,使用@fastmath
做了进一步的小改进。您还可以使用SLEEFPirates.sin\u fast
using QuadGK, ChangePrecision
@changeprecision Float32 begin
T = 0.1
f = 100.
@inline @fastmath Uin(t,f) = sin(2pi * f * t) + sin(2pi * 2f *t)
@fastmath Uout(t, fref,f,T) = first(quadgk(s -> Uin(s,f) * sin(2pi * fref * s), t-T, t, rtol=1e-2, order=10))/T
frng = 80.:1.:120.
@time Uout.(0., frng, f, T)
end
您的问题与容错能力的选择有关。1e-3的相对误差听起来不算太大,但实际上,当积分接近零时,误差就很大了。特别是,当fref=80.0时(以及85、90、95、而不是100、105等),会发生这种情况: 引用
quadgk的docstring
:
(请注意,在以下情况下,指定正原子非常有用:
范数(I)可能为零。)
让我们尝试设置一个绝对公差,例如1e-6,并进行比较。首先是代码(使用@ARamirez中的代码):
然后是基准测试(为此使用基准测试工具)
好的,速度快了5000倍。可以吗?您可以将变量括起来,而不是全局变量。我强烈建议您阅读手册的性能提示部分:。特别是,请阅读性能提示第一条。@chrisrackaukas?在使用它们作为参数时?是的,请参见以下ARamirez的回复。@DNF我做了:(.但是,正如我所说,这些不能是常数,在REPL中它们是全局的。但是,我也在代码中这样做了,在代码中我把它们包含在函数的局部范围内,所以可能不是全局的。我最终会弄明白的,谢谢。在Mathematica中公差和位宽度是一样的吗?谢谢。好的,我想编译器会推断出来的f和T是浮点数,因为我已经给它们赋值了,但这是有意义的,我应该有che
julia> Uout(0.0, 80.0, f, T)
1.2104987553880609e-16
Uin(t, f) = sin(2π * f * t) + sin(4π * f * t)
function Uout(t, fref, f , T)
quadgk(s -> Uin(s, f) * sin(2π * fref * s), t-T, t, rtol=1e-3)[1]/T
end
function Uout_new(t, fref, f , T) # with atol
quadgk(s -> Uin(s, f) * sin(2π * fref * s), t-T, t, rtol=1e-3, atol=1e-6)[1]/T
end
using BenchmarkTools
T = 0.1
f = 100.0
freqs = 80.0:1.0:120.0
@btime Uout.(0.0, $freqs, $f, $T);
6.302 s (53344283 allocations: 1.09 GiB)
@btime Uout_new.(0.0, $freqs, $f, $T);
1.270 ms (11725 allocations: 262.08 KiB)