Verbeia开启了一个关于Mathematica中函数式编程风格表现的相当有趣的讨论。可在此处找到:What is the most efficient way to construct large block matrices in Mathematica? )
我正在研究一个问题,在对代码进行一些计时之后,一个特别耗时的部分就是我通过递归关系计算矩阵的条目:
c = Table[0, {2L+1}, {2L+1}];
c[[1, 1]] = 1;
Do[c[[i, i]] = e[[i - 1]] c[[i - 1, i - 1]], {i, 2, 2 L + 1}];
Do[c[[i, 1]] = (1 - e[[i - 1]]) c[[i - 1, 1]], {i, 2, 2 L + 1}];
Do[c[[i, j]] = (1 - e[[i - 1]]) c[[i - 1, j]] +
e[[i - 1]] c[[i - 1, j - 1]], {i, 2, 2 L + 1}, {j, 2, i - 1}];
其中e
是一些外部定义的列表。有什么方法可以用更有效的方式写出来吗?我似乎无法找到任何使用内置函数以更惯用和有效的方式实现此目的的明显方法。
我意识到我只能做这么多,因为这段代码是O(n^2)
,但是我有一系列矩阵乘法(总共大约6个),它们组合起来比这个语句花费的时间少。我能做些什么来加快这个速度,这对我来说会在运行时间上产生明显的差异。
更新
与推荐的 acl 一致,我尝试使用Compile
来加快表达速度。对于相对较小的L = 600
,我在原始Do[...]
上获得3.81秒,在普通旧版Compile
上获得1.54秒,在Compile[..., CompilationTarget->"C"]
获得0.033秒。
对于更实际的L = 1200
大小,Do
,Compile
和Compile[.., CompilationTarget->"C"]
的时间分别为16.68,0.605和0.132。我能够达到与他在帖子中提到的相同的2个数量级的加速。
答案 0 :(得分:3)
试试Compile
。在这里,我定义了3个函数:f
定义它,fc
编译(到某种字节码)和fcc
编译为C(查看文档以了解如何检查生成的码)。
首先,让mma告诉我们是否有东西无法编译:
SetSystemOptions["CompileOptions"->"CompileReportExternal"->True]
然后是功能:
ClearAll[f];
f=Function[{ell,e},
Module[{c=Table[0,{2ell+1},{2ell+1}]},
c[[1,1]]=1;
Do[c[[i,i]]=e[[i-1]] c[[i-1,i-1]],{i,2,2 ell+1}];
Do[c[[i,1]]=(1-e[[i-1]]) c[[i-1,1]],{i,2,2 ell+1}];
Do[c[[i,j]]=(1-e[[i-1]]) c[[i-1,j]]+e[[i-1]] c[[i-1,j-1]],
{i,2,2 ell+1},{j,2,i-1}];
c
]
];
ClearAll[fc];
fc=Compile[{{ell,_Integer},{e,_Integer,1}},
Module[{c},
c=Table[0,{2ell+1},{2ell+1}];
c[[1,1]]=1;
Do[c[[i,i]]=e[[i-1]] c[[i-1,i-1]],{i,2,2 ell+1}];
Do[c[[i,1]]=(1-e[[i-1]]) c[[i-1,1]],{i,2,2 ell+1}];
Do[c[[i,j]]=(1-e[[i-1]]) c[[i-1,j]]+e[[i-1]] c[[i-1,j-1]],
{i,2,2 ell+1},{j,2,i-1}];
c
]
];
ClearAll[fcc];
fcc=Compile[{{ell,_Integer},{e,_Integer,1}},
Module[{c},
c=Table[0,{2ell+1},{2ell+1}];
c[[1,1]]=1;
Do[c[[i,i]]=e[[i-1]] c[[i-1,i-1]],{i,2,2 ell+1}];
Do[c[[i,1]]=(1-e[[i-1]]) c[[i-1,1]],{i,2,2 ell+1}];
Do[c[[i,j]]=(1-e[[i-1]]) c[[i-1,j]]+e[[i-1]] c[[i-1,j-1]],
{i,2,2 ell+1},{j,2,i-1}];
c
],
CompilationTarget->"C",
RuntimeOptions->"Speed"
];
没有错误,所以没关系。现在进行测试(这些在带有2.4GHz核心2 duo的电池上运行的macbook上):
ell=400;
e=RandomInteger[{0,1},2*ell];
f[ell,e];//Timing
fc[ell,e];//Timing
fcc[ell,e];//Timing
给
{2.60925, Null}
{0.092022, Null}
{0.022709, Null}
所以编译为C的版本在这里要快两个数量级。
如果您更改类型并获得编译错误,请询问。
编辑:如果e
包含实数,请尝试
ClearAll[fc];
fc=Compile[{{ell,_Integer},{e,_Real,1}},
Module[{c},
c=Table[0.,{2ell+1},{2ell+1}];
c[[1,1]]=1;
Do[c[[i,i]]=e[[i-1]] c[[i-1,i-1]],{i,2,2 ell+1}];
Do[c[[i,1]]=(1-e[[i-1]]) c[[i-1,1]],{i,2,2 ell+1}];
Do[c[[i,j]]=(1-e[[i-1]]) c[[i-1,j]]+e[[i-1]] c[[i-1,j-1]],
{i,2,2 ell+1},{j,2,i-1}];
c
]
];
ClearAll[fcc];
fcc=Compile[{{ell,_Integer},{e,_Real,1}},
Module[{c},
c=Table[0.,{2ell+1},{2ell+1}];
c[[1,1]]=1;
Do[c[[i,i]]=e[[i-1]] c[[i-1,i-1]],{i,2,2 ell+1}];
Do[c[[i,1]]=(1-e[[i-1]]) c[[i-1,1]],{i,2,2 ell+1}];
Do[c[[i,j]]=(1-e[[i-1]]) c[[i-1,j]]+e[[i-1]] c[[i-1,j-1]],
{i,2,2 ell+1},{j,2,i-1}];
c
],
CompilationTarget\[Rule]"C",
RuntimeOptions\[Rule]"Speed"
];
代替。
通过说
可以了解其工作原理Needs["CompiledFunctionTools`"]
CompilePrint[fc]
并获得
2 arguments
18 Integer registers
6 Real registers
3 Tensor registers
Underflow checking off
Overflow checking off
Integer overflow checking on
RuntimeAttributes -> {}
I0 = A1
T(R1)0 = A2
I12 = 0
I1 = 2
I3 = 1
I14 = -1
R0 = 0.
Result = T(R2)2
1 I11 = I1 * I0
2 I11 = I11 + I3
3 I7 = I1 * I0
4 I7 = I7 + I3
5 I17 = I12
6 T(R2)2 = Table[ I11, I7]
7 I15 = I12
8 goto 13
9 I16 = I12
10 goto 12
11 Element[ T(R2)2, I17] = R0
12 if[ ++ I16 < I7] goto 11
13 if[ ++ I15 < I11] goto 9
14 R1 = I3
15 Part[ T(R2)2, I3, I3] = R1
16 I4 = I1 * I0
17 I4 = I4 + I3
18 I5 = I3
19 goto 26
20 I10 = I5 + I14
21 I8 = I10
22 R4 = Part[ T(R1)0, I8]
23 R5 = Part[ T(R2)2, I8, I8]
24 R4 = R4 * R5
25 Part[ T(R2)2, I5, I5] = R4
26 if[ ++ I5 < I4] goto 20
27 I4 = I1 * I0
28 I4 = I4 + I3
29 I5 = I3
30 goto 40
31 I10 = I5 + I14
32 I13 = I10
33 R4 = Part[ T(R1)0, I13]
34 R5 = - R4
35 R4 = I3
36 R4 = R4 + R5
37 R5 = Part[ T(R2)2, I13, I3]
38 R4 = R4 * R5
39 Part[ T(R2)2, I5, I3] = R4
40 if[ ++ I5 < I4] goto 31
41 I4 = I1 * I0
42 I4 = I4 + I3
43 I5 = I3
44 goto 63
45 I6 = I5 + I14
46 I17 = I3
47 goto 62
48 I16 = I5 + I14
49 I9 = I16
50 R4 = Part[ T(R1)0, I9]
51 R2 = R4
52 R4 = - R2
53 R5 = I3
54 R5 = R5 + R4
55 R4 = Part[ T(R2)2, I9, I17]
56 R5 = R5 * R4
57 I16 = I17 + I14
58 R4 = Part[ T(R2)2, I9, I16]
59 R3 = R2 * R4
60 R5 = R5 + R3
61 Part[ T(R2)2, I5, I17] = R5
62 if[ ++ I17 < I6] goto 48
63 if[ ++ I5 < I4] goto 45
64 Return
使用寄存器的名称指示其类型等。如果使用“C”选项,您还可以查看生成的C代码(但这有点难以阅读)。
答案 1 :(得分:2)
鉴于已定义e
(如评论中所述),您可以像这样前两个步骤:
firsttwosteps = DiagonalMatrix[FoldList[#1 * #2&, 1, e]] +
ArrayFlatten[{{ {{0}} , {Table[0,{2L}]} },
{Transpose[{Rest@ FoldList[(1-#2)#1 &,1,e]}], Table[0,{2L},{2L}]}}]
也就是说,根据需要在两个矩阵中设置对角线和第一列并添加它们。这是有效的,因为第二步对第一步的唯一依赖是位置{1,1}的元素。
第三步有点棘手。如果你想要一个纯粹的功能解决方案,那么就是两个FoldList
的情况。您可能会发现以转置形式构建firsttwosteps
更容易,然后将上三角形结果转换为较低三角形的最终结果。
firsttwostepsT = DiagonalMatrix[FoldList[#1 * #2&, 1, e]] +
ArrayFlatten[{{ {{0}} ,{Rest@ FoldList[(1-#2)#1 &,1,e]} },
{ Table[0,{2L}, {1}], Table[0,{2L},{2L}]}}]
然后:
Transpose @ FoldList[With[{cim1 = #1},
Most@FoldList[(1 - #2[[1]]) #1 + #2[[1]] #2[[2]] &, 0.,
Transpose[{Join[e, {0}], cim1}]]] &, First@firsttwostepsT, Join[e, {0}]]
在我做的检查中,这保留了firsttwostepsT
的对角线,并在转置之前产生一个上三角矩阵。
您的代码缓慢的原因是您通过定义单个部分重复重新定义相同的整个矩阵。作为一般原则,您几乎不需要在Mathematica中使用Do
循环和ReplacePart
类型构造。几乎总有一种更好的方法。