Перемножение матриц на разных вычислителях¶
В этом примере мы покажем, как изучить производительность кода при помощи инструментария BenchmarkTools
и сравним его выполнение на CPU и на GPU.
Подключение к GPU¶
Наличие у пользователей ресурсов GPU пока остается премиум-функцией платформы Engee. GPU – графическая видеокарта, позволяющая существенно распараллелить выполнение кода путем его выполнения на десятках тысяч вычислительных ядер, находящихся внутри графического сопроцессора.
Основной библиотекой для работы с GPU является CUDA.jl
. Установим эту библиотеку, а вместе с ней и инструментарий для оценки производительности кода (пакет BenchmarkTools
).
# Закомментируйте эту строку, если будете устанавливать библиотеки другим образом
Pkg.add( url="https://github.com/JuliaBinaryWrappers/CUDA_Runtime_jll.jl.git" )
Pkg.add( ["CUDA", "cuDNN", "Flux", "BenchmarkTools"] );
Pkg.instantiate()
Перемножение матриц на CPU¶
Посмотрим, сколько времени в среднем занимает перемножение матриц на обычном процессоре.
N = 1_000
A = rand(ComplexF64, (N,N))
B = rand(ComplexF64, (N,N))
using BenchmarkTools
@benchmark A*B
Выполнение этой ячейки может занять довольно много времени, поскольку команда @benchmark
запускает переданную ей операцию много раз, чтобы сгладить эффект "разогрева", присущий Julia. А еще это уменьшает влияние редких условий, когда код по случайным обстоятельствам показывает минимально возможную производительность.
В данном конкретном случае эксперимент показал, что перемножение матриц комплексных чисел размером 1000 на 1000 в среднем занимает 300 миллисекунд.
Перемножение матриц на GPU¶
Чтобы перемножить матрицы на видеокарте их нужно перенести на нее, что можно сделать множеством способов. Например, командой A |> gpu
, но поскольку в системе может не оказаться GPU, мы проверим конфигурацию вычислительного пространства и выберем доступный вычислитель.
После переноса матрицы Matrix
теперь являются объектами CuArray
. Их перемножение между собой выполняется без дополнительного кода (благодаря перегрузке оператора умножения). Но умножить матрицу A_gpu
на матрицу B
мы не сможем без переноса обеих матриц на один и тот же вычислитель (иначе вы получите ошибку KernelError: kernel returns a value of type Union{}
).
using CUDA, Flux
if CUDA.functional()
A_gpu = A |> gpu
B_gpu = B |> gpu
@benchmark A_gpu * B_gpu
end
Минимальное время операции на GPU почти в 10 тысяч раз меньше минимального времени вычислений на СPU (41 микросекунда против 384 миллисекунд).
Заключение¶
Julia позволяет переносить вычисления на GPU, благодаря чему самые разные прикладные вычисления можно многократно ускорить, не переписывая их код. Мы выполнили перемножение матриц на процессоре и на графической видеокарте и определили, что квадратные матрицы размером 1000 на 1000, состоящие из случайных комплексных чисел, графическая видеокарта перемножает в десятки тысяч раз быстрее, чем центральный процессор.