我正在尝试在Ocaml中实现utf8解码作为学习项目。为了检查性能,我正在针对go标准库进行基准测试。
这是go代码:
package main
import (
"fmt"
"time"
"unicode/utf8"
)
func main() {
start := time.Now()
for i := 0; i < 1000000000; i++ {
utf8.ValidRune(23450)
}
elapsed := time.Since(start)
fmt.Println(elapsed)
}
当我运行它时,我得到:
go build b.go
./b
344.979492ms
我决定在ocaml中写一个等价物:
let min = 0x0000
let max = 0x10FFFF
let surrogateMin = 0xD800
let surrogateMax = 0xDFFF
let validUchar c =
if (0 <= c && c < surrogateMin) then
true
else if (surrogateMax < c && c <= max) then
true
else
false
let time f x =
let t = Sys.time () in
let _ = f x in
let t2 = Sys.time () in
let diff = (t2 -. t) *. 1000. in
print_endline ((string_of_float diff) ^ "ms")
let test () =
for i = 0 to 1000000000 do
let _ = validUchar 23450 in
()
done
let () = time test ()
输出:
ocamlopt bMl.ml -o bMl
./bMl
2041.075ms
ocaml等价物基本上复制了来自https://golang.org/src/unicode/utf8/utf8.go#L517的go stdlib的实现
为什么ocaml代码这么慢?
正如观察到的,你应该使用Unix.gettimeofday
来测量挂钟时间。但是,您可以使用Sys.opaque_identity
来阻止OCaml优化无用的操作,并且可以使用ignore
来“返回单位”而不是表达式的通常值。共:
let time f x =
let t = Unix.gettimeofday () in
ignore (Sys.opaque_identity (f x));
let t2 = Unix.gettimeofday () in
...
let test () =
for i = 1 to 1_000_000_000 do
ignore (Sys.opaque_identity (validUchar 23450));
done
请注意i = 1
,如果你想要十亿次迭代,你需要它(在添加下划线之前我无法分辨的数字是10亿,OCaml允许)。以前,您正在测量10亿加1次迭代。这不是那个区别。
您对validUchar
的详细定义并未对其性能产生任何影响。请写一个微基准并确认。
最后,在进行了上面建议的更改并以更自然的方式编写validUchar
之后,我得到了一个与Go运行时相同的OCaml运行时......在ocamlopt参数中添加-O3之后。并且很容易确认这不是由于编译器“优化操作” - 在f x
中注释掉time
调用导致运行时间为0或接近0的值,如1.19e-06。
不要因为你对这个问题的回答而气馁。但是,请期待任何一种“为什么这个基准有这个结果?”对编程论坛的问题将得到类似的回答。
Sys.time
不应该用于时间测量,因为它返回处理器时间,而不是实时。 Unix.gettimeofday
函数是一个更好的候选者。或者,您可以使用time
命令从shell计时程序。
作为旁注,基准测试很难,而且很容易产生误导性的结果。在你的特定情况下,如果你打开优化,两个编译器都会删除计算,因为它们没有被使用,并且会产生什么都不做的代码,因此相当快:)