Mathematica 做數值計算時有哪些方式可以達到提速的目的?


你這問題太籠統,只能引用Wolfram Blog上的一篇文章籠統地回答了
10 Tips for Writing Fast Mathematica Code

1. 數值計算儘早使用浮點數
2. 善用Compile(直接編譯成DLL跑得更快)
3. 熟悉並盡量使用內置函數
4. 學會使用Profiler
5. 以存代算,一樣的東西不要算好多遍
6. 並行化(並用CUDALink和OpenCLLink進行GPU運算)
7. 收集數據用Sow和Reap,別總用AppendTo
8. 知道Block, With, Module分別是什麼,能用前兩個就別用Module
9. Pattern雖爽不要濫用
10. 多試幾種玩法
--------------------------------------------------
舉個例子,順便介紹一下RuntimeTools`Profile這個函數。這個函數可以用來看一段代碼中每一句的運行時間(由於沒有文檔,也不知道是AbsoluteTiming還是Timing),但是不能用來profile內存佔用。

基本用法:打開Debugger,然後想profile誰就把誰用RuntimeTools`Profile括起來。

示例:我想算一堆粒子之間的Lennard-Jones作用力

vec{f}(vec{r})=frac{vec{r}}{r^2}left(frac{1}{r^6}-frac{2}{r^{12}}
ight)

每個粒子的總受力就是

vec{f}_j = sum_{i
eq j} {frac{vec{r}_{ij}}{r_{ij}^2}left(frac{1}{r_{ij}^6}-frac{2}{r_{ij}^{12}}
ight)}

先把每個粒子的坐標初始化了

r0 = RandomReal[{-1,1}, {1000,3}]

於是我很耿直地寫道(3. 使用內置函數Outer, Map, Total,不過求R用內置的DistanceMatrix似乎要慢一點)

LJForce[r0_] := Module[{r, R, f},
r = Outer[Subtract, r0, r0, 1];
R = Map[Norm, r, {2}];
f = Total[Quiet[r/R^2 (1/R^6 - 2/R^12)] /. Indeterminate -&> 0]]

然後發現一個簡單的O(N^2)對1000x3的數組跑了7秒多

pts = RandomReal[{0, 1}, {1000, 3}]; LJForce[pts]; // AbsoluteTiming (* {7.49027, Null} *)

profile一下(4. 學會使用Profiler)

LJForce[pts]; // RuntimeTools`Profile

發現乘除運算非常耗時,最下面一個1000x3矩陣乘12次方再倒數都花了0.64秒時間,所以相應調整代碼

LJForce1[r0_] := Module[{r, R, Ri2, f},
r = Outer[Subtract, r0, r0, 1];
R = Map[Norm, r, {2}];
Ri2 = Quiet[1/R^2] /. ComplexInfinity -&> 0;
f = Total[r Ri2 (Ri2^3 - 2 Ri2^6)]]

測下時間

LJForce1[pts]; // AbsoluteTiming (* {4.41834, Null} *)

ReplaceAll大家都知道很慢的,所以換掉(10. 多試幾種玩法)

LJForce2[r0_] := Module[{r, R, Ri2, f},
r = Outer[Subtract, r0, r0, 1];
R = Map[Norm, r, {2}];
Ri2 = With[{UT = Quiet[1/R^2]~UpperTriangularize~1}, UT + Transpose[UT]];
f = Total[r Ri2 (Ri2^3 - 2 Ri2^6)]]

測下時間

LJForce2[pts]; // AbsoluteTiming (* {1.7076, Null} *)

最後換用Compile(2. 善用Compile),Indeterminate,ComplexInfinity這種暴力的東西沒法放進去,所以稍作改動

LJForceC = Compile[{{r0, _Real, 2}},
Module[{r, Ri2},
r = Outer[Subtract, r0, r0, 1];
Ri2 = Map[With[{R = Norm[#]}, If[R == 0., 0., 1/R^2]] , r, {2}];
Total[r Ri2 (Ri2^3 - 2 Ri2^6)] ] ];

測下時間

LJForceC[pts]; // AbsoluteTiming (* {0.414843, Null} *)

這裡面的性能應該還是沒有完全榨乾的,比如把R^12寫成(R^6)^2還能省點時間。那個If看起來比較蠢,白白增加分支,但是我沒想出來怎麼避免。

附一個最慢的代碼作為對比

LJForce0[r0_] := Module[{n, r, R, f},
n = Length[r0];
r = Table[r0[[i]] - r0[[j]], {i, n}, {j, n}];
R = Table[Norm[r[[i, j]]], {i, n}, {j, n}];
f = Total[Quiet[r/R^2 (1/R^6 - 2/R^12)] /. Indeterminate -&> 0]]
LJForce0[pts]; // AbsoluteTiming (* {17.7498, Null} *)


考慮用Compile和library link


@蘇晗曉 的答案基本已經很清楚了

另外提一下,上次Wolfram的工程師做技術宣講的時候,特別提到了,少用循環多用函數式編程手法,這可以顯著提高這部分的效率。

還有如果需要極端效率的話,使用C和Wolfram的混合編程是不錯的選擇


推薦閱讀:

如何使用mathematica發送郵件?
為什麼在 Mathematica 中使用循環是低效的?
如何實現任意精度計算?
學習 Mathematica 有什麼推薦書籍?
Mathematica 直接計算二重極限(double limit)的格式是什麼?

TAG:效率 | WolframMathematica | 數值計算 |