Performance 为什么是';所有(itr)do';在这种情况下,阻塞比for循环慢? 我的代码是做什么的
我们的目标是构建一个函数,用julia检查给定字符串中的所有括号是否正确打开和关闭。所以Performance 为什么是';所有(itr)do';在这种情况下,阻塞比for循环慢? 我的代码是做什么的,performance,julia,benchmarking,Performance,Julia,Benchmarking,我们的目标是构建一个函数,用julia检查给定字符串中的所有括号是否正确打开和关闭。所以 "{abc()([[def]])()}" 应该返回true,而 "{(bracket order mixed up here!})[and this bracket doesn't close!" 应该返回false 问题: 我有两个版本的函数为什么第一版速度提高了10%左右? 第一版 第二版 在这里,我将for循环替换为do块: function matching_brackets(s::Abstra
"{abc()([[def]])()}"
应该返回true,而
"{(bracket order mixed up here!})[and this bracket doesn't close!"
应该返回false
问题:
我有两个版本的函数为什么第一版速度提高了10%左右?
第一版
第二版
在这里,我将for循环替换为do块:
function matching_brackets(s::AbstractString)
close_open_map = Dict('}' => '{', ')' => '(', ']' => '[')
order_arr = []
all_correct = all(s) do char
if char in values(close_open_map)
push!(order_arr, char)
elseif (char in keys(close_open_map)) &&
(isempty(order_arr) || (close_open_map[char] != pop!(order_arr)))
return false
end
return true
end
return all_correct && isempty(order_arr)
end
时间安排
使用BenchmarkTools的@benchmark对字符串“{()()[()]()}”
和“{()()()[())]()}”
进行比较时,两个字符串的执行速度都降低了约10%
附加信息
版本信息:
Julia Version 1.3.1
Commit 2d5741174c (2019-12-30 21:36 UTC)
Platform Info:
OS: macOS (x86_64-apple-darwin18.6.0)
CPU: Intel(R) Core(TM) i5-4260U CPU @ 1.40GHz
WORD_SIZE: 64
LIBM: libopenlibm
LLVM: libLLVM-6.0.1 (ORCJIT, haswell)
定时代码:
using BenchmarkTools
benchmark_strings = ["{()()[()]()}", "{()()[())]()}"]
for s in benchmark_strings
b_old = @benchmark matching_brackets_old("$s") samples=100000 seconds=30
b_new = @benchmark matching_brackets("$s") samples=100000 seconds=30
println("For String=", s)
println(b_old)
println(b_new)
println(judge(minimum(b_new), minimum(b_old)))
println("Result: ", matching_brackets(s))
end
结果:
For String={()()[()]()}
Trial(8.177 μs)
Trial(9.197 μs)
TrialJudgement(+12.48% => regression)
Result: true
For String={()()[())]()}
Trial(8.197 μs)
Trial(9.202 μs)
TrialJudgement(+12.27% => regression)
Result: false
编辑
我混淆了审判的顺序,所以正如弗朗索瓦·费沃特所说,第一版更快。我的问题仍然是:为什么?我在我的机器上没有观察到相同的情况:在我的测试中,版本I对于两个字符串都更快:
julia> versioninfo()
Julia Version 1.3.0
Commit 46ce4d7933 (2019-11-26 06:09 UTC)
Platform Info:
OS: Linux (x86_64-pc-linux-gnu)
CPU: Intel(R) Core(TM) i5-6200U CPU @ 2.30GHz
WORD_SIZE: 64
LIBM: libopenlibm
LLVM: libLLVM-6.0.1 (ORCJIT, skylake)
Environment:
JULIA_PROJECT = @.
julia> @btime matching_brackets_old("{()()[()]()}")
716.443 ns (18 allocations: 800 bytes)
true
julia> @btime matching_brackets("{()()[()]()}")
761.434 ns (19 allocations: 832 bytes)
true
julia> @btime matching_brackets_old("{()()[())]()}")
574.847 ns (15 allocations: 752 bytes)
false
julia> @btime matching_brackets("{()()[())]()}")
612.793 ns (16 allocations: 784 bytes)
false
我认为(但这是一个大胆的猜测),当字符串大小增加时,for循环和高阶函数之间的差异变得越来越小
但是,我鼓励您更仔细地查看
order\u arr
变量:正如目前编写的那样,它属于Vector{Any}
类型,与任何抽象类型值的容器一样,它会影响性能。以下版本通过具体键入order\u arr
的元素执行得更好:
function matching_brackets_new(s::AbstractString)
close_open_map = Dict('}' => '{', ')' => '(', ']' => '[')
# Make sure the compiler knows about the type of elements in order_arr
order_arr = eltype(s)[] # or order_arr = Char[]
for char in s
if char in values(close_open_map)
push!(order_arr, char)
elseif (char in keys(close_open_map)) &&
(isempty(order_arr) || (close_open_map[char] != pop!(order_arr)))
return false
end
end
return isempty(order_arr)
end
屈服:
julia> @btime matching_brackets_new("{()()[()]()}")
570.641 ns (18 allocations: 784 bytes)
true
julia> @btime matching_brackets_new("{()()[())]()}")
447.758 ns (15 allocations: 736 bytes)
false
既然
judge
的错误已经解决,答案可能是通常的警告:函数调用,就像在本例中传递给all
的闭包所导致的那样,是非常优化的,但不是免费的
为了获得真正的改进,我建议,除了使堆栈类型稳定(这在这里不是什么大问题)之外,还可以通过调用中的和键来消除隐式执行的迭代。只做一次就足够了,不用字典:
const MATCHING_PAIRS = ('{' => '}', '(' => ')', '[' => ']')
function matching_brackets(s::AbstractString)
stack = Vector{eltype(s)}()
for c in s
for (open, close) in MATCHING_PAIRS
if c == open
push!(stack, c)
elseif c == close
if isempty(stack) || (pop!(stack) != open)
return false
end
end
end
end
return isempty(stack)
end
通过在元组上展开内部循环,甚至可以挤出更多的时间:
function matching_brackets_unrolled(s::AbstractString)
stack = Vector{eltype(s)}()
for c in s
if (c == '(') || (c == '[') || (c == '{')
push!(stack, c)
elseif (c == ')')
if isempty(stack) || (pop!(stack) != '(')
return false
end
elseif (c == ']')
if isempty(stack) || (pop!(stack) != '[')
return false
end
elseif (c == '}')
if isempty(stack) || (pop!(stack) != '{')
return false
end
end
end
return isempty(stack)
end
这有点难看,当然也不能很好地扩展。我的基准测试(matching_方括号_new
是您的第二个版本,matching_方括号
我的第一个版本):
更新:如果在第一个版本中插入break
s,为了真正避免不必要的循环,计时几乎无法区分,代码很好:
function matching_brackets(s::AbstractString)
stack = Vector{eltype(s)}()
for c in s
for (open, close) in MATCHING_PAIRS
if c == open
push!(stack, c)
break
elseif c == close
if isempty(stack) || (pop!(stack) != open)
return false
end
break
end
end
end
return isempty(stack)
end
与
由于这是一个有效的实现,我建议您也在上询问此代码以获得更多反馈。@phipsgabler:关于为什么一个版本更快的细节的问题完全在本主题中(只要它包括有关测试设置的详细信息,包括编译器版本/选项和硬件)。关于这段代码的CR问题可能是要求反馈它是否有效(或者写得好),而不是试图解释现有的性能差异。@PeterCordes绝对,我不是说它不在主题上。我只是建议这样做,因为代码正在运行,作为一个例子,我想给出更多的反馈,这反过来也不会是一个很好的答案,但是在CR上。你有什么样的机器?英特尔天湖?AMD Zen+?多里托?还有什么编译器版本/选项?谢谢你的回答。我尝试用Char[]
声明数组的类型,但这两个字符串没有区别。我将在一秒钟内尝试一个更长的字符串。Jup,对于更大的字符串有很大的不同。谢谢你的回答。只有一个问题:我认为您需要将&&
替换为|
,并删除展开函数中isempty前面的not(!
)。哦,的确,这在重写过程中丢失了。@Luapulu看到我的更新了——这个好的版本几乎可以与另一个版本一样快,如果一个人没有忘记打破内部循环,就像我所做的那样…:)啊,是的,我想知道你为什么不爆发。谢谢<代码>用于(关闭,打开)
应该是用于(打开,关闭)
julia> versioninfo()
Julia Version 1.3.1
Commit 2d5741174c (2019-12-30 21:36 UTC)
Platform Info:
OS: Linux (x86_64-pc-linux-gnu)
CPU: Intel(R) Core(TM) i7 CPU 960 @ 3.20GHz
WORD_SIZE: 64
LIBM: libopenlibm
LLVM: libLLVM-6.0.1 (ORCJIT, nehalem)
# NOT MATCHING
julia> @benchmark matching_brackets_new("{()()[())]()}")
BenchmarkTools.Trial:
memory estimate: 784 bytes
allocs estimate: 16
--------------
minimum time: 674.844 ns (0.00% GC)
median time: 736.200 ns (0.00% GC)
mean time: 800.935 ns (6.54% GC)
maximum time: 23.831 μs (96.16% GC)
--------------
samples: 10000
evals/sample: 160
julia> @benchmark matching_brackets_old("{()()[())]()}")
BenchmarkTools.Trial:
memory estimate: 752 bytes
allocs estimate: 15
--------------
minimum time: 630.743 ns (0.00% GC)
median time: 681.725 ns (0.00% GC)
mean time: 753.937 ns (6.41% GC)
maximum time: 23.056 μs (94.19% GC)
--------------
samples: 10000
evals/sample: 171
julia> @benchmark matching_brackets("{()()[())]()}")
BenchmarkTools.Trial:
memory estimate: 112 bytes
allocs estimate: 2
--------------
minimum time: 164.883 ns (0.00% GC)
median time: 172.900 ns (0.00% GC)
mean time: 186.523 ns (4.33% GC)
maximum time: 5.428 μs (96.54% GC)
--------------
samples: 10000
evals/sample: 759
julia> @benchmark matching_brackets_unrolled("{()()[())]()}")
BenchmarkTools.Trial:
memory estimate: 112 bytes
allocs estimate: 2
--------------
minimum time: 134.459 ns (0.00% GC)
median time: 140.292 ns (0.00% GC)
mean time: 150.067 ns (5.84% GC)
maximum time: 5.095 μs (96.56% GC)
--------------
samples: 10000
evals/sample: 878
# MATCHING
julia> @benchmark matching_brackets_old("{()()[()]()}")
BenchmarkTools.Trial:
memory estimate: 800 bytes
allocs estimate: 18
--------------
minimum time: 786.358 ns (0.00% GC)
median time: 833.873 ns (0.00% GC)
mean time: 904.437 ns (5.43% GC)
maximum time: 29.355 μs (96.88% GC)
--------------
samples: 10000
evals/sample: 106
julia> @benchmark matching_brackets_new("{()()[()]()}")
BenchmarkTools.Trial:
memory estimate: 832 bytes
allocs estimate: 19
--------------
minimum time: 823.597 ns (0.00% GC)
median time: 892.506 ns (0.00% GC)
mean time: 981.381 ns (5.98% GC)
maximum time: 47.308 μs (97.84% GC)
--------------
samples: 10000
evals/sample: 77
julia> @benchmark matching_brackets("{()()[()]()}")
BenchmarkTools.Trial:
memory estimate: 112 bytes
allocs estimate: 2
--------------
minimum time: 206.062 ns (0.00% GC)
median time: 214.481 ns (0.00% GC)
mean time: 227.385 ns (3.38% GC)
maximum time: 6.890 μs (96.22% GC)
--------------
samples: 10000
evals/sample: 535
julia> @benchmark matching_brackets_unrolled("{()()[()]()}")
BenchmarkTools.Trial:
memory estimate: 112 bytes
allocs estimate: 2
--------------
minimum time: 160.186 ns (0.00% GC)
median time: 164.752 ns (0.00% GC)
mean time: 180.794 ns (4.95% GC)
maximum time: 5.751 μs (97.03% GC)
--------------
samples: 10000
evals/sample: 800
function matching_brackets(s::AbstractString)
stack = Vector{eltype(s)}()
for c in s
for (open, close) in MATCHING_PAIRS
if c == open
push!(stack, c)
break
elseif c == close
if isempty(stack) || (pop!(stack) != open)
return false
end
break
end
end
end
return isempty(stack)
end
julia> @benchmark matching_brackets_unrolled("{()()[())]()}")
BenchmarkTools.Trial:
memory estimate: 112 bytes
allocs estimate: 2
--------------
minimum time: 137.574 ns (0.00% GC)
median time: 144.978 ns (0.00% GC)
mean time: 165.365 ns (10.44% GC)
maximum time: 9.344 μs (98.02% GC)
--------------
samples: 10000
evals/sample: 867
julia> @benchmark matching_brackets("{()()[())]()}") # with breaks
BenchmarkTools.Trial:
memory estimate: 112 bytes
allocs estimate: 2
--------------
minimum time: 148.255 ns (0.00% GC)
median time: 155.231 ns (0.00% GC)
mean time: 175.245 ns (9.62% GC)
maximum time: 9.602 μs (98.31% GC)
--------------
samples: 10000
evals/sample: 839