Random 生成和调用多个随机数的最佳方法?
我有一个一般性问题。我有一个Julia程序,每次遍历for循环时都需要使用一个随机数。我想知道,如果我在循环之前批量生成随机数,并将它们存储在一个调用这些预先生成的随机数的数组中,而不是动态生成这些随机数,那么会有什么性能优势?如果是这样的话,是否有一个最佳的批量大小?正如Peter O.评论的那样,这取决于具体情况。但让我举一个需要分批处理的例子:Random 生成和调用多个随机数的最佳方法?,random,julia,Random,Julia,我有一个一般性问题。我有一个Julia程序,每次遍历for循环时都需要使用一个随机数。我想知道,如果我在循环之前批量生成随机数,并将它们存储在一个调用这些预先生成的随机数的数组中,而不是动态生成这些随机数,那么会有什么性能优势?如果是这样的话,是否有一个最佳的批量大小?正如Peter O.评论的那样,这取决于具体情况。但让我举一个需要分批处理的例子: julia> using Random, BenchmarkTools julia> function f1()
julia> using Random, BenchmarkTools
julia> function f1()
x = Vector{Float64}(undef, 10^6)
y = zeros(10^6)
for i in 1:100
rand!(x)
y .+= x
end
return y
end
f1 (generic function with 1 method)
julia> function f2()
y = zeros(10^6)
@inbounds for i in 1:100
@simd for j in 1:10^6
y[j] += rand()
end
end
return y
end
f2 (generic function with 1 method)
julia> function f3()
y = zeros(10^6)
@inbounds for i in 1:100
for j in 1:10^6
y[j] += rand()
end
end
return y
end
f3 (generic function with 1 method)
julia> function f4()
x = Vector{Float64}(undef, 10^6)
y = zeros(10^6)
@inbounds for i in 1:100
rand!(x)
@simd for j in 1:10^6
y[j] += x[j]
end
end
return y
end
f4 (generic function with 1 method)
julia> function f5()
x = Vector{Float64}(undef, 10^6)
y = zeros(10^6)
@inbounds for i in 1:100
rand!(x)
for j in 1:10^6
y[j] += x[j]
end
end
return y
end
f5 (generic function with 1 method)
julia> @btime f1();
171.816 ms (4 allocations: 15.26 MiB)
julia> @btime f2();
370.950 ms (2 allocations: 7.63 MiB)
julia> @btime f3();
412.871 ms (2 allocations: 7.63 MiB)
julia> @btime f4();
172.355 ms (4 allocations: 15.26 MiB)
julia> @btime f5();
174.676 ms (4 allocations: 15.26 MiB)
如您所见,f1和使用循环f4和f5的两个变量比不使用缓存存储生成的随机变量f2和f3函数时要快得多。我已经展示了使用和不使用@simd进行比较的两种变体
编辑
拉斐克的评论非常好。以下是基准。正如您所看到的,仍然存在一些差异,但要低得多,因为最大的成本是生成随机数,而不是加法
julia> function g1(rnd)
x = Vector{Float64}(undef, 10^6)
y = zeros(10^6)
for i in 1:100
rand!(rnd, x)
y .+= x
end
return y
end
g1 (generic function with 1 method)
julia> function g2(rnd)
y = zeros(10^6)
@inbounds for i in 1:100
@simd for j in 1:10^6
y[j] += rand(rnd)
end
end
return y
end
g2 (generic function with 1 method)
julia> function g3(rnd)
y = zeros(10^6)
@inbounds for i in 1:100
for j in 1:10^6
y[j] += rand(rnd)
end
end
return y
end
g3 (generic function with 1 method)
julia> using Random
julia> rnd = MersenneTwister();
julia> @btime g1($rnd);
168.874 ms (4 allocations: 15.26 MiB)
julia> @btime g2($rnd);
193.398 ms (2 allocations: 7.63 MiB)
julia> @btime g3($rnd);
192.320 ms (2 allocations: 7.63 MiB)
正如Peter O.评论的那样,这要视情况而定。但让我举一个需要分批处理的例子:
julia> using Random, BenchmarkTools
julia> function f1()
x = Vector{Float64}(undef, 10^6)
y = zeros(10^6)
for i in 1:100
rand!(x)
y .+= x
end
return y
end
f1 (generic function with 1 method)
julia> function f2()
y = zeros(10^6)
@inbounds for i in 1:100
@simd for j in 1:10^6
y[j] += rand()
end
end
return y
end
f2 (generic function with 1 method)
julia> function f3()
y = zeros(10^6)
@inbounds for i in 1:100
for j in 1:10^6
y[j] += rand()
end
end
return y
end
f3 (generic function with 1 method)
julia> function f4()
x = Vector{Float64}(undef, 10^6)
y = zeros(10^6)
@inbounds for i in 1:100
rand!(x)
@simd for j in 1:10^6
y[j] += x[j]
end
end
return y
end
f4 (generic function with 1 method)
julia> function f5()
x = Vector{Float64}(undef, 10^6)
y = zeros(10^6)
@inbounds for i in 1:100
rand!(x)
for j in 1:10^6
y[j] += x[j]
end
end
return y
end
f5 (generic function with 1 method)
julia> @btime f1();
171.816 ms (4 allocations: 15.26 MiB)
julia> @btime f2();
370.950 ms (2 allocations: 7.63 MiB)
julia> @btime f3();
412.871 ms (2 allocations: 7.63 MiB)
julia> @btime f4();
172.355 ms (4 allocations: 15.26 MiB)
julia> @btime f5();
174.676 ms (4 allocations: 15.26 MiB)
如您所见,f1和使用循环f4和f5的两个变量比不使用缓存存储生成的随机变量f2和f3函数时要快得多。我已经展示了使用和不使用@simd进行比较的两种变体
编辑
拉斐克的评论非常好。以下是基准。正如您所看到的,仍然存在一些差异,但要低得多,因为最大的成本是生成随机数,而不是加法
julia> function g1(rnd)
x = Vector{Float64}(undef, 10^6)
y = zeros(10^6)
for i in 1:100
rand!(rnd, x)
y .+= x
end
return y
end
g1 (generic function with 1 method)
julia> function g2(rnd)
y = zeros(10^6)
@inbounds for i in 1:100
@simd for j in 1:10^6
y[j] += rand(rnd)
end
end
return y
end
g2 (generic function with 1 method)
julia> function g3(rnd)
y = zeros(10^6)
@inbounds for i in 1:100
for j in 1:10^6
y[j] += rand(rnd)
end
end
return y
end
g3 (generic function with 1 method)
julia> using Random
julia> rnd = MersenneTwister();
julia> @btime g1($rnd);
168.874 ms (4 allocations: 15.26 MiB)
julia> @btime g2($rnd);
193.398 ms (2 allocations: 7.63 MiB)
julia> @btime g3($rnd);
192.320 ms (2 allocations: 7.63 MiB)
你的问题相当广泛,取决于你的具体应用。在没有更多信息的情况下,您唯一可以判断的方法是在批处理和不批处理的情况下测量应用程序的性能。你应该编辑你的问题,解释更多关于你的具体应用。例如,应用程序是否因为随机数生成器而太慢?您的问题相当广泛,将取决于您的具体应用程序。在没有更多信息的情况下,您唯一可以判断的方法是在批处理和不批处理的情况下测量应用程序的性能。你应该编辑你的问题,解释更多关于你的具体应用。例如,应用程序是否因为随机数生成器而太慢?我想强调的是,使用全局隐式RNG调用时使用的RNG,例如,rand在调用站点上的速度很慢。这意味着调用rand100000是很好的,因为与随机生成相比,全局RNG是摊销的,但是在循环中调用rand对于每个生成的标量都有开销。因此,建议在循环之前使用命名的RNG,例如RNG=MersenneTwister,然后使用randrng。如果你想在Julia 1.3+上使用全局版本,你可以使用rng=Random.default\u rng。这个简单的技巧可以让这个基准的非批处理版本在我的机器上运行得更快。这是一个非常好的观点。我没有想过这件事。仍然在用兰德缓冲!我认为速度更快,因为这是我的基准测试所显示的,因为使用randrng之后似乎会禁用SIMD。我将用一个比较来更新答案。>稍后使用randrng似乎会禁用SIMD。我不太清楚你的意思,但由于兰德公司比兰德公司更为投入,我会感到惊讶的是,SIMD仅为后者而禁用。顺便说一句,对于您的初始基准测试,我得到了相同的f2和f3时间,所以@simd没有为我改变任何东西。有趣的是,在我们的两台不同的机器上,最好的策略并不相同!批处理与非批处理。。。在生产机器上进行基准测试是绝对没有办法的:我的意思是y.+=x使用SIMD,而对于rand和randrng,SIMD都被禁用。是的,很有趣的是,我们得到了不同的结果。我想强调一下,使用全局隐式RNG调用时使用的RNG,例如,rand在调用站点时速度较慢。这意味着调用rand100000是很好的,因为与随机生成相比,全局RNG是摊销的,但是在循环中调用rand对于每个生成的标量都有开销。因此,建议在循环之前使用命名的RNG,例如RNG=MersenneTwister,然后使用randrng。如果你想在Julia 1.3+上使用全局版本,你可以使用rng=Random.default\u rng。这个简单的技巧可以让这个基准的非批处理版本在我的机器上运行得更快。这是一个非常好的观点。我没有想过这件事。仍然在用兰德缓冲!我认为速度更快,因为这是我的基准测试所显示的,因为使用randrng之后似乎会禁用SIMD。我将用一个比较来更新答案。>稍后使用randrng似乎会禁用SIMD。我不太清楚你的意思,但由于兰德公司比兰德公司更为投入,我会感到惊讶的是,SIMD仅为后者而禁用。顺便说一句,对于您的初始基准测试,我得到了相同的f2和f3时间,所以@simd没有为我改变任何东西。有趣的是,在我们的两台不同的机器上,最好的策略并不相同 ! 批处理与非批处理。。。在生产机器上进行基准测试是绝对没有办法的:我的意思是y.+=x使用SIMD,而对于rand和randrng,SIMD都被禁用。是的,非常有趣的是,我们得到了不同的结果。