【发布时间】:2016-06-18 09:44:37
【问题描述】:
从 C# 重写一个简单的程序到 Go,我发现生成的可执行文件慢了 3 到 4 倍。特别是 Go 版本使用 3 到 4 倍的 CPU。令人惊讶的是,代码执行了很多 I/O,并且不应该消耗大量 CPU。
我制作了一个非常简单的版本,只进行顺序写入,并进行了基准测试。我在 Windows 10 和 Linux (Debian Jessie) 上运行了相同的基准测试。时间无法比较(不同的系统、磁盘等),但结果很有趣。
我在两个平台上使用相同的 Go 版本:1.6
在 Windows os.File.Write 上使用 cgo(请参阅下面的 runtime.cgocall),而不是在 Linux 上。为什么?
这里是 disk.go 程序:
package main
import (
"crypto/rand"
"fmt"
"os"
"time"
)
const (
// size of the test file
fullSize = 268435456
// size of read/write per call
partSize = 128
// path of temporary test file
filePath = "./bigfile.tmp"
)
func main() {
buffer := make([]byte, partSize)
seqWrite := func() error {
return sequentialWrite(filePath, fullSize, buffer)
}
err := fillBuffer(buffer)
panicIfError(err)
duration, err := durationOf(seqWrite)
panicIfError(err)
fmt.Printf("Duration : %v\n", duration)
}
// It's just a test ;)
func panicIfError(err error) {
if err != nil {
panic(err)
}
}
func durationOf(f func() error) (time.Duration, error) {
startTime := time.Now()
err := f()
return time.Since(startTime), err
}
func fillBuffer(buffer []byte) error {
_, err := rand.Read(buffer)
return err
}
func sequentialWrite(filePath string, fullSize int, buffer []byte) error {
desc, err := os.OpenFile(filePath, os.O_WRONLY|os.O_CREATE, 0666)
if err != nil {
return err
}
defer func() {
desc.Close()
err := os.Remove(filePath)
panicIfError(err)
}()
var totalWrote int
for totalWrote < fullSize {
wrote, err := desc.Write(buffer)
totalWrote += wrote
if err != nil {
return err
}
}
return nil
}
基准测试(disk_test.go):
package main
import (
"testing"
)
// go test -bench SequentialWrite -cpuprofile=cpu.out
// Windows : go tool pprof -text -nodecount=10 ./disk.test.exe cpu.out
// Linux : go tool pprof -text -nodecount=10 ./disk.test cpu.out
func BenchmarkSequentialWrite(t *testing.B) {
buffer := make([]byte, partSize)
err := sequentialWrite(filePath, fullSize, buffer)
panicIfError(err)
}
Windows 结果(使用 cgo):
11.68s of 11.95s total (97.74%)
Dropped 18 nodes (cum <= 0.06s)
Showing top 10 nodes out of 26 (cum >= 0.09s)
flat flat% sum% cum cum%
11.08s 92.72% 92.72% 11.20s 93.72% runtime.cgocall
0.11s 0.92% 93.64% 0.11s 0.92% runtime.deferreturn
0.09s 0.75% 94.39% 11.45s 95.82% os.(*File).write
0.08s 0.67% 95.06% 0.16s 1.34% runtime.deferproc.func1
0.07s 0.59% 95.65% 0.07s 0.59% runtime.newdefer
0.06s 0.5% 96.15% 0.28s 2.34% runtime.systemstack
0.06s 0.5% 96.65% 11.25s 94.14% syscall.Write
0.05s 0.42% 97.07% 0.07s 0.59% runtime.deferproc
0.04s 0.33% 97.41% 11.49s 96.15% os.(*File).Write
0.04s 0.33% 97.74% 0.09s 0.75% syscall.(*LazyProc).Find
Linux 结果(没有 cgo):
5.04s of 5.10s total (98.82%)
Dropped 5 nodes (cum <= 0.03s)
Showing top 10 nodes out of 19 (cum >= 0.06s)
flat flat% sum% cum cum%
4.62s 90.59% 90.59% 4.87s 95.49% syscall.Syscall
0.09s 1.76% 92.35% 0.09s 1.76% runtime/internal/atomic.Cas
0.08s 1.57% 93.92% 0.19s 3.73% runtime.exitsyscall
0.06s 1.18% 95.10% 4.98s 97.65% os.(*File).write
0.04s 0.78% 95.88% 5.10s 100% _/home/sam/Provisoire/go-disk.sequentialWrite
0.04s 0.78% 96.67% 5.05s 99.02% os.(*File).Write
0.04s 0.78% 97.45% 0.04s 0.78% runtime.memclr
0.03s 0.59% 98.04% 0.08s 1.57% runtime.exitsyscallfast
0.02s 0.39% 98.43% 0.03s 0.59% os.epipecheck
0.02s 0.39% 98.82% 0.06s 1.18% runtime.casgstatus
【问题讨论】:
-
我记得在某处读到过,Windows 操作系统没有像 unix 系统那样的系统调用接口,而是公开了一个 C API。不知道那是多么真实。
-
我可能是错的,但看看github.com/golang/go/blob/master/src/syscall/…,似乎所有的系统调用都通过cgo,但我对windows并不熟悉。这个问题更适合 golangnuts ML。
-
事实上Windows确实有系统调用。问题在于,与某些其他操作系统内核(包括 Linux)相反,它们具有稳定的系统调用编号表,只能通过添加新的系统调用来扩展,Windows 从未发布这些编号,并且它们确实在不同的该系列操作系统的内核版本。它们甚至可能在不同的服务包之间存在差异。由于访问这些系统调用的唯一记录方式是通过 DLL,这就是 Go 应该做的事情。
-
请注意,您观察到的是存在
bufio包的原因之一:系统调用在任何具有特权进程分离的操作系统中都是昂贵的,因为执行上下文必须是“门控”从用户空间到内核空间并返回。如果您使用使用 32KiB 缓冲区 IIRC 的io.Copy(),我认为结果会好很多。 -
@OneOfOne:“我对 Windows 不是很熟悉。这个问题更适合 golangnuts。”没有必要在 golang-nuts 上提问。 Stack Overflow 上有很多 Windows 专家。