-
-
Notifications
You must be signed in to change notification settings - Fork 80
High Memory Usage with ethtool.Stats Function in Go 1.24+ #104
Description
Description:
We have observed a significant increase in memory churn when using the ethtool.Stats function with Go version 1.24 and onwards. This is due to a change in the Go compiler's memory allocation strategy, where objects larger than 128KB are now allocated on the heap instead of the stack. Previously, this threshold was in the range of MBs.
As a result, every time the Stats function is called, large objects like gstrings and stats are allocated on the heap, leading to increased memory pressure and frequent garbage collection.
Steps to Reproduce:
- Use Go 1.24 or newer.
- Call the
ethtool.Statsfunction in a program.
Observed Behavior:
- A large amount of memory is allocated on the heap for
gstringsandstatsvariables during each call toethtool.Stats. - This memory is then left for the garbage collector to clean up, resulting in frequent garbage collection cycles and potential performance degradation.
Expected Behavior:
- Memory allocation behavior should be optimized to avoid excessive heap allocations for large local variables like
gstringsandstats. - A consistent and efficient allocation strategy should be implemented to minimize garbage collection overhead.
Reference:
This behavior is linked to changes in Go's escape analysis and memory allocation strategy introduced in Go 1.24. For more details, refer to the related Golang issue: golang/go#73536.
Analysis with pprof:
This issue can be easily analyzed using pprof. Below is a sample program that demonstrates the high memory churn:
package main
import (
"fmt"
"log"
"net"
"time"
"net/http"
_ "net/http/pprof"
"github.com/safchain/ethtool"
)
func main() {
go func() {
log.Println(http.ListenAndServe("localhost:8000", nil))
}()
ifs, err := net.Interfaces()
if err != nil {
log.Fatal(err)
}
if len(ifs) == 0 {
log.Fatal("No interfaces found")
}
ticker := time.NewTicker(10 * time.Second)
defer ticker.Stop()
for {
select {
case <-ticker.C:
eth, err := ethtool.NewEthtool()
if err != nil {
log.Fatalf("Error creating ethtool: %v", err)
}
for _, iface := range ifs {
stats, err := eth.Stats(iface.Name)
if err != nil {
log.Printf("Error getting stats for %s: %v", iface.Name, err)
continue
}
fmt.Printf("Stats for %s: %+v\n", iface.Name, stats)
}
eth.Close()
}
}
}Using pprof to analyze the heap, we can observe a significant difference in the alloc_space profile between Go versions 1.23 and 1.24. This confirms the issue with increased heap allocations in Go 1.24.
Thank you for addressing this!