package benchmark import ( "runtime" "syscall" "time" ) // Recorder erfasst den Ressourcenverbrauch um einen Lauf herum. // // Er misst, statt zu schätzen: Rechenzeit kommt aus getrusage des // Betriebssystems, der Speicherhöchststand aus der Go-Laufzeit. Beide Werte // werden **vorher und nachher** gelesen und voneinander abgezogen — sonst // enthielte die Messung alles, was der Prozess vor dem Lauf getan hat. type Recorder struct { // startTime ist der Beginn der Messung. startTime time.Time // startUsage sind die Verbrauchswerte zu Beginn. startUsage syscall.Rusage // startMemoryStats sind die Speicherwerte zu Beginn. startMemoryStats runtime.MemStats // peakHeapBytes ist der höchste beobachtete Haufenstand. peakHeapBytes int64 // stopSampling beendet die Abtastung. stopSampling chan struct{} // samplingDone meldet das Ende der Abtastung. samplingDone chan struct{} } // samplingInterval ist der Abstand zweier Speicherabtastungen. // // Fünfzig Millisekunden: Fein genug, um eine Spitze beim Lesen einer großen // Datei zu treffen, grob genug, dass die Messung selbst nicht ins Gewicht // fällt. Ohne Abtastung bliebe nur der Endstand — und der ist nach einer // Speicherbereinigung niedrig, gerade weil der Höchststand hoch war. const samplingInterval = 50 * time.Millisecond // StartRecording beginnt eine Messung. func StartRecording() *Recorder { recorder := &Recorder{ startTime: time.Now(), stopSampling: make(chan struct{}), samplingDone: make(chan struct{}), } _ = syscall.Getrusage(syscall.RUSAGE_SELF, &recorder.startUsage) runtime.ReadMemStats(&recorder.startMemoryStats) go recorder.sampleMemory() return recorder } // sampleMemory tastet den Speicherstand ab, bis die Messung endet. func (recorder *Recorder) sampleMemory() { defer close(recorder.samplingDone) samplingTicker := time.NewTicker(samplingInterval) defer samplingTicker.Stop() for { select { case <-recorder.stopSampling: return case <-samplingTicker.C: var currentMemoryStats runtime.MemStats runtime.ReadMemStats(¤tMemoryStats) if currentHeap := int64(currentMemoryStats.HeapAlloc); currentHeap > recorder.peakHeapBytes { recorder.peakHeapBytes = currentHeap } } } } // Stop beendet die Messung und liefert die Verbrauchswerte. func (recorder *Recorder) Stop() ResourceUsage { elapsedTime := time.Since(recorder.startTime) close(recorder.stopSampling) <-recorder.samplingDone var endUsage syscall.Rusage _ = syscall.Getrusage(syscall.RUSAGE_SELF, &endUsage) var endMemoryStats runtime.MemStats runtime.ReadMemStats(&endMemoryStats) // Der letzte Stand zählt mit: Endet der Lauf zwischen zwei Abtastungen, // bliebe die Spitze sonst unbemerkt. peakMemoryBytes := recorder.peakHeapBytes if endHeap := int64(endMemoryStats.HeapAlloc); endHeap > peakMemoryBytes { peakMemoryBytes = endHeap } return ResourceUsage{ WallClockSeconds: elapsedTime.Seconds(), UserCPUSeconds: timevalDifference(endUsage.Utime, recorder.startUsage.Utime), SystemCPUSeconds: timevalDifference(endUsage.Stime, recorder.startUsage.Stime), PeakMemoryBytes: peakMemoryBytes, AllocatedBytes: int64(endMemoryStats.TotalAlloc - recorder.startMemoryStats.TotalAlloc), GarbageCollections: int(endMemoryStats.NumGC - recorder.startMemoryStats.NumGC), } } // timevalDifference liefert den Abstand zweier Zeitwerte in Sekunden. func timevalDifference(endValue syscall.Timeval, startValue syscall.Timeval) float64 { return float64(endValue.Sec-startValue.Sec) + float64(endValue.Usec-startValue.Usec)/1_000_000 }