19.1 基本向量化

很好地近似来说,向量化的目标是编写避免循环并使用整个数组操作的代码。作为一个简单的例子,考虑

for i = 1:n
  for j = 1:m
    c(i,j) = a(i,j) + b(i,j);
  endfor
endfor

与简单得多的写法相比:

c = a + b;

这不仅更容易编写,在内部也更容易优化。Octave 将此操作委托给底层的实现,该实现除了其他优化外,还可能使用特殊的向量硬件指令,甚至可以并行执行加法运算。一般来说,如果代码被向量化,底层实现就拥有更多的自由度来进行假设,从而实现更快的执行。

这对于循环体"廉价"的循环尤为重要。通常只需对最内层循环进行向量化就足以获得可接受的性能。一个通用的经验法则是:向量化后的循环体的"阶数"应大于或等于外层循环的"阶数"。

作为一个不那么简单的例子,与其写成:

for i = 1:n-1
  a(i) = b(i+1) - b(i);
endfor

不如写成:

a = b(2:n) - b(1:n-1);

这展示了一个重要的通用概念:使用数组进行索引,而不是在索引变量上循环。详见 索引表达式。同时也要大量使用布尔索引。如果需要测试某个条件,这个条件也可以写成布尔索引的形式。例如,与其写成:

for i = 1:n
  if (a(i) > 5)
    a(i) -= 20
  endif
endfor

不如写成:

a(a>5) -= 20;

这利用了 a > 5 会产生一个布尔索引的事实。

尽可能使用逐元素向量运算符来避免循环(如 .*.^ 等运算符)。详见 算术运算符

同时在逐元素运算符中利用广播机制,既可以避免循环,也可以避免不必要的中间内存分配。详见 广播

尽可能使用内置函数和库函数。内置函数和已编译函数非常快。即使使用 m 文件库函数,它也很有可能已经过优化,或者将在未来的版本中得到进一步优化。

例如,比以下写法更好的是:

a = b(2:n) - b(1:n-1);

使用:

a = diff (b);

大多数 Octave 函数在设计时都考虑到了向量和数组参数。如果你发现自己正在编写一个包含非常简单操作的循环,很可能这样的函数已经存在了。以下函数在向量化代码中经常出现: