Riscrivendo un semplice programma da C# a Go, ho trovato l'eseguibile risultante da 3 a 4 volte più lento. Esattamente la versione Go utilizza da 3 a 4 volte più CPU. È sorprendente perché il codice fa molti I/O e non dovrebbe consumare una quantità significativa di CPU.Perché vai usare cgo su Windows per un semplice File.Write?
Ho realizzato una versione molto semplice facendo solo scritture sequenziali e realizzato benchmark. Ho eseguito gli stessi benchmark su Windows 10 e Linux (Debian Jessie). Il tempo non può essere comparato (non gli stessi sistemi, dischi, ...) ma il risultato è interessante.
sto usando la stessa versione Go su entrambe le piattaforme: 1.6
In Windows os.File.Write uso CGO (vedi runtime.cgocall
di seguito), non su Linux. Perché ?
Ecco il programma 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
}
Il test benchmark (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)
}
Il risultato di Windows (con 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
Il risultato di Linux (senza 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
Ricordo di aver letto da qualche parte che i sistemi operativi Windows non hanno un'interfaccia di syscall come fanno i sistemi unix ed espongono invece un'API C. Non sono sicuro di quanto sia vero. –
Potrei sbagliarmi, ma guardando https://github.com/golang/go/blob/master/src/syscall/zsyscall_windows.go, sembra come se tutti i sysc fossero passati attraverso cgo, ma non ho molta familiarità con finestre. questa domanda si adatterebbe meglio sulla ML golanella. – OneOfOne
Infatti Windows * do * ha syscalls. Il problema è che, contrariamente ad alcuni altri kernel del sistema operativo (incluso Linux) che hanno tabelle di numeri di syscall stabili, che si estendono solo aggiungendo nuove syscalls, Windows non ha mai pubblicato questi numeri, e * fanno * differiscono tra diverse versioni dei kernel di questa famiglia di sistemi operativi. E potrebbero persino essere legittimamente diversi tra, diciamo, diversi service pack. Poiché l'unico modo documentato per accedere a queste syscalls è tramite DLL, è ciò che si suppone che Go stia facendo. – kostix