AnyMath 文档
Notebook

Julia、MATLAB和Python的性能对比

导言

在选择用于工程计算的工具时,不仅方便性和库的数量很重要,而且计算的速度也很重要。

MATLAB被认为是科学计算的标准,Python由于NumPy和SciPy生态系统而变得广泛,而Julia是专门为高性能计算而创建的。

在这个例子中,我们将比较Julia,MATLAB和Python在几个典型工程任务上的性能。:

*矢量计算;

*矩阵运算;

*求解线性方程组;

*快速傅立叶变换(FFT)计算;

*数值积分;

*求数π的蒙特卡洛方法;

*求解热导率方程。

在所有测试中,我们将测量执行时间并比较结果。

为了客观起见,我们将使用内置工具测量时间。:

@belapsed在朱莉娅;

tic … toc 在MATLAB中;

time.perf_counter()在Python中。

连接图书馆

要运行测试,我们需要以下库:

  • MATLAB -从Julia调用MATLAB;
  • PyCall -与Python的交互;
  • LinearAlgebra-用于矩阵运算;
  • FFTW-用于快速傅立叶变换;
  • BenchmarkTools -精确的时间测量。
In [ ]:
neededLibs = ["MATLAB", "PyCall", "BenchmarkTools", "LinearAlgebra", "FFTW"]
for lib in neededLibs
    try
        eval(Meta.parse("using $lib"))
    catch ex
        Pkg.add(lib)
        eval(Meta.parse("using $lib"))
    end
end

Python模块的准备

我们将连接必要的Python库。

In [ ]:
np = pyimport("numpy")
scipy_integrate = pyimport("scipy.integrate")
Out[0]:
PyObject <module 'scipy.integrate' from '/opt/python3.11/lib/python3.11/site-packages/scipy/integrate/__init__.py'>

测试1。 矢量计算

计算表达式

为1000万个元素的数组。

这个表达式被特别选择作为密集的零碎计算的一个例子.

In [ ]:
N = 10^7
x = rand(N)
Out[0]:
10000000-element Vector{Float64}:
 0.3209199928094635
 0.1865690245097057
 0.027519003346574533
 0.1035696000771914
 0.7617551461014622
 0.3285472660096638
 0.8569073684267615
 0.4177394290269377
 0.403488227552495
 0.6147429411864976
 0.8956454977439492
 0.9337120189173626
 0.9543570461710522
 ⋮
 0.6089946931248793
 0.9624634504691789
 0.6232579150388496
 0.49009958177103763
 0.774540432270273
 0.7570462764844246
 0.8760435688206902
 0.3420754138897466
 0.6462666791429025
 0.6322249655087943
 0.21683558167577033
 0.49816246221767047

茱莉亚

In [ ]:
julia_time = @belapsed sin.($x).^2 .+ cos.($x).^2
println("Julia: ", julia_time, " 秒数")
Julia: 0.213971722 секунд

MATLAB

让我们将数据传输到MATLAB并测量执行时间。

In [ ]:
@mput x

mat"""
tic;
y = sin(x).^2 + cos(x).^2;
matlab_time = toc;
"""
@mget matlab_time
println("MATLAB: ", matlab_time, " 秒数")
MATLAB: 0.279133

Python

In [ ]:
py_x = PyObject(x)

py"""
import time
import numpy as np

start = time.perf_counter()

y = np.sin($py_x)**2 + np.cos($py_x)**2

python_time = time.perf_counter() - start
"""

python_time = py"python_time"

println("Python: ", python_time, " 秒数")
Python: 0.3749271952547133 секунд

Julia演示了由于JIT编译而产生的最低执行时间,该编译生成的机器代码没有解释的开销。

测试2。 大矩阵的乘积

我们将生成两个随机的5000×5000矩阵并执行它们的乘法。

这是最常见的工程测试之一。

