Julia 转置矩阵并保留列主内存布局 问题说明:矩阵的行范数

Julia 转置矩阵并保留列主内存布局 问题说明:矩阵的行范数,julia,Julia,考虑这个玩具例子,我计算随机矩阵M的所有列的范数 julia> M = rand(Float64, 10000, 10000); julia> @time map(x -> norm(x), M[:,j] for j in 1:size(M)[2]); 0.363795 seconds (166.70 k allocations: 770.086 MiB, 27.78% gc time) 然后计算行范数 julia> @time map(x -> norm(

考虑这个玩具例子,我计算随机矩阵M的所有列的范数

julia> M = rand(Float64, 10000, 10000);

julia> @time map(x -> norm(x), M[:,j] for j in 1:size(M)[2]);
  0.363795 seconds (166.70 k allocations: 770.086 MiB, 27.78% gc time)
然后计算行范数

julia> @time map(x -> norm(x), M[:,i] for i in 1:size(M)[1]);
  1.288872 seconds (176.19 k allocations: 770.232 MiB, 0.37% gc time)
两次执行之间的因素是由于(我认为)矩阵的内存布局(主列)。事实上,行规范的计算是对非连续数据的循环,这会导致缓存未命中的非矢量化代码。 我希望两种计算的执行时间相同

在计算行的规范时,是否可以将
M
的布局转换为row major以获得相同的速度

我试了什么 我尝试了
transpose
permutedims
,但没有成功,似乎在使用这些函数时,内存现在位于row major(原始矩阵的columns major)中

我在这里使用了
copy
来强制新布局

如何强制换位的列主布局,或原始矩阵的行主布局

编辑 正如@mcabbott和@przemyslaw szufel所指出的,在我展示的最后一个代码中有一个错误,我计算了
Mt
行的规范,而不是列的规范

Mt
列规范的测试给出了:

julia> Mt = transpose(M);
julia> @time map(x -> norm(x), M[:,j] for j in 1:size(M)[2]);
  1.307777 seconds (204.52 k allocations: 772.032 MiB, 0.45% gc time)

julia> Mt = permutedims(M)
julia> @time map(x -> norm(x), M[:,j] for j in 1:size(M)[2]); 
  0.334047 seconds (166.53 k allocations: 770.079 MiB, 1.42% gc time)

因此,最后,
permutedims
似乎像预期的那样存储在主列中。事实上,Julia数组总是存储在major列中<代码>转置是一种例外,因为它是列主存储矩阵的行主视图。

这里有几个问题:

  • 您没有正确地对代码进行benchamring—很可能在第一次运行时测试编译的代码,在第二次运行时测试未编译的代码(并因此测量编译时间)。您应该始终运行两次
    @time
    ,或者改用BenchmarkTools
  • 您的代码效率低下-不必要的内存复制
  • M的类型是不稳定的,因此测量包括找出其类型的时间,而这不是通常运行Julia函数时的情况
  • 您不需要lambda,只需直接解析函数即可
  • 正如@mcabbott所提到的,您的代码包含一个bug,并且您正在测量两次相同的东西。 清除代码后,代码如下所示:
现在是关于数据布局的问题。 Julia正在使用列主内存布局。因此,处理列的操作将比处理行的操作快。 一种可能的解决方法是使用
M
的转置副本:

const Mᵀ = collect(M')
这需要一些时间进行复制,但允许您稍后匹配性能:

julia> @btime map(norm, @view Mᵀ[:,j] for j in 1:size(M)[2]);
  48.455 ms (2 allocations: 78.20 KiB)

julia> map(norm, Mᵀ[:,j] for j in 1:size(M)[2]) == map(norm, M[i,:] for i in 1:size(M)[1])
true

在计算规范时,创建每列/每行的副本会浪费大量时间。改用
视图
s,或者更好地使用
eachcol
/
eachrow
,它也不分配:

julia> M = rand(1000, 1000);

julia> @btime map(norm, $M[:,j] for j in 1:size($M, 2));  # slow and ugly
  946.301 μs (1001 allocations: 7.76 MiB)

julia> @btime map(norm, eachcol($M));  # fast and nice
  223.199 μs (1 allocation: 7.94 KiB)

julia> @btime norm.(eachcol($M));  # even nicer, but allocates more for some reason.
  227.701 μs (3 allocations: 47.08 KiB)

我刚刚发现它是相关的,但它很旧,我认为这四个计算的结果是相同的。前两种是相同的条定时噪声,但是
map(norm,eachcol(M))
会更快。请注意,
copy(transpose(M))
permutedims(M)
,两者都返回一个新的
数组(总是列主数组),数据被移动,而
transpose(M)
是一个包装器,具有行主存储。相关问题:关于
norm(a;dims)
,关于
视图(transpose(a),i,:)
。感谢您对我的代码进行了所有这些优化,并提供了另一种转置方式。事实上,这是一个愚蠢的错误,但在这里发帖让我学到了很多东西
,即通过生成器进行广播首先收集,而映射仅收集答案。
julia> @btime map(norm, @view Mᵀ[:,j] for j in 1:size(M)[2]);
  48.455 ms (2 allocations: 78.20 KiB)

julia> map(norm, Mᵀ[:,j] for j in 1:size(M)[2]) == map(norm, M[i,:] for i in 1:size(M)[1])
true
julia> M = rand(1000, 1000);

julia> @btime map(norm, $M[:,j] for j in 1:size($M, 2));  # slow and ugly
  946.301 μs (1001 allocations: 7.76 MiB)

julia> @btime map(norm, eachcol($M));  # fast and nice
  223.199 μs (1 allocation: 7.94 KiB)

julia> @btime norm.(eachcol($M));  # even nicer, but allocates more for some reason.
  227.701 μs (3 allocations: 47.08 KiB)