昨天版主有讲到,julia
标量优先速度更快,并举了例子。我回忆了下,产生了点问题。举个例子,假设A
,B
为一维矢量,长度n
。
# 假设设置MATLAB函数
function funMatlab(A, B)
C = sin.(A .* B) # 展开后循环两次,计算2n次
F = cos.(A ./ B) # 同上,2n次
C.^2 .+ F.^2 # 展开循环相当于三次,计算3n次。共7n次。
end
# 这是Julia函数
function funJulia(a, b)
C = sin(a*b) # 对标量2次计算
F = cos(a/ b) # 对标量2次计算
C^2 + F^2 # 对标量3次计算
end
> funJulia.(A, B) # 总共计算7n次
我好奇的是,两次计算次数不是一样的么?所以“标量优先”优势来自于哪儿呢?
1 个赞
内存。详情请见 More Dots: Syntactic Loop Fusion in Julia (julialang.org)。简单说就是MATLAB之所以需要vectorization,是因为可以直接调用底层C/Fortran函数,但是代价是内存开销。Julia不需要这么做,而应该更接近C/Fortran的代码。
2 个赞
我觉得这个问题还是挺tricky的。
例如CUDA.jl提供的这个例子里,跑的最快的还是向量形式的代码。
确实,这个问题,或者说我提 “标量优先” 其实都是围绕 CPU 这种简单的计算模型来考虑的。主要是想避免大家直接落入“向量化编程可以加速代码”这样的性能误区。
GPU 的场景要更复杂的多,因为 GPU 有它的流水线调度机制,所以很多内存传输开销被压缩到了一个甚至可以忽略不计的程度。但如果我的理解没错的话,CUDA 上最快的策略应该还是去手动写 kernel。从某种意义上来说,也是标量的策略更快,但在 GPU 上手写 kernel 一直属于进阶话题,所以大家用 GPU 都是以向量化形式使用居多。
计算本身的次数是一样的。但是对于这种基础运算来说,计算开销本身的占比可能甚至不如内存传输的开销来的高。不妨思考一下 A
这个矩阵被遍历了几次: funcMatlab(A, B)
中 A
被遍历了两次,而 funcJulia.(A, B)
中 A
只被遍历了一次。
另外之所以我提 “标量优先” 的概念,除了性能因素以外,还有:
- 标量实现的代码总是可以被轻松封装成向量化代码,但反过来向量化代码却很难拆成标量实现
- 标量实现的代码整体来说总是比向量化代码来的更简洁、可读性也更强
2 个赞