New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Avoid returning slice into buffer from Readfile #1033
Conversation
Signed-off-by: eriknordmark <erik@zededa.com>
before
PR applied
Seems nothing changed. And I try to change get new Process() every time, also nothing changed.
Am I missed something? |
Your test is making p.Name() available for garbage collection each time around the loop, hence you can not see how much memory is held as a result of assigning some variable to p.Name(). Note that it isn't a leak; it is a memory usage expansion where retaining a very short slice is keeping a reference on a much large chunk of memory for the array underlaying the slice. To see that p.Name() is using holding a reference on a large chunk of memory you have to keep it around and compare with the case when it is reallocated to use a buffer which is only the size of the name string itself (plus whatever golang overhead for the slice). The code below shows that (by doing the reallocation on top of gopsutil - I'm too clueless to build the package with my PR and use it for the test). The output without fix is: After: Thus about 60 byte per p.Name vs 2.2 kbyte per p.Name. Note that I don't think the user of gopsutil/process should need to know that p.Name has a reference on a large buffer and needs to be reallocated to save space, hence fixing it in the caller as in this test program seems just wrong. But the test program shows the impact on the memory usage. Note that the real usecase is when saving the names of all the processes on the server, which could be thousands, and not saving a single process name over and over again as in this example code. Simplest code (I can also share code which dumps sorted output from runtime.MemProfileRecord showing where the allocations where made): package main
import (
"fmt"
"flag"
"os"
"runtime"
"github.com/shirou/gopsutil/process"
)
func getProcessNames(count int, realloc bool) []string {
var names []string
for i := 0; i < count; i++ {
p := testGetProcess()
name, _ := p.Name()
if realloc {
name = string([]byte(name))
}
names = append(names, name)
}
return names
}
func testGetProcess() process.Process {
checkPid := os.Getpid() // process.test
ret, _ := process.NewProcess(int32(checkPid))
return *ret
}
func main() {
reallocPtr := flag.Bool("r", false, "realloc flag")
countPtr := flag.Int("c", 10000, "process count")
flag.Parse()
logMemUsage()
p := getProcessNames(*countPtr, *reallocPtr)
logMemUsage()
fmt.Printf("Number of processes %d\n", len(p))
}
func logMemUsage() {
var m runtime.MemStats
runtime.ReadMemStats(&m)
fmt.Printf("Alloc %d Mb, TotalAlloc %d Mb, Sys %d Mb, NumGC %d\n",
roundToMb(m.Alloc), roundToMb(m.TotalAlloc), roundToMb(m.Sys), m.NumGC)
}
func roundToMb(b uint64) uint64 {
kb := (b + 512) / 1024
mb := (kb + 512) / 1024
return mb
} |
Understand. This is realloc issue, so heap alloc keep low but GC is much more. Keep heap lower is better, so I merge this PR. Thank you for sharing your cool knowledge about memory! |
[process][linux] apply #1033 to v3.
The memory usage is quite ineffecient without this fix since the whole Readfile buffer can not be garbage collected due to the name and status short strings being slices with the underlying array being the Readfile buffer.