【发布时间】:2021-02-24 02:13:36
【问题描述】:
我知道有关 Julia 中的多线程性能的问题已经被问过(例如 here),但它们涉及相当复杂的代码,其中可能涉及很多事情。
在这里,我正在使用 Julia v1.5.3 在多个线程上运行一个非常简单的循环,与使用 Chapel 运行相同的循环相比,加速似乎并没有很好地扩展。
我想知道我做错了什么以及如何更有效地在 Julia 中运行多线程。
顺序码
using BenchmarkTools
function slow(n::Int, digits::String)
total = 0.0
for i in 1:n
if !occursin(digits, string(i))
total += 1.0 / i
end
end
println("total = ", total)
end
@btime slow(Int64(1e8), "9")
时间:8.034s
在 4 个线程上与 Threads.@threads 共享内存并行
using BenchmarkTools
using Base.Threads
function slow(n::Int, digits::String)
total = Atomic{Float64}(0)
@threads for i in 1:n
if !occursin(digits, string(i))
atomic_add!(total, 1.0 / i)
end
end
println("total = ", total)
end
@btime slow(Int64(1e8), "9")
时间:6.938s
加速:1.2
在 4 个线程上与 FLoops 共享内存并行
using BenchmarkTools
using FLoops
function slow(n::Int, digits::String)
total = 0.0
@floop for i in 1:n
if !occursin(digits, string(i))
@reduce(total += 1.0 / i)
end
end
println("total = ", total)
end
@btime slow(Int64(1e8), "9")
时间:10.850 秒
没有加速:比顺序代码慢。
测试不同数量的线程(不同的硬件)
我在另一台机器上测试了顺序代码和Threads.@threads 代码,并尝试了不同数量的线程。
结果如下:
| Number of threads | Speedup |
|---|---|
| 2 | 1.2 |
| 4 | 1.2 |
| 8 | 1.0 (no speedup) |
| 16 | 0.9 (the code takes longer to run than the sequential code) |
对于更繁重的计算(上面代码中的n = 1e9)可以最大限度地减少任何开销的相对影响,结果非常相似:
| Number of threads | Speedup |
|---|---|
| 2 | 1.1 |
| 4 | 1.3 |
| 8 | 1.1 |
| 16 | 0.8 (the code takes longer to run than the sequential code) |
比较:与Chapel 相同的循环显示完美缩放
使用 Chapel v1.23.0 运行代码:
use Time;
var watch: Timer;
config const n = 1e8: int;
config const digits = "9";
var total = 0.0;
watch.start();
forall i in 1..n with (+ reduce total) {
if (i: string).find(digits) == -1 then
total += 1.0 / i;
}
watch.stop();
writef("total = %{###.###############} in %{##.##} seconds\n",
total, watch.elapsed());
首次运行(与第一次 Julia 测试的硬件相同):
| Number of threads | Time (s) | Speedup |
|---|---|---|
| 1 | 13.33 | n/a |
| 2 | 7.34 | 1.8 |
第二次运行(相同硬件):
| Number of threads | Time (s) | Speedup |
|---|---|---|
| 1 | 13.59 | n/a |
| 2 | 6.83 | 2.0 |
第三次运行(不同的硬件):
| Number of threads | Time (s) | Speedup |
|---|---|---|
| 1 | 19.99 | n/a |
| 2 | 10.06 | 2.0 |
| 4 | 5.05 | 4.0 |
| 8 | 2.54 | 7.9 |
| 16 | 1.28 | 15.6 |
【问题讨论】:
-
另外,等效的教堂代码是线程安全的吗? (我不太了解教堂)
-
是的,它是线程安全的。
-
与问题没有直接关系,但在自己计时时,我将 Chapel 中的计时计算转换为更紧凑/优雅的:total = + reduce [i in 1..n] if ( i: string).find(digits) == -1 then 1.0 / i else 0.0;
-
感谢 Brad 提供的简洁紧凑的版本!我将在答案中保留我不太优雅的版本,因为它可能更容易被非教堂专家阅读。
标签: multithreading performance parallel-processing julia chapel