In [ ]:
A = rand(5000, 5000)
B = rand(5000, 5000)
Out[0]:
5000×5000 Matrix{Float64}:
 0.247848   0.275075   0.103041   …  0.955899   0.442469  0.142161
 0.829696   0.570888   0.779525      0.803322   0.261279  0.416469
 0.302144   0.470861   0.929515      0.879647   0.463827  0.659836
 0.622374   0.715513   0.749483      0.790127   0.532119  0.0226104
 0.22009    0.807178   0.496316      0.0163167  0.143018  0.68864
 0.0202647  0.932929   0.196466   …  0.137595   0.750025  0.830217
 0.737694   0.466092   0.52982       0.103175   0.552223  0.872671
 0.182882   0.327355   0.803583      0.274724   0.901884  0.520132
 0.684589   0.0492455  0.0692971     0.276476   0.475649  0.246488
 0.161581   0.674723   0.786201      0.141705   0.594002  0.243299
 0.834808   0.484064   0.874741   …  0.366673   0.909212  0.195076
 0.78583    0.849583   0.549752      0.937795   0.488873  0.396699
 0.482646   0.689754   0.191593      0.287839   0.90025   0.0900362
 ⋮                                ⋱                       
 0.0490212  0.362037   0.0800845     0.814074   0.688598  0.0435109
 0.646176   0.398827   0.342848      0.520501   0.293243  0.948626
 0.0814196  0.786904   0.694019   …  0.717057   0.894803  0.214561
 0.205205   0.599291   0.604951      0.284944   0.892169  0.670361
 0.746486   0.425566   0.838346      0.281594   0.641695  0.319097
 0.160965   0.64698    0.371449      0.730004   0.395501  0.110665
 0.64245    0.0561828  0.924955      0.713472   0.702081  0.368934
 0.331956   0.273443   0.749144   …  0.622444   0.8223    0.736889
 0.494684   0.795712   0.231628      0.986563   0.707547  0.639998
 0.0958006  0.190157   0.610949      0.561864   0.527178  0.922572
 0.188368   0.56108    0.954863      0.0105769  0.689971  0.779974
 0.277134   0.151909   0.227931      0.534004   0.353346  0.575454

茱莉亚

In [ ]:
julia_time = @belapsed $A * $B
println("Julia: ", julia_time, " 秒数")
Julia: 2.176358714 секунд

Matlab

In [ ]:
@mput A B

mat"""
tic;
C = A * B;
matlab_time = toc;
"""
@mget matlab_time

println("Matlab: ", matlab_time, " 秒数")
Matlab: 2.319368 секунд

Python

In [ ]:
pyA = PyObject(A)
pyB = PyObject(B)

py"""
tmp = $pyA @ $pyB
"""

py"""
import timeit
python_time = timeit.timeit( lambda: $pyA @ $pyB, number=1)
"""
python_time = py"python_time"
println("Python: ", python_time, " 秒数")
Python: 7.0526929018087685 секунд

Julia和MATLAB的结果接近,而Python在组织调用的速度上明显逊色。

测试3。 求解线性方程组

让我们解决方程组:

对于矩阵 尺寸为8000×8000。

In [ ]:
A = rand(8000, 8000)
b = rand(8000)

茱莉亚

In [ ]:
julia_time = @belapsed A\b
println("Julia: ", julia_time, " 秒数")
Julia: 5.758521299 секунд

MATLAB

In [ ]:
@mput A b

mat"""
tic;
x = A\\b;
matlab_time = toc;
"""
@mget matlab_time
println("Matlab: ", matlab_time, " 秒数")
Matlab: 6.065439 секунд

Python

In [ ]:
pyA = PyObject(A)
pyb = PyObject(b)

py"""
import time
import numpy as np
start = time.perf_counter()
x = np.linalg.solve($pyA, $pyb)
python_time = time.perf_counter() - start
"""
python_time = py"python_time"
println("Python: ", python_time, " 秒数")
Python: 11.172098383773118 секунд

Julia和MATLAB的性能相同;Python在矩阵乘法方面略逊一筹。

测试4。 快速傅立叶变换

让我们计算长度为1000万个样本的信号的FFT。

