Документация Engee
Notebook

Сравнение производительности Julia, MATLAB и Python

Введение

При выборе инструмента для инженерных расчётов важны не только удобство и количество библиотек, но и скорость выполнения вычислений.

MATLAB считается стандартом для научных расчётов, Python получил широкое распространение благодаря экосистеме NumPy и SciPy, а Julia была создана специально для высокопроизводительных вычислений.

В данном примере мы сравним производительность Julia, MATLAB и Python на нескольких типичных инженерных задачах:

  • векторные вычисления;

  • матричные операции;

  • решение системы линейных уравнений;

  • вычисление быстрого преобразования Фурье (FFT);

  • численное интегрирование;

  • Метод Монте-Карло для нахождения числа π;

  • Решение уравнения теплопроводности.

Во всех тестах измерим время выполнения и сравним результаты.

Для объективности, измерение времени будем проводить с помощью встроенных инструментов:

@belapsedв Julia;

tictoc в MATLAB;

time.perf_counter()в Python.

Подключение библиотек

Для проведения тестов, нам потребуются следующие библиотеки:

  • MATLAB — вызов MATLAB из Julia;
  • PyCall — взаимодействие с Python;
  • LinearAlgebra— для матричных операций;
  • FFTW— для быстрого преобразования Фурье;
  • BenchmarkTools — точное измерение времени.
In [ ]:
using MATLAB, PyCall, BenchmarkTools, LinearAlgebra, FFTW

Подготовка 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. Векторные вычисления

Вычислим выражение

для массива из 10 миллионов элементов.

Данное выражение специально выбрано как пример интенсивных поэлементных вычислений.

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

Julia

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

Julia

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)

Julia

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. Быстрое преобразование Фурье

Вычислим FFT сигнала длиной 10 миллионов отсчётов.

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

Julia

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. Численное интегрирование

Постановка задачи

Вычислим интеграл

методом трапеций на сетке из 100 миллионов точек.

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

Julia

Определим функцию интегрирования.

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. Метод Монте-Карло для вычисления числа π

Сгенерируем 100 миллионов случайных точек внутри квадрата и определим, сколько из них попадает в единичную окружность.

Это классический пример, где требуется огромное количество простых вычислений в цикле.

Julia

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 временных шагов для уравнения теплопроводности

на сетке из 100 тысяч узлов.

Это одна из типичных инженерных задач.

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

Julia

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 — более чем на три порядка, раскрывая фундаментальное преимущество компиляции вложенных циклов.

Итоги

Составим сравнительную таблицу с результатами измерений времени вычислений.

Тест

Время Julia, 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 для инженерных вычислений. В задачах, сводящихся к вызову оптимизированных библиотек (умножение матриц, решение СЛАУ), Julia находится на уровне MATLAB, опережая Python. Ключевое преимущество раскрывается в сценариях, не сводимых к чистой векторизации, — численном интегрировании, методе Монте-Карло, эволюционных схемах, — где разрыв с MATLAB достигает порядка, а с Python — двух-трёх порядков.

С инженерной точки зрения Julia в Engee особенно привлекателен: в отличие от проприетарного MATLAB, Julia — открытая технология, что в сочетании с Engee обеспечивает воспроизводимость расчётов и гибкость масштабирования; кроме того, преодолевается фундаментальное ограничение Python — низкая скорость исполнения циклического кода, критичная для имитационного моделирования. Таким образом, для широкого спектра вычислительно-ёмких инженерных задач Julia обеспечивает оптимальный баланс производительности, экономической эффективности и научной воспроизводимости.