我正在尝试在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代码这么慢?
答案 0 :(得分:8)
如您所见,您应该使用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
不要因为你对这个问题的回答而气馁。但做期望任何一种“为什么这个基准有这个结果?”对编程论坛的问题也会得到类似的回答。
答案 1 :(得分:5)
Sys.time
不应该用于时间测量,因为它会返回处理器时间,而不是实时时间。 Unix.gettimeofday
函数是一个更好的候选者。或者,您可以使用time
命令从shell计算程序。
作为旁注,基准测试很难,而且很容易产生误导性的结果。在您的特定情况下,如果您打开优化,两个编译器都将删除计算,因为它们未被使用并将生成无效的代码,因此相当快:)