In [ ]:
signal = rand(10^7)
Out[0]:
10000000-element Vector{Float64}:
 0.19964052302019852
 0.5334663195022673
 0.9121321253899346
 0.04129394526409169
 0.6589395955777433
 0.5388352658418127
 0.26895775008531053
 0.5829179391433303
 0.6515824751637956
 0.836612931381444
 0.6559235223843917
 0.6026254909782042
 0.5890734109652789
 ⋮
 0.6905007831051632
 0.2881370605087836
 0.5270581989024726
 0.9312542593606681
 0.5850647418223617
 0.11650802263000237
 0.9507322240086514
 0.10666018251459808
 0.21469656971547735
 0.4365831131657778
 0.5078941848918891
 0.657329997060914

茱莉亚

In [ ]:
fft(signal)
julia_time = @belapsed fft(signal)
println("Julia: ", julia_time, " 秒数")
Julia: 1.01158086 секунд

Matlab

In [ ]:
@mput signal
mat"""
tic;
Y = fft(signal);
matlab_time = toc;
"""
@mget matlab_time
println("Matlab: ", matlab_time, " 秒数")
Matlab: 0.486747 секунд

Python

In [ ]:
py_signal = PyObject(signal)

py"""
import time
import numpy as np

start = time.perf_counter()

Y = np.fft.fft($py_signal)

python_time = time.perf_counter() - start
"""

python_time = py"python_time"

println("Python: ", python_time, " 秒数")
Python: 2.5993731832131743 секунд

在这个测试中,MATLAB显示了最好的结果;Julia略逊一筹,Python显示可以接受,但速度显着降低。

测试5。 数值积分

设置任务

计算积分

在1亿点的网格上使用梯形方法。

In [ ]:
N = 10^8
Out[0]:
100000000

茱莉亚

让我们定义积分函数。

In [ ]:
function integrate_julia(N)
    h = 1000 / N
    s = 0.0
    @inbounds for i in 1:N
        x = i*h
        s += sin(x)*cos(x)*exp(-x/1000)
    end
    return s*h
end
Out[0]:
integrate_julia (generic function with 1 method)
In [ ]:
julia_time = @belapsed integrate_julia(N)
println("Julia: ", julia_time, " 秒数")
Julia: 2.705959406 секунд

MATLAB

In [ ]:
@mput N

mat"""
N = double(N);
tic;

h = 1000 / N;
s = 0.0;

for i = 1:N
    x = i * h;
    s = s + sin(x) * cos(x) * exp(-x / 1000);
end

result = s * h;

matlab_time = toc;
"""

@mget matlab_time

println("MATLAB: ", matlab_time, " 秒数")
MATLAB: 5.584325 секунд

Python

In [ ]:
py"""
import math
import time

def integrate_python(N):

    h = 1000/N
    s = 0.0

    for i in range(N):
        x = i*h
        s += math.sin(x)*math.cos(x)*math.exp(-x/1000)

    return s*h

start = time.perf_counter()

integrate_python(100000000)

python_time = time.perf_counter() - start
"""
python_time = py"python_time"

println("Python: ", python_time, " 秒数")
Python: 25.88057485362515 секунд

Julia几乎是MATLAB的两倍,比Python好一个数量级,这清楚地表明了编译顺序循环的效率。

测试6。 计算数pi的蒙特卡洛方法

让我们在正方形内生成1亿个随机点,并确定它们中有多少属于单位圆内。

这是一个典型的例子,在一个循环中需要很多简单的计算。

茱莉亚

In [ ]:
function montecarlo_pi(N)
    inside = 0

    @inbounds for i in 1:N
        x = rand()
        y = rand()

        if x*x + y*y <= 1.0
            inside += 1
        end
    end

    return 4 * inside / N
end

julia_time = @belapsed montecarlo_pi(100_000_000)
println("Julia: ", julia_time, " 秒数")
Julia: 0.328965659 секунд

MATLAB

In [ ]:
mat"""
tic
inside = 0;

for i = 1:100000000
    x = rand();
    y = rand();
    if x*x + y*y <= 1
        inside = inside + 1;
    end

end

pi_est = 4 * inside / 100000000;

matlab_time = toc;
"""

@mget matlab_time

println("MATLAB: ", matlab_time, " 秒数")
MATLAB: 9.227858 секунд

Python

In [ ]:
py"""
import random
import time

def montecarlo_pi(N):

    inside = 0

    for _ in range(N):

        x = random.random()
        y = random.random()

        if x*x + y*y <= 1.0:
            inside += 1

    return 4.0 * inside / N


start = time.perf_counter()

montecarlo_pi(100_000_000)

python_time = time.perf_counter() - start
"""

python_time = py"python_time"

println("Python: ", python_time, " 秒数")
Python: 15.37143736006692 секунд

在对随机数生成器进行密集调用的循环中,Julia的运行速度比MATLAB和Python快一个数量级,从而消除了迭代的开销。

测试7。 求解热方程

让我们为热方程执行1000个时间步长

在10万个节点的网格上。

这是典型的工程任务之一。

In [ ]:
u = rand(10^5)
steps = 10^3
Out[0]:
1000

茱莉亚

In [ ]:
function heat1d(u, α, steps)
    tmp = similar(u)
    for n in 1:steps
        @inbounds for i in 2:length(u)-1
            tmp[i] =
                u[i] +
                α*(u[i+1] - 2u[i] + u[i-1])
        end
        u, tmp = tmp, u
    end
    return u
end

julia_time = @belapsed heat1d(copy($u), 0.1, steps)
println("Julia: ", julia_time, " 秒数")
Julia: 0.071473353 секунд

Matlab

In [ ]:
@mput u
@mput steps
mat"""
u = double(u);

tmp = zeros(size(u));

tic

for n = 1:steps
    for i = 2:length(u)-1
        tmp(i) = u(i) + 0.1*(u(i+1) - 2*u(i) + u(i-1));
    end

    t = u;
    u = tmp;
    tmp = t;

end

matlab_time = toc;
"""

@mget matlab_time

println("MATLAB: ", matlab_time, " 秒数")
MATLAB: 0.632743 секунд

Python

In [ ]:
pyu = PyObject(u)
psteps = PyObject(steps)
py"""
import numpy as np
import time

def heat1d(u, alpha, steps):

    tmp = np.empty_like(u)

    for _ in range(steps):

        for i in range(1, len(u)-1):

            tmp[i] = (
                u[i]
                + alpha*(u[i+1] - 2*u[i] + u[i-1])
            )

        u, tmp = tmp, u

    return u


start = time.perf_counter()

heat1d($pyu, 0.1, $psteps)

python_time = time.perf_counter() - start
"""

python_time = py"python_time"

println("Python: ", python_time, " 秒数")
Python: 76.2125775879249 секунд

Julia的性能优于MATLAB几乎一个数量级,Python超过三个数量级,揭示了嵌套循环编译的根本优势。

结果

让我们与计算时间的测量结果做一个比较表。

测试

朱莉娅时间,c

Matlab时间,c

Python时间,c

矢量计算

0.2139

0.2791

0.3749

大矩阵的乘积

2.1764

2.3193

7.0526

求解线性方程组

5.7585

6.0654

11.1721

快速傅立叶变换

1.0116

0.4867

2.5993

数值积分

2.7059

5.5843

25.8805

计算数π的蒙特卡洛方法

0.3290

9.2279

15.3714

求解热方程

0.0715

0.6327

76.2126

根据该组测试,Julia演示了最佳的加权平均性能,在某些操作上显示出与MATLAB的奇偶校验以及在具有密集周期的任务中的多重优越性。 尽管Python具有高度优化的库,但在需要非平凡逻辑的地方,它在系统上是逊色的。

结论

测试证明了Julia在工程计算方面的高效率。 在归结为调用优化库(矩阵乘法,SLOUGH求解)的任务中,Julia处于MATLAB级别,领先于Python。 关键优势体现在无法简化为纯矢量化的场景中,例如数值积分,蒙特卡洛方法和进化方案,其中与MATLAB的差距达到一个数量级,与Python的差距—两个或三个数量级。

从工程的角度来看,Julia在AnyMath中特别有吸引力:与专有MATLAB不同,Julia是一种开放技术,与AnyMath结合使用,提供计算的可重复性和缩放的灵活性;此外,python的基本限制被 因此,对于广泛的计算密集型工程任务,Julia提供了性能,成本效益和科学再现性的最佳平衡。