纯粹作为一个实验,我在MATLAB中编写排序函数,然后通过MATLAB分析器运行它们。我觉得最令人困惑的方面是交换元素。
我发现在矩阵中交换两个元素的“官方”方式
self.Data([i1, i2]) = self.Data([i2, i1])
比在四行代码中运行要慢得多:
e1 = self.Data(i1);
e2 = self.Data(i2);
self.Data(i1) = e2;
self.Data(i2) = e1;
第二个示例占用的总时间 12次小于第一个示例中的单行代码。
有人会解释为什么吗?
答案 0 :(得分:6)
根据发布的建议,我进行了一些测试。 当分配的LHS和RHS中引用相同的矩阵时,似乎会出现性能损失。
我的理论是MATLAB使用内部引用计数/写时复制机制,这导致整个矩阵在内部被引用时被内部复制。 (这是猜测,因为我不知道MATLAB的内部结构。)
以下是调用函数885548次的结果。 (这里的差异是第四次,而不是我最初发布的十二次。每个函数都有额外的函数包装开销,而在我的初始帖子中我只是总结了各行。)
swap1: 12.547 s swap2: 14.301 s swap3: 51.739 s
以下是代码:
methods (Access = public)
function swap(self, i1, i2)
swap1(self, i1, i2);
swap2(self, i1, i2);
swap3(self, i1, i2);
self.SwapCount = self.SwapCount + 1;
end
end
methods (Access = private)
%
% swap1: stores values in temporary doubles
% This has the best performance
%
function swap1(self, i1, i2)
e1 = self.Data(i1);
e2 = self.Data(i2);
self.Data(i1) = e2;
self.Data(i2) = e1;
end
%
% swap2: stores values in a temporary matrix
% Marginally slower than swap1
%
function swap2(self, i1, i2)
m = self.Data([i1, i2]);
self.Data([i2, i1]) = m;
end
%
% swap3: does not use variables for storage.
% This has the worst performance
%
function swap3(self, i1, i2)
self.Data([i1, i2]) = self.Data([i2, i1]);
end
end
答案 1 :(得分:4)
在第一种(慢)方法中,RHS值是一个矩阵,所以我认为MATLAB在创建一个新矩阵来存储这两个元素时会产生性能损失。第二种(快速)方法通过直接处理元素来避免这种情况。
查看MathWorks上的“Techniques for Improving Performance”文章,了解改进MATLAB代码的方法。
答案 2 :(得分:2)
您也可以这样做:
tmp = self.Data(i1);
self.Data(i1) = self.Data(i2);
self.Data(i2) = tmp;
答案 3 :(得分:2)
Zach可能是正确的,因为可能会使矩阵的临时副本执行第一个操作,尽管我猜测在MATLAB中有一些内部优化试图避免这种情况。它可能是您正在使用的MATLAB版本的函数。我在版本7.1.0.246(几年前)中尝试了两种情况,只看到了大约2-2.5的速度差异。
这可能是通过所谓的“循环展开”来提高速度的一个例子。在进行向量运算时,在内部代码中的某个级别,可能存在一个FOR循环,它循环遍历您正在交换的索引。通过在第二个示例中执行标量操作,您可以避免循环中的任何开销。请注意这两个(有些愚蠢)的例子:
vec = [1 2 3 4];
%Example 1:
for i = 1:4,
vec(i) = vec(i)+1;
end;
%Example 2:
vec(1) = vec(1)+1;
vec(2) = vec(2)+1;
vec(3) = vec(3)+1;
vec(4) = vec(4)+1;
不可否认,简单地使用矢量操作会更容易:
vec = vec+1;
但上述示例仅用于说明目的。当我多次重复每个示例并对其进行计时时,示例2实际上比示例1快一些。对于具有已知编号的小循环(在示例中,仅为4),放弃循环实际上更有效。当然,在这个特定的例子中,上面给出的向量运算实际上是最快的。
我通常遵循这条规则:尝试一些不同的东西,并针对您的具体问题选择最快的。
答案 4 :(得分:2)
这篇文章值得更新,因为JIT编译器现在是一个东西(since R2015b),因此timeit
(since R2013b)更可靠的函数计时。
以下是大型数组中元素交换的简短基准测试功能。 我使用术语“直接交换”和“使用临时变量”来分别描述问题中的两种方法。
结果非常惊人,与使用临时变量相比,直接交换2个元素的性能越来越差。
function benchie()
% Variables for plotting, loop to increase size of the arrays
M = 15; D = zeros(1,M); W = zeros(1,M);
for n = 1:M;
N = 2^n;
% Create some random array of length N, and random indices to swap
v = rand(N,1);
x = randi([1, N], N, 1);
y = randi([1, N], N, 1);
% Time the functions
D(n) = timeit(@()direct);
W(n) = timeit(@()withtemp);
end
% Plotting
plot(2.^(1:M), D, 2.^(1:M), W);
legend('direct', 'with temp')
xlabel('number of elements'); ylabel('time (s)')
function direct()
% Direct swapping of two elements
for k = 1:N
v([x(k) y(k)]) = v([y(k) x(k)]);
end
end
function withtemp()
% Using an intermediate temporary variable
for k = 1:N
tmp = v(y(k));
v(y(k)) = v(x(k));
v(x(k)) = tmp;
end
end
end