我尝试了以下内容。
a = randn(100,100);
b = randn(100,100);
c = randn(100,1);
@time a*b*c
@time a*(b*c)
julia> @time a*b*c;
0.000591 seconds (7 allocations: 79.234 KiB)
julia> @time a*(b*c);
0.000101 seconds (6 allocations: 1.906 KiB)
结果与上述结果非常一致。虽然它确实直观地理解为什么第二个更好(矩阵向量乘法两次而不是大矩阵 - 矩阵乘法)。
我很想知道,朱莉娅不应该优化这一点,知道矩阵的尺寸,它可以重新排序操作以优化它吗?或者我只是懒得想要那个或者是否有其他我没有看到的技术问题。
所以,这是我在dump()
(a*b*c)
时得到的结果
head: Symbol call
args: Array{Any}((4,))
1: Symbol *
2: Array{Float64}((100, 100)) [0.290788 -0.0601455 … -0.408164 1.16261; -0.539274 -1.56979 … 2.56233 0.806247; … ; 1.30981 -1.31929 … 1.38655 -1.89169; -1.58483 0.318804 … -0.0500151 2.13105]
3: Array{Float64}((100, 100)) [-0.464882 1.60371 … -0.390234 0.605401; -1.06837 0.296049 … 0.759708 0.0124688; … ; -0.149613 -1.38653 … 0.284494 1.47524; 0.34351 0.420449 … 0.544973 1.85736]
4: Array{Float64}((100, 1)) [1.64066; 0.593296; … ; 0.908361; 0.486164]
typ: Any
dump(a*(b*c))
Expr
head: Symbol call
args: Array{Any}((3,))
1: Symbol *
2: Array{Float64}((100, 100)) [0.290788 -0.0601455 … -0.408164 1.16261; -0.539274 -1.56979 … 2.56233 0.806247; … ; 1.30981 -1.31929 … 1.38655 -1.89169; -1.58483 0.318804 … -0.0500151 2.13105]
3: Expr
head: Symbol call
args: Array{Any}((3,))
1: Symbol *
2: Array{Float64}((100, 100)) [-0.464882 1.60371 … -0.390234 0.605401; -1.06837 0.296049 … 0.759708 0.0124688; … ; -0.149613 -1.38653 … 0.284494 1.47524; 0.34351 0.420449 … 0.544973 1.85736]
3: Array{Float64}((100, 1)) [1.64066; 0.593296; … ; 0.908361; 0.486164]
typ: Any
typ: Any
那么,假设运算符是关联的,是否存在尝试优化此问题的问题?它会变得非常复杂或难以处理吗?
答案 0 :(得分:3)
编辑:整个答案假定*
是Julia中的二元运算符。 这是不正确的,因此OP问题完全有效。有关一些讨论,请参阅https://discourse.julialang.org/t/is-there-any-way-to-make-custom-binary-infix-operators-right-associative/3202/5(特别提到*
被解析为n-arry,which can be seen here)。我将在下面留下原来的答案。
矩阵 - 矩阵乘法需要n^3
步(对于小矩阵)。矩阵向量乘法需要n^2
步。
a*b*c
与(a*b)*c
相同,后者共执行n^3+n^2
次操作,a*(b*c)
执行2*n^2
次操作,因为它只包含矩阵向量乘法
如果您想要自动优化矩阵代数表达式,可以尝试像https://github.com/Jutho/TensorOperations.jl这样的库(谷歌首次针对这类问题点击)。评论中提到的另一个选项是https://github.com/AustinPrivett/MatrixChainMultiply.jl
至于Julia为什么不自动执行此操作:这必须是解析器的一部分(运算符的优先级),但是您不希望解析器能够在运行时内部查看以了解对象是什么是。另一方面,这可以通过在编译之前应用宏来实现,如上述包所做的那样。也许如果你使用一些编译时类型信息也是可行的,但是对于朱莉娅优雅的多分派机制中的几种类型而言,这将是一个相当丑陋的特例。