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
Add prealloc check #10806
Add prealloc check #10806
Conversation
test-me-please |
Need to build a new |
06e2aed
to
e092974
Compare
test-me-please |
Needed for cilium/cilium#10806 Signed-off-by: Tobias Klauser <tklauser@distanz.ch>
|
What's the rationale for requiring to always prealloc slices? I understand that preallocating is important for performance-sensitive code sections but I fear that requiring to always prealloc slices has potential to introduce subtle bugs (by getting the length wrong typically or if the loop body is later modified to not iterate over all elements, etc). |
It's not requiring to always preallocate slices, only if it detects that the slice is later This isn't decided yet but it was something that people suggested while reviewing #10716 and #10751. I can create a thread on Cilium slack to discuss this further if somebody feels the need for it. |
Sorry, forgot to answer this part: the rationale here is to pre-allocate slices where we already know the capacity in order to avoid re-allocations in a loop, which could temporarily spike memory usage. The Go runtime is known to be slow to return freed memory to the OS since Go 1.13, so from the OS perspective it might look like the process allocated more memory than it actually is using. So pre-allocating in this cases helps to reduce memory churn. |
This is a good concern to have, it's a well-known shortcoming of Go slice syntax. Are there tools we can enable by default to help us to make sure we don't mess this up? Or some best practices we document to avoid this? Relying on code review from others to pick up on bugs in this area is not terribly reliable though I think that's partly what we're relying on right now. |
Note that by forcing to preallocate, you might also end up allocating more memory than necessary which works against your goal of reducing overall memory consumption as part of #10056. For example, take this code from func getCiliumPods(namespace, label string) ([]string, error) {
output, err := execCommand(fmt.Sprintf("kubectl -n %s get pods -l %s", namespace, label))
if err != nil {
return nil, err
}
lines := strings.Split(output, "\n")
ciliumPods := make([]string, 0, len(lines))
for _, l := range lines {
if !strings.HasPrefix(l, "cilium") {
continue
}
// NAME READY STATUS RESTARTS AGE
// cilium-cfmww 0/1 Running 0 3m
// ^
pod := strings.Split(l, " ")[0]
ciliumPods = append(ciliumPods, pod)
}
return ciliumPods, nil
} In a real-case scenario, I'd expect many more non-cilium pods in a cluster than cilium pods. Let's assume that each node hosts 10 pods and among these 10 you have 2 cilium pods (agent+operator). With this example with conservative numbers, you already end up allocating 5 times more memory than necessary as most lines will be skipped in the loop. |
Note that in the example above we use Your point is valid in general though and we should make sure to only preallocate in cases where we're really sure that we will use as many entries into the slice. For cases where this wasn't clear, I changed slice definitions to |
Fair enough in this case :) I was just trying to point out a potential issue with this approach.
If I recall correctly, internally, when slices are reallocated their capacity is doubled for every reallocation (can't find a reference atm). In practice, depending on the case, it might be that using I guess that what I'm trying to say is that the question of memory optimization is not so simple after all and having a tool that complains every time a slice could be preallocated might not be the best thing. What I fear more is the introduction of subtle bugs. For instance, a case where a contributor sees the warning, decides to Maybe the solution would be as simple as patching |
Reallocation seems to depend on the size of the stored type: https://play.golang.org/p/EABWUjgAJww And it might also change depending on Go version. But yeah, agree that it all depends on the case :)
Fully agree. As @joestringer already pointed out Go's In any case I think we also have the problem of developers getting it wrong without the use of the
Thanks for the suggestion, I like that solution. Will prepare a PR against upstream and hope they will take it. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Only one nit. The rest looks good.
You mentioned some false positives reported by prealloc on the weekly meeting. I guess that's not the case anymore? :)
newcap := old.cap
doublecap := newcap + newcap
if cap > doublecap {
newcap = cap
} else {
if old.len < 1024 {
newcap = doublecap
} else {
// Check 0 < newcap to detect overflow
// and prevent an infinite loop.
for 0 < newcap && newcap < cap {
newcap += newcap / 4
}
// Set newcap to the requested cap when
// the newcap calculation overflowed.
if newcap <= 0 {
newcap = cap
}
}
} In the end it's a little more subtle that just doubling. Anyway, this was just to satisfy curiosity and as you pointed out, it might change depending on the version of Go. |
[...]
Nice, thanks for digging this up. It might even be a bit more involved as it looks like the compiler might be able to optimize some cases before calling |
e092974
to
94ec042
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM but I wonder if we should drop the last commit of #10823
This makes the prealloc check to be introduced in the next commit pass. Signed-off-by: Tobias Klauser <tklauser@distanz.ch>
Run slice preallocation checks as part of `make precheck`. Signed-off-by: Tobias Klauser <tklauser@distanz.ch>
94ec042
to
d0c1883
Compare
Needed for cilium/cilium#10806 Signed-off-by: Tobias Klauser <tklauser@distanz.ch>
Rebased after #10823 was merged. Also built a new Before this can be successfully tested, cilium/packer-ci-build#204 needs to be merged and a new box image be built. |
As discussed in today's Cilium community meeting, we will not enable I'll close this PR for now and will reopen it the remaining fixes which make sense. |
As discussed previously, this check shouldn't be run by default as part of the `precheck` Makfile target due to the possibility of false positives (see #10806 for the previous approach). Thus, we'll run `prealloc` manually once at the end of a release cycle and manually review its results and thix them where appropriate. Signed-off-by: Tobias Klauser <tklauser@distanz.ch>
Sent PR #10913 instead. |
As discussed previously, this check shouldn't be run by default as part of the `precheck` Makfile target due to the possibility of false positives (see #10806 for the previous approach). Thus, we'll run `prealloc` manually once at the end of a release cycle and manually review its results and thix them where appropriate. Signed-off-by: Tobias Klauser <tklauser@distanz.ch>
As discussed previously, this check shouldn't be run by default as part of the `precheck` Makfile target due to the possibility of false positives (see #10806 for the previous approach). Thus, we'll run `prealloc` manually once at the end of a release cycle and manually review its results and thix them where appropriate. Signed-off-by: Tobias Klauser <tklauser@distanz.ch>
Run https://github.com/alexkohler/prealloc as part of the
precheck
make target. See #10716 and #10751 for related fixes found by this tool.Useful for #10056
Reviewable by commit, the first 3 commits are cleanups unrelated to the actual functionality.