Существует множество пакетов Julia, которые позволяют вам записать ваше сокращение в одну простую строку. Вот несколько примеров, основанных на Einsum.jl , OMEinsum.jl и TensorOperations.jl :
using OMEinsum
f_omeinsum(A,B) = ein"ijk,km->ijm"(A,B)
using Einsum
f_einsum(A,B) = @einsum C[i,j,l] := A[i,j,k] * B[k,l]
using TensorOperations
f_tensor(A,B) = @tensor C[i,j,l] := A[i,j,k] * B[k,l]
Помимо этих элегантных (и быстро, см. ниже) версии, вы можете немного улучшить свой код l oop. Вот ваш код, завернутый в функцию, и улучшенная версия с комментариями:
function f(A,B)
C = zeros(8,15,5)
for i in 1:8
for j in 1:15
for k in 1:10
for l in 1:5
C[i,j,l] = A[i,j,:]⋅B[:,l]
end
end
end
end
return C
end
function f_fast(A,B)
# check bounds
n1,n2,n3 = size(A)
m1, m2 = size(B)
@assert m1 == n3
C = zeros(n1,n2,m2)
# * @inbounds to skip boundchecks inside the loop
# * different order of the loops to account for Julia's column major order
# * written out the k-loop (dot product) explicitly to avoid temporary allocations
@inbounds for l in 1:m2
for k in 1:m1
for j in 1:n2
for i in 1:n1
C[i,j,l] += A[i,j,k]*B[k,l]
end
end
end
end
return C
end
Давайте сравним все подходы. Сначала мы проверим правильность:
using Test
@test f(A,B) ≈ f_omeinsum(A,B) # Test passed
@test f(A,B) ≈ f_einsum(A,B) # Test passed
@test f(A,B) ≈ f_tensor(A,B) # Test passed
@test f(A,B) ≈ f_fast(A,B) # Test passed
Теперь давайте проверим, используя BenchmarkTools.jl . Я поместил время на мою машину в качестве комментариев.
using BenchmarkTools
@btime f($A,$B); # 663.500 μs (12001 allocations: 1.84 MiB)
@btime f_omeinsum($A,$B); # 33.799 μs (242 allocations: 20.20 KiB)
@btime f_einsum($A,$B); # 4.200 μs (1 allocation: 4.81 KiB)
@btime f_tensor($A,$B); # 2.367 μs (3 allocations: 4.94 KiB)
@btime f_fast($A,$B); # 7.375 μs (1 allocation: 4.81 KiB)
Как мы видим, все подходы, основанные на einsum / тензорной записи, намного быстрее, чем ваша оригинальная реализация l oop - и только один лайнер! Производительность нашего f_fast
находится на том же уровне, но все еще немного отстает от f_tensor
, который является самым быстрым.
Наконец, давайте go все для производительности, потому что мы можем. Используя мастер dry из LoopVectorization.jl , мы заменим @inbounds
в f_fast
на @avx
(мы называем эту новую версию f_avx
ниже) и автоматически получим еще 2-кратное ускорение относительно с показателем f_tensor
выше:
@test f(A,B) ≈ f_avx(A,B) # Test passed
@btime f_avx($A,$B); # 930.769 ns (1 allocation: 4.81 KiB)
Однако из-за его простоты я все же предпочел бы f_tensor
, если в вашем приложении не учитывается каждая микросекунда.