调用 isSpaceAscii 时的性能问题
Performance issue when calling isSpaceAscii
我尝试从标准库调用 isSpaceAscii
,但性能比我自己的过程差。
重现代码:
import strutils
import std/monotimes
import stats
template timeIt(tag: string, iter: untyped, body: untyped) =
var st: RunningStat
for i in countup(1, iter):
let t0 = getMonoTime().ticks
body
let t1 = getMonoTime().ticks
let d = t1 - t0
st.push(d.float64)
echo tag, ": ", st.min
proc isSpace(c: char): bool =
result = c in Whitespace
when isMainModule:
# check eqaulity
for i in 1..255:
let c = char(i)
doAssert isSpace(c) == isSpaceAscii(c)
timeIt "isSpaceAscii", 1000:
for i in 1..255:
let c = char(i)
discard isSpaceAscii(c)
timeIt "isSpace", 1000:
for i in 1..255:
let c = char(i)
discard isSpace(c)
基准测试结果:
$ nim compile -d:release --verbosity:0 --hints:off --run test.nim
isSpaceAscii: 380.0
isSpace: 20.0
编译器版本:
$ nim -V
Nim Compiler Version 1.4.2 [Linux: amd64]
为什么 isSpace
比 isSpaceAscii
快?
基准测试很难,因为您并不总是在衡量您认为自己在衡量的东西。
您看到的明显差异是因为 isSpace
循环不执行任何操作,并且与 isSpace
函数位于同一编译单元中,因此编译器可以对其进行优化离开
as you can see on godbolt
如果您改为使用 -d:release -d:lto
编译,编译器将执行 link 时间优化,并将优化两个版本。
$ nim c -d:release -d:lto -r test.nim
isSpaceAscii: 16
isSpace: 16
我们只是在测量循环开销。
要真正比较 isSpaceAscii 和 isSpace,就编译器而言,它们需要做实际工作。
import std/[strutils,monotimes,stats]
template timeIt(tag: string, iters: int, body: untyped) =
var st: RunningStat
when declared(warmup): #BUG
for i in 1..iters:
body
for i in 1..iters:
let t0 = getMonoTime().ticks
body
let t1 = getMonoTime().ticks
st.push((t1-t0).float64)
echo tag,": ", st.min
proc isSpace(c:char):bool = c in Whitespace
template badloop(procname: untyped) =
for i in 1..255:
let c = char(i)
discard procname(c)
template goodloop(procname: untyped) =
var x: int
for i in 1..255:
let c = char(i)
if procname(c): inc x
when isMainModule:
let nruns = 1000
for i in 1..255:
doAssert isSpace(i.char) == isSpaceAscii(i.char)
timeit "isSpaceAscii, good",nruns:
goodloop(isSpaceAscii)
timeit "isSpace, good",nruns:
goodloop(isSpace)
timeit "isSpaceAscii, bad",nruns:
badloop(isSpaceAscii)
timeit "isSpace, bad",nruns:
badloop(isSpace)
结果:
$ nim -d:release -d:lto -r test.nim
isSpaceAscii, good: 439.0
isSpace, good: 382.0
isSpaceAscii, bad: 17.0
isSpace, bad: 17.0
接近了,但好像还是有出入,这是怎么回事?
T̶h̶e̶ ̶c̶p̶u̶ ̶h̶a̶s̶ ̶w̶a̶r̶m̶e̶d̶ ̶u̶p̶ ̶b̶y̶ ̶t̶h̶e̶ ̶t̶i̶m̶e̶ ̶i̶t̶ ̶g̶e̶t̶s̶ ̶t̶o̶ ̶t̶h̶e̶ ̶s̶e̶c̶o̶n̶d̶ ̶t̶e̶s̶t̶,̶ ̶a̶n̶d̶ ̶i̶t̶ ̶g̶o̶e̶s̶ ̶f̶a̶s̶t̶e̶r̶.̶ (Edit due to a bug -d:warmup
didn't change the code,差异是由于编译器做出了不同的优化选择。)
再试一次,t̶u̶r̶n̶i̶n̶g̶̶o̶n̶̶t̶h̶e̶̶w̶a̶r̶m̶u̶p̶̶l̶o̶o̶p̶我们已经添加到timeit
,这次全力以赴使用-d:danger
$ nim c -d:danger -d:lto -d:warmup -r test.nim
isSpaceAscii, good: 237.0
isSpace, good: 234.0
isSpaceAscii, bad: 15.0
isSpace, bad: 15.0
差不多。
编辑 好像是为了突出微基准测试的不可测性,我对热身部分的看法完全错了。我应该写 when defined(warmup)
,由于那个错误,我的“预热”代码实际上从未 运行。事实上,由于我们用了 最快的 时间,所以前几 运行 秒已经足够预热了。
从那时起,我已经 运行 了多个版本的代码,结果差异太大,无法得出更多结论,除了可能:
- 编译器的优化选择变化无常
- 基准测试很难
我尝试从标准库调用 isSpaceAscii
,但性能比我自己的过程差。
重现代码:
import strutils
import std/monotimes
import stats
template timeIt(tag: string, iter: untyped, body: untyped) =
var st: RunningStat
for i in countup(1, iter):
let t0 = getMonoTime().ticks
body
let t1 = getMonoTime().ticks
let d = t1 - t0
st.push(d.float64)
echo tag, ": ", st.min
proc isSpace(c: char): bool =
result = c in Whitespace
when isMainModule:
# check eqaulity
for i in 1..255:
let c = char(i)
doAssert isSpace(c) == isSpaceAscii(c)
timeIt "isSpaceAscii", 1000:
for i in 1..255:
let c = char(i)
discard isSpaceAscii(c)
timeIt "isSpace", 1000:
for i in 1..255:
let c = char(i)
discard isSpace(c)
基准测试结果:
$ nim compile -d:release --verbosity:0 --hints:off --run test.nim
isSpaceAscii: 380.0
isSpace: 20.0
编译器版本:
$ nim -V
Nim Compiler Version 1.4.2 [Linux: amd64]
为什么 isSpace
比 isSpaceAscii
快?
基准测试很难,因为您并不总是在衡量您认为自己在衡量的东西。
您看到的明显差异是因为 isSpace
循环不执行任何操作,并且与 isSpace
函数位于同一编译单元中,因此编译器可以对其进行优化离开
as you can see on godbolt
如果您改为使用 -d:release -d:lto
编译,编译器将执行 link 时间优化,并将优化两个版本。
$ nim c -d:release -d:lto -r test.nim
isSpaceAscii: 16
isSpace: 16
我们只是在测量循环开销。
要真正比较 isSpaceAscii 和 isSpace,就编译器而言,它们需要做实际工作。
import std/[strutils,monotimes,stats]
template timeIt(tag: string, iters: int, body: untyped) =
var st: RunningStat
when declared(warmup): #BUG
for i in 1..iters:
body
for i in 1..iters:
let t0 = getMonoTime().ticks
body
let t1 = getMonoTime().ticks
st.push((t1-t0).float64)
echo tag,": ", st.min
proc isSpace(c:char):bool = c in Whitespace
template badloop(procname: untyped) =
for i in 1..255:
let c = char(i)
discard procname(c)
template goodloop(procname: untyped) =
var x: int
for i in 1..255:
let c = char(i)
if procname(c): inc x
when isMainModule:
let nruns = 1000
for i in 1..255:
doAssert isSpace(i.char) == isSpaceAscii(i.char)
timeit "isSpaceAscii, good",nruns:
goodloop(isSpaceAscii)
timeit "isSpace, good",nruns:
goodloop(isSpace)
timeit "isSpaceAscii, bad",nruns:
badloop(isSpaceAscii)
timeit "isSpace, bad",nruns:
badloop(isSpace)
结果:
$ nim -d:release -d:lto -r test.nim
isSpaceAscii, good: 439.0
isSpace, good: 382.0
isSpaceAscii, bad: 17.0
isSpace, bad: 17.0
接近了,但好像还是有出入,这是怎么回事?
T̶h̶e̶ ̶c̶p̶u̶ ̶h̶a̶s̶ ̶w̶a̶r̶m̶e̶d̶ ̶u̶p̶ ̶b̶y̶ ̶t̶h̶e̶ ̶t̶i̶m̶e̶ ̶i̶t̶ ̶g̶e̶t̶s̶ ̶t̶o̶ ̶t̶h̶e̶ ̶s̶e̶c̶o̶n̶d̶ ̶t̶e̶s̶t̶,̶ ̶a̶n̶d̶ ̶i̶t̶ ̶g̶o̶e̶s̶ ̶f̶a̶s̶t̶e̶r̶.̶ (Edit due to a bug -d:warmup
didn't change the code,差异是由于编译器做出了不同的优化选择。)
再试一次,t̶u̶r̶n̶i̶n̶g̶̶o̶n̶̶t̶h̶e̶̶w̶a̶r̶m̶u̶p̶̶l̶o̶o̶p̶我们已经添加到timeit
,这次全力以赴使用-d:danger
$ nim c -d:danger -d:lto -d:warmup -r test.nim
isSpaceAscii, good: 237.0
isSpace, good: 234.0
isSpaceAscii, bad: 15.0
isSpace, bad: 15.0
差不多。
编辑 好像是为了突出微基准测试的不可测性,我对热身部分的看法完全错了。我应该写 when defined(warmup)
,由于那个错误,我的“预热”代码实际上从未 运行。事实上,由于我们用了 最快的 时间,所以前几 运行 秒已经足够预热了。
从那时起,我已经 运行 了多个版本的代码,结果差异太大,无法得出更多结论,除了可能:
- 编译器的优化选择变化无常
- 基准测试很难