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

Перемножение матриц на разных вычислителях

В этом примере мы покажем, как изучить производительность кода при помощи инструментария BenchmarkTools и сравним его выполнение на CPU и на GPU.

Подключение к GPU

Наличие у пользователей ресурсов GPU пока остается премиум-функцией платформы Engee. GPU – графическая видеокарта, позволяющая существенно распараллелить выполнение кода путем его выполнения на десятках тысяч вычислительных ядер, находящихся внутри графического сопроцессора.

Основной библиотекой для работы с GPU является CUDA.jl. Установим эту библиотеку, а вместе с ней и инструментарий для оценки производительности кода (пакет BenchmarkTools).

In [ ]:
# Закомментируйте эту строку, если будете устанавливать библиотеки другим образом
Pkg.add( url="https://github.com/JuliaBinaryWrappers/CUDA_Runtime_jll.jl.git" )
Pkg.add( ["CUDA", "cuDNN", "Flux", "BenchmarkTools"] );
Pkg.instantiate()

Перемножение матриц на CPU

Посмотрим, сколько времени в среднем занимает перемножение матриц на обычном процессоре.

In [ ]:
N = 1_000

A = rand(ComplexF64, (N,N))
B = rand(ComplexF64, (N,N))

using BenchmarkTools
@benchmark A*B
Out[0]:
BenchmarkTools.Trial: 11 samples with 1 evaluation per sample.
 Range (minmax):  384.014 ms500.289 ms   GC (min … max): 0.00% … 0.00%
 Time  (median):     496.367 ms                GC (median):    0.00%
 Time  (mean ± σ):   462.194 ms ±  50.123 ms   GC (mean ± σ):  0.00% ± 0.00%

            ▁                                                █  
  ▆▁▁▁▁▁▁▁▁▁█▆▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▆▁█ ▁
  384 ms           Histogram: frequency by time          500 ms <

 Memory estimate: 15.26 MiB, allocs estimate: 2.

Выполнение этой ячейки может занять довольно много времени, поскольку команда @benchmark запускает переданную ей операцию много раз, чтобы сгладить эффект "разогрева", присущий Julia. А еще это уменьшает влияние редких условий, когда код по случайным обстоятельствам показывает минимально возможную производительность.

В данном конкретном случае эксперимент показал, что перемножение матриц комплексных чисел размером 1000 на 1000 в среднем занимает 300 миллисекунд.

Перемножение матриц на GPU

Чтобы перемножить матрицы на видеокарте их нужно перенести на нее, что можно сделать множеством способов. Например, командой A |> gpu, но поскольку в системе может не оказаться GPU, мы проверим конфигурацию вычислительного пространства и выберем доступный вычислитель.

После переноса матрицы Matrix теперь являются объектами CuArray. Их перемножение между собой выполняется без дополнительного кода (благодаря перегрузке оператора умножения). Но умножить матрицу A_gpu на матрицу B мы не сможем без переноса обеих матриц на один и тот же вычислитель (иначе вы получите ошибку KernelError: kernel returns a value of type Union{}).

In [ ]:
using CUDA, Flux

if CUDA.functional()
    A_gpu = A |> gpu
    B_gpu = B |> gpu
    @benchmark A_gpu * B_gpu
end
Out[0]:
BenchmarkTools.Trial: 9878 samples with 1 evaluation per sample.
 Range (minmax):   41.620 μs349.520 ms   GC (min … max): 0.00% … 94.80%
 Time  (median):     492.424 μs                GC (median):    0.00%
 Time  (mean ± σ):   503.189 μs ±   3.522 ms   GC (mean ± σ):  6.78% ±  0.98%

                                                            █    
  ▃▂▂▂▂▁▁▁▁▁▂▁▁▁▁▁▁▁▁▂▁▁▁▁▁▁▁▂▁▁▁▂▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▂▁▁▂▁▂▂▂▂▂▃█ ▂
  41.6 μs          Histogram: frequency by time          509 μs <

 Memory estimate: 1.62 KiB, allocs estimate: 71.

Минимальное время операции на GPU почти в 10 тысяч раз меньше минимального времени вычислений на СPU (41 микросекунда против 384 миллисекунд).

Заключение

Julia позволяет переносить вычисления на GPU, благодаря чему самые разные прикладные вычисления можно многократно ускорить, не переписывая их код. Мы выполнили перемножение матриц на процессоре и на графической видеокарте и определили, что квадратные матрицы размером 1000 на 1000, состоящие из случайных комплексных чисел, графическая видеокарта перемножает в десятки тысяч раз быстрее, чем центральный процессор.