Performance matlab矩阵运算速度

Performance matlab矩阵运算速度,performance,matlab,matrix,matrix-multiplication,Performance,Matlab,Matrix,Matrix Multiplication,我被要求让一些MATLAB代码运行得更快,遇到了一些我觉得奇怪的事情 在其中一个函数中有一个循环,我们将一个3x1向量(我们称之为x)-一个3x3矩阵(我们称之为a)-和x的转置相乘,得到一个标量。该代码包含一整套元素对元素的乘法和加法,非常麻烦: val = x(1)*A(1,1)*x(1) + x(1)*A(1,2)*x(2) + x(1)*A(1,3)*x(3) + ... x(2)*A(2,1)*x(1) + x(2)*A(2,2)*x(2) + x(2)*A(2,3)*x(

我被要求让一些MATLAB代码运行得更快,遇到了一些我觉得奇怪的事情

在其中一个函数中有一个循环,我们将一个3x1向量(我们称之为
x
)-一个3x3矩阵(我们称之为
a
)-和
x
的转置相乘,得到一个标量。该代码包含一整套元素对元素的乘法和加法,非常麻烦:

val = x(1)*A(1,1)*x(1) + x(1)*A(1,2)*x(2) + x(1)*A(1,3)*x(3) + ...
      x(2)*A(2,1)*x(1) + x(2)*A(2,2)*x(2) + x(2)*A(2,3)*x(3) + ... 
      x(3)*A(3,1)*x(1) + x(3)*A(3,2)*x(2) + x(3)*A(3,3)*x(3);
我想我应该用以下方式来取代它:

val = x*A*x';
令我惊讶的是,它的运行速度明显慢了(慢了4-5倍)。只是因为向量和矩阵太小,以致于MATLAB的优化不适用吗?

编辑:我改进了测试,以提供更精确的时间。我还优化了展开版本,它现在比我最初拥有的要好得多,但随着大小的增加,矩阵乘法的速度要快得多

EDIT2:为了确保JIT编译器正在处理展开的函数,我修改了代码,将生成的函数作为M文件写入。此外,现在可以认为比较是公平的,因为这两种方法都是通过传递函数句柄TIMEIT来计算的:
TIMEIT(@myfunc)


对于合理的大小,我不相信您的方法比矩阵乘法更快。让我们比较一下这两种方法

我正在使用符号数学工具箱来帮助我得到方程的“展开”形式
x'*A*x
(尝试手动乘以20x20矩阵和20x1向量!):

我试图通过避免求幂来优化生成的函数(我们更喜欢
x*x
而不是
x^2
)。我还将下标转换为线性索引(
A(9)
,而不是
A(3,3)
)。因此,对于
n=3
我们得到了与您相同的方程式:

>> s
s =
A(1)*(x(1)*x(1)) + A(5)*(x(2)*x(2)) + A(9)*(x(3)*x(3)) + 
A(4)*x(1)*x(2) + A(7)*x(1)*x(3) + A(2)*x(1)*x(2) + 
A(8)*x(2)*x(3) + A(3)*x(1)*x(3) + A(6)*x(2)*x(3)
考虑到上面构造M函数的方法,我们现在对其进行各种大小的评估,并将其与表单进行比较(我将其放在一个单独的函数中,以考虑函数调用开销)。我正在使用该功能而不是
tic/toc
,以获得更准确的计时。为了进行公平比较,每个方法都实现为一个M文件函数,该函数将所有需要的变量作为输入参数传递

function results = testMatrixMultVsUnrolled()
    % vector/matrix size
    N_vec = 2:50;
    results = zeros(numel(N_vec),3);
    for ii = 1:numel(N_vec);
        % some random data
        N = N_vec(ii);
        x = rand(N,1); A = rand(N,N);

        % matrix multiplication
        f = @matMult;
        results(ii,1) = timeit(@() feval(f, A,x));

        % unrolled equation
        f = buildUnrolledFunction(N);
        results(ii,2) = timeit(@() feval(f, A,x));

        % check result
        results(ii,3) = norm(matMult(A,x) - f(A,x));
    end

    % display results
    fprintf('N = %2d: mtimes = %.6f ms, unroll = %.6f ms [error = %g]\n', ...
        [N_vec(:) results(:,1:2)*1e3 results(:,3)]')
    plot(N_vec, results(:,1:2)*1e3, 'LineWidth',2)
    xlabel('size (N)'), ylabel('timing [msec]'), grid on
    legend({'mtimes','unrolled'})
    title('Matrix multiplication: $$x^\mathsf{T}Ax$$', ...
        'Interpreter','latex', 'FontSize',14)
end

function v = matMult(A,x)
    v = x.' * A * x;
end
结果是:

0.0767
1.6988

6.1975
7.9353

在小尺寸情况下,这两种方法的性能有些相似。尽管适用于
N10
。我比较了嵌入式代码/显式编写与矩阵乘法,与@DennisJaheruddin的建议类似。委员会:

。。。对于展开的版本,情况只会变得更糟。正如我之前所说,MATLAB是经过解释的,因此解析代码的成本开始显示在如此庞大的文件中

在我看来,在进行了一百万次迭代之后,我们最多只获得了1秒,我认为这并不能证明所有的麻烦和攻击都是合理的,因为我们使用的是可读性和简洁性更高的
v=x'*a*x
。因此,代码中还有其他地方可以改进,而不是专注于已经优化的操作,如矩阵乘法

在MATLAB中(这是MATLAB最擅长的!)。一旦您获得了足够大的数据,它就真的大放异彩了(如下所示):


@Amro给出了一个简洁的答案,我同意,一般来说,您不应该费心写出显式计算,而只是在代码中的任何地方使用矩阵乘法

然而,如果您的矩阵足够小,并且您确实需要计算几十亿次,那么写出的表单可以显著更快(开销更少)。然而,诀窍是不要将代码放在单独的函数中,因为调用开销将远远大于计算时间

下面是一个小例子:

x = 1:3;
A = rand(3);
v=0;

unroll = @(x) A(1)*(x(1)*x(1)) + A(5)*(x(2)*x(2)) + A(9)*(x(3)*x(3)) + A(4)*x(1)*x(2) + A(7)*x(1)*x(3) + A(2)*x(1)*x(2) + A(8)*x(2)*x(3) + A(3)*x(1)*x(3) + A(6)*x(2)*x(3); 
regular = @(x) x*A*x'; 

%Written out, no function call
tic
for t = 1:1e6
  v = A(1)*(x(1)*x(1)) + A(5)*(x(2)*x(2)) + A(9)*(x(3)*x(3)) + A(4)*x(1)*x(2) + A(7)*x(1)*x(3) + A(2)*x(1)*x(2) + A(8)*x(2)*x(3) + A(3)*x(1)*x(3) + A(6)*x(2)*x(3);;
end
t1=toc;

%Matrix form, no function call
tic
for t = 1:1e6
  v = x*A*x';
end
t2=toc;

%Written out, function call
tic
for t = 1:1e6
  v = unroll(x);
end
t3=toc;

%Matrix form, function call
tic
for t = 1:1e6
  v = regular(x); 
end
t4=toc;

[t1;t2;t3;t4]
这将产生以下结果:

0.0767
1.6988

6.1975
7.9353

因此,如果您通过(匿名)函数调用它,那么使用写出的表单将不会有什么意思,但是如果您真的想获得最佳速度,直接使用写出的表单可以大大加快微小矩阵的速度。

您能在修改前后发布代码吗?在(更快的版本)之前val=x(1)*a(1,1)*x(1)+。。。x(1)*A(1,2)*x(2)+。。。x(1)*A(1,3)*x(3)+。。。x(2)*A(2,1)*x(1)+。。。x(2)*A(2,2)*x(2)+。。。x(2)*A(2,3)*x(3)+。。。x(3)*A(3,1)*x(1)+。。。x(3)*A(3,2)*x(2)+。。。x(3)*A(3,3)*x(3);vs-val=xAx';有时,用这种方法展开方程的速度更快,尽管它对于较大的尺寸是不可行的。。。此外,您不会利用针对矩阵乘法的优化BLAS实现(英特尔MKL)也不应该是
v=x'*A*x
,这样您就可以得到标量结果
1x3*3x3*3x1=1x1
。请注意,如果A是对称的,那么这就是我的感谢转录错误的矩阵形式,以及对注释编辑器的不熟悉……这可能是一个重要的观点,可以解释OP报告的更快时间,这可能不在您的测试中:Matlab的JIT是否适用于匿名函数?如果工作量不大,您可以尝试将生成的函数保存到一个m文件中,而不是动态创建一个函数,看看这是否有什么不同。@BasSwinckels:done,请参阅最近的编辑。这并没有带来巨大的影响,尽管对于较小的尺寸,它会更快一些(你也可以自己测试!)。我只想提醒你,这一范围的
N=2:50
对于
mtimes
来说是微不足道的,可以轻松处理数千次。我甚至不会尝试为这样的大小编写扩展版本:)我仍然看不出它的优势。即使对这种矩阵乘法进行1e6次迭代,我们也几乎看不到1秒的改进。总而言之,这并不是一个很大的改进,我更喜欢直接的实现,特别是因为这不适用于较大的规模:)关于函数调用开销的观察非常切题。我和一位同事做了一个实验,比较了:(A)内联写出的表单,(b)x'Ax,以及(c)写出表单的函数调用版本。b原来是
>> N=5000; x=rand(N,1); A=rand(N,N);
>> tic, for i=1e4, v=x.'*A*x; end, toc
Elapsed time is 0.021959 seconds.
x = 1:3;
A = rand(3);
v=0;

unroll = @(x) A(1)*(x(1)*x(1)) + A(5)*(x(2)*x(2)) + A(9)*(x(3)*x(3)) + A(4)*x(1)*x(2) + A(7)*x(1)*x(3) + A(2)*x(1)*x(2) + A(8)*x(2)*x(3) + A(3)*x(1)*x(3) + A(6)*x(2)*x(3); 
regular = @(x) x*A*x'; 

%Written out, no function call
tic
for t = 1:1e6
  v = A(1)*(x(1)*x(1)) + A(5)*(x(2)*x(2)) + A(9)*(x(3)*x(3)) + A(4)*x(1)*x(2) + A(7)*x(1)*x(3) + A(2)*x(1)*x(2) + A(8)*x(2)*x(3) + A(3)*x(1)*x(3) + A(6)*x(2)*x(3);;
end
t1=toc;

%Matrix form, no function call
tic
for t = 1:1e6
  v = x*A*x';
end
t2=toc;

%Written out, function call
tic
for t = 1:1e6
  v = unroll(x);
end
t3=toc;

%Matrix form, function call
tic
for t = 1:1e6
  v = regular(x); 
end
t4=toc;

[t1;t2;t3;t4]
0.0767
1.6988

6.1975
7.9353