Julia vs.MATLAB-距离矩阵-运行时测试

Julia vs.MATLAB-距离矩阵-运行时测试,matlab,performance,julia,linear-algebra,Matlab,Performance,Julia,Linear Algebra,不久前我开始学习朱莉娅,我决定做一个简单的练习 Julia和Matlab在计算欧几里德函数的简单代码上的比较 一组高维点的距离矩阵 任务很简单,可分为两种情况: 案例1:给定两个n x d矩阵形式的数据集,例如X1和X2,计算X1中每个点与X2中所有点之间的成对欧氏距离。如果X1的大小为n1 x d,而X2的大小为n2 x d,则得到的欧氏距离矩阵d的大小为n1 x n2。在一般情况下,矩阵D是不对称的,对角元素不等于零 案例2:给定一个数据集,以nxd矩阵x的形式,计算x中所有n点之间的成对欧

不久前我开始学习朱莉娅,我决定做一个简单的练习 Julia和Matlab在计算欧几里德函数的简单代码上的比较 一组高维点的距离矩阵

任务很简单,可分为两种情况:

案例1:给定两个n x d矩阵形式的数据集,例如X1和X2,计算X1中每个点与X2中所有点之间的成对欧氏距离。如果X1的大小为n1 x d,而X2的大小为n2 x d,则得到的欧氏距离矩阵d的大小为n1 x n2。在一般情况下,矩阵D是不对称的,对角元素不等于零

案例2:给定一个数据集,以nxd矩阵x的形式,计算x中所有n点之间的成对欧氏距离。所得欧氏距离矩阵d的大小为nxn,对称,主对角线上的元素为零

下面给出了我在Matlab和Julia中对这些函数的实现。请注意,这些实现都不依赖于任何类型的循环,而是依赖于简单的线性代数运算。另外,请注意,使用这两种语言的实现非常相似

在为这些实现运行任何测试之前,我的期望是Julia代码将比Matlab代码快得多,并且有很大的优势。令我惊讶的是,事实并非如此

下面给出了我的实验参数和代码。我的机器是MacBookPro。(2015年年中15英寸),配备2.8 GHz Intel Core i7(四核)和16 GB 1600 MHz DDR3

Matlab版本:R2018a

Julia版本:0.6.3
BLAS:libopenblas(使用64比特动态搜索无亲和力Haswell)
LAPACK:libopenblas64\ubr/> LIBM:libopenlibm
LLVM:libLLVM-3.9.1(ORCJIT,haswell)

结果见下表(1)

表1:计算两个不同数据集之间欧几里德距离矩阵的30次试验的平均时间(以秒为单位)(第1列), 在一个数据集中的所有成对点之间(第2列)


Matlab:2.68(0.12)秒。1.88(0.04)秒

朱莉娅V1:5.38(0.17)秒。4.74(0.05)秒

朱莉娅V2:5.2(0.1)秒


我没想到两种语言之间会有这么大的差异。我希望Julia比Matlab快,或者至少和Matlab一样快。看到Matlab在这个特定任务中比Julia快2.5倍,真是令人惊讶。出于几个原因,我不想根据这些结果得出任何早期结论

首先,虽然我认为我的Matlab实现是最好的,但我想知道我的Julia实现是否是这项任务的最佳实现。我仍在学习Julia,我希望有一个更高效的Julia代码可以为这项任务提供更快的计算时间。特别是,J的主要瓶颈在哪里ulia在这个任务中?或者,为什么Matlab在这种情况下有优势

第二,我当前的Julia软件包基于MacOS的通用和标准BLAS和LAPACK软件包。我想知道JuliaPro与BLAS和LAPACK软件包基于Intel MKL是否会比我当前使用的版本更快。这就是为什么我选择从更多了解StackOverflow的人那里获得一些反馈

第三个原因是我想知道Julia的编译时间是否 包括在表1所示的计时中(第2行和第3行),以及是否有更好的方法来评估函数的执行时间

如果您对我之前的三个问题有任何反馈,我将不胜感激

谢谢大家!

提示:这个问题可能与StackOverflow上的另一个问题重复。但是,这并不完全正确。这个问题有三个方面,正如下面的答案所反映的。首先,是的,问题的一部分与OpenBLAS与MKL的比较有关。第二,事实证明,实现和c如其中一个答案所示,可以改进代码。最后,可以使用BenchmarkTools.jl改进julia代码本身的基准测试

MATLAB

num_trials = 30;
dim = 1000;
n1 = 10000;
n2 = 10000;

T = zeros(num_trials,1);

XX1 = randn(n1,dim);
XX2 = rand(n2,dim);

%%% DIFEERENT MATRICES
DD2ds = zeros(n1,n2);

for (i = 1:num_trials)
  tic;
  DD2ds = distmat_euc2ds(XX1,XX2);
  T(i) = toc;
end

mt = mean(T);
st = std(T);

fprintf(1,'\nDifferent Matrices:: dim: %d, n1 x n2: %d x %d -> Avg. Time %f (+- %f) \n',dim,n1,n2,mt,st);

%%% SAME Matrix 
T = zeros(num_trials,1);

DD1ds = zeros(n1,n1);

for (i = 1:num_trials)
  tic;
  DD1ds = distmat_euc1ds(XX1);
  T(i) = toc;
end

mt = mean(T);
st = std(T);

fprintf(1,'\nSame Matrix:: dim: %d, n1 x n1 : %d x %d -> Avg. Time %f (+- %f) \n\n',dim,n1,n1,mt,st);
distmat_euc2ds.m

function [DD] = distmat_euc2ds (XX1,XX2)
    n1 = size(XX1,1);
    n2 = size(XX2,1);
    DD = sqrt(ones(n1,1)*sum(XX2.^2.0,2)' + (ones(n2,1)*sum(XX1.^2.0,2)')' - 2.*XX1*XX2');
end
function [DD] = distmat_euc1ds (XX)
    n1 = size(XX,1);
    GG = XX*XX';
    DD = sqrt(ones(n1,1)*diag(GG)' + diag(GG)*ones(1,n1) - 2.*GG);
end
distmat_euc1ds.m

function [DD] = distmat_euc2ds (XX1,XX2)
    n1 = size(XX1,1);
    n2 = size(XX2,1);
    DD = sqrt(ones(n1,1)*sum(XX2.^2.0,2)' + (ones(n2,1)*sum(XX1.^2.0,2)')' - 2.*XX1*XX2');
end
function [DD] = distmat_euc1ds (XX)
    n1 = size(XX,1);
    GG = XX*XX';
    DD = sqrt(ones(n1,1)*diag(GG)' + diag(GG)*ones(1,n1) - 2.*GG);
end
朱莉娅

include("distmat_euc.jl")

num_trials = 30;

dim = 1000;
n1 = 10000;
n2 = 10000;

T = zeros(num_trials);

XX1 = randn(n1,dim)
XX2 = rand(n2,dim)

DD = zeros(n1,n2)

# Euclidean Distance Matrix: Two Different Matrices V1
# ====================================================
for i = 1:num_trials
    tic()
    DD = distmat_eucv1(XX1,XX2)
    T[i] = toq();
end

mt = mean(T)
st = std(T)

println("Different Matrices V1:: dim:$dim, n1 x n2: $n1 x $n2 -> Avg. Time $mt (+- $st)")


# Euclidean Distance Matrix: Two Different Matrices V2
# ====================================================
for i = 1:num_trials
    tic()
    DD = distmat_eucv2(XX1,XX2)
    T[i] = toq();
end

mt = mean(T)
st = std(T)

println("Different Matrices V2:: dim:$dim, n1 x n2: $n1 x $n2 -> Avg. Time $mt (+- $st)")


# Euclidean Distance Matrix: Same Matrix V1
# =========================================
for i = 1:num_trials
    tic()
    DD = distmat_eucv1(XX1)
    T[i] = toq();
end

mt = mean(T)
st = std(T)

println("Same Matrix V1:: dim:$dim, n1 x n2: $n1 x $n2 -> Avg. Time $mt (+- $st)")
distmat_euc.jl

function distmat_eucv1(XX1::Array{Float64,2},XX2::Array{Float64,2})
    (num1,dim1) = size(XX1)
    (num2,dim2) = size(XX2)

    if (dim1 != dim2)
        error("Matrices' 2nd dimensions must agree!")
    end

    DD = sqrt.((ones(num1)*sum(XX2.^2.0,2)') +
         (ones(num2)*sum(XX1.^2.0,2)')' - 2.0.*XX1*XX2');
end


function distmat_eucv2(XX1::Array{Float64,2},XX2::Array{Float64,2})
    (num1,dim1) = size(XX1)
    (num2,dim2) = size(XX2)

    if (dim1 != dim2)
        error("Matrices' 2nd dimensions must agree!")
    end

    DD = (ones(num1)*sum(Base.FastMath.pow_fast.(XX2,2.0),2)') +
         (ones(num2)*sum(Base.FastMath.pow_fast.(XX1,2.0),2)')' -
         Base.LinAlg.BLAS.gemm('N','T',2.0,XX1,XX2);

    DD = Base.FastMath.sqrt_fast.(DD)
end


function distmat_eucv1(XX::Array{Float64,2})
    n = size(XX,1)
    GG = XX*XX';
    DD = sqrt.(ones(n)*diag(GG)' + diag(GG)*ones(1,n) - 2.0.*GG)
end

第一个问题:如果我像这样重新编写julia距离函数:

function dist2(X1::Matrix, X2::Matrix)
    size(X1, 2) != size(X2, 2) && error("Matrices' 2nd dimensions must agree!")
    return sqrt.(sum(abs2, X1, 2) .+ sum(abs2, X2, 2)' .- 2 .* (X1 * X2'))
end
我将执行时间减少了40%以上

对于单个数据集,您可以保存更多,如下所示:

function dist2(X::Matrix)
    G = X * X'
    dG = diag(G)
    return sqrt.(dG .+ dG' .- 2 .* G)
end
第三个问题:您应该使用BenchmarkTools.jl进行基准测试,并像这样执行基准测试(记住
$
变量插值):

此外,您不应该使用浮动来执行幂运算,例如:
X.^2.0
。写入
X.^2
更快,同样正确

对于乘法,
2.0.*X
2.*X
之间没有速度差异,但是您仍然应该更喜欢使用整数,因为它更通用。例如,如果
X
具有
Float32
元素,则与
2.0
相乘时,将数组提升为
Float64
s使用
2
将保留eltype


最后,请注意,在新版本的Matlab中,您也可以通过简单地将
Mx1
数组与
1xN
数组相加来获得广播行为。无需首先将它们与
一相乘(…)

为什么您希望Julia的速度明显更快?MatLab被明确设计为在矩阵代数方面更快。TBH我怀疑您在这里计时的大部分内容是openblas和intel mkl blas在您的特定体系结构上的差异。(我认为MatLab使用内存中的mkl)。但是,如果您想深入研究它,请