已合并
runtime: add ClearSpan experiment to clear spans on first allocation #61
cyf_123123创建于 5月13日
runtime: add ClearSpan experiment to clear spans on first allocation #61
已合并
共 6 个文件变更+88-2
| @@ -0,0 +1,8 @@ | |||
| 1 | +// Code generated by mkconsts.go. DO NOT EDIT. | ||
| 2 | + | ||
| 3 | +//go:build !goexperiment.clearspan | ||
| 4 | + | ||
| 5 | +package goexperiment | ||
| 6 | + | ||
| 7 | +const ClearSpan = false | ||
| 8 | +const ClearSpanInt = 0 | ||
| @@ -0,0 +1,8 @@ | |||
| 1 | +// Code generated by mkconsts.go. DO NOT EDIT. | ||
| 2 | + | ||
| 3 | +//go:build goexperiment.clearspan | ||
| 4 | + | ||
| 5 | +package goexperiment | ||
| 6 | + | ||
| 7 | +const ClearSpan = true | ||
| 8 | +const ClearSpanInt = 1 | ||
| @@ -137,4 +137,7 @@ type Flags struct { | |||
| 137 | 137 | ||
| 138 | // RevertCopyHashKeys enables the "revert" hash map implementation. | 138 | // RevertCopyHashKeys enables the "revert" hash map implementation. |
| 139 | RevertCopyHashKeys bool | 139 | RevertCopyHashKeys bool |
| 140 | + | ||
| 141 | + // Clear the span if necessary | ||
| 142 | + ClearSpan bool | ||
| 140 | } | 143 | } |
| @@ -0,0 +1,19 @@ | |||
| 1 | +// Copyright (c) Huawei Technologies Co., Ltd. 2025-2025. All rights reserved. | ||
| 2 | +package runtime_test | ||
| 3 | + | ||
| 4 | +import ( | ||
| 5 | + "internal/goexperiment" | ||
| 6 | + "runtime" | ||
| 7 | + "testing" | ||
| 8 | +) | ||
| 9 | + | ||
| 10 | +func TestClearSpan(t *testing.T) { | ||
| 11 | + needzero, cleared := runtime.TryClearSpanFunc() | ||
| 12 | + if goexperiment.ClearSpan { | ||
| 13 | + if needzero != 0 || !cleared { | ||
| 14 | + t.Fatalf("ClearSpan enabled: expected needzero=0 and cleared=true, got needzero=%d cleared=%v", needzero, cleared) | ||
| 15 | + } | ||
| 16 | + } else if needzero == 0 || cleared { | ||
| 17 | + t.Fatalf("ClearSpan disabled: expected needzero!=0 and cleared=false, got needzero=%d cleared=%v", needzero, cleared) | ||
| 18 | + } | ||
| 19 | +} | ||
| @@ -0,0 +1,35 @@ | |||
| 1 | +// Copyright (c) Huawei Technologies Co., Ltd. 2025-2025. All rights reserved. | ||
| 2 | + | ||
| 3 | +package runtime | ||
| 4 | + | ||
| 5 | +import "unsafe" | ||
| 6 | + | ||
| 7 | +func TryClearSpanFunc() (needzero uint8, cleared bool) { | ||
| 8 | + buf := make([]byte, 128) | ||
| 9 | + for i := range buf { | ||
| 10 | + buf[i] = 0xff | ||
| 11 | + } | ||
| 12 | + | ||
| 13 | + ms := AllocMSpan() | ||
| 14 | + defer FreeMSpan(ms) | ||
| 15 | + | ||
| 16 | + s := (*mspan)(unsafe.Pointer(ms)) | ||
| 17 | + s.startAddr = uintptr(unsafe.Pointer(&buf[0])) | ||
| 18 | + s.elemsize = 16 | ||
| 19 | + s.nelems = uint16(len(buf) / int(s.elemsize)) | ||
| 20 | + s.allocCount = 0 | ||
| 21 | + s.needzero = 1 | ||
| 22 | + | ||
| 23 | + clearSpanFunc(s) | ||
| 24 | + | ||
| 25 | + needzero = s.needzero | ||
| 26 | + cleared = true | ||
| 27 | + for _, b := range buf { | ||
| 28 | + if b != 0 { | ||
| 29 | + cleared = false | ||
| 30 | + break | ||
| 31 | + } | ||
| 32 | + } | ||
| 33 | + KeepAlive(buf) | ||
| 34 | + return | ||
| 35 | +} | ||
| @@ -102,6 +102,7 @@ package runtime | |||
| 102 | 102 | ||
| 103 | import ( | 103 | import ( |
| 104 | "internal/goarch" | 104 | "internal/goarch" |
| 105 | + "internal/goexperiment" | ||
| 105 | "internal/goos" | 106 | "internal/goos" |
| 106 | "internal/runtime/atomic" | 107 | "internal/runtime/atomic" |
| 107 | "internal/runtime/math" | 108 | "internal/runtime/math" |
| @@ -972,6 +973,9 @@ func (c *mcache) nextFree(spc spanClass) (v gclinkptr, s *mspan, checkGCTrigger | |||
| 972 | throw("freeIndex is not valid") | 973 | throw("freeIndex is not valid") |
| 973 | } | 974 | } |
| 974 | 975 | ||
| 976 | + // Clear the span if necessary | ||
| 977 | + clearSpanFunc(s) | ||
| 978 | + | ||
| 975 | v = gclinkptr(uintptr(freeIndex)*s.elemsize + s.base()) | 979 | v = gclinkptr(uintptr(freeIndex)*s.elemsize + s.base()) |
| 976 | s.allocCount++ | 980 | s.allocCount++ |
| 977 | if s.allocCount > s.nelems { | 981 | if s.allocCount > s.nelems { |
| @@ -981,6 +985,13 @@ func (c *mcache) nextFree(spc spanClass) (v gclinkptr, s *mspan, checkGCTrigger | |||
| 981 | return | 985 | return |
| 982 | } | 986 | } |
| 983 | 987 | ||
| 988 | +func clearSpanFunc(s *mspan) { | ||
| 989 | + if goexperiment.ClearSpan && (s.allocCount == 0) && (s.needzero != 0) { | ||
| 990 | + memclrNoHeapPointers(unsafe.Pointer(s.startAddr), (s.elemsize * uintptr(s.nelems))) | ||
| 991 | + s.needzero = 0 | ||
| 992 | + } | ||
| 993 | +} | ||
| 994 | + | ||
| 984 | // doubleCheckMalloc enables a bunch of extra checks to malloc to double-check | 995 | // doubleCheckMalloc enables a bunch of extra checks to malloc to double-check |
| 985 | // that various invariants are upheld. | 996 | // that various invariants are upheld. |
| 986 | // | 997 | // |
| @@ -1175,8 +1186,10 @@ func mallocgcTiny(size uintptr, typ *_type, needzero bool) (unsafe.Pointer, uint | |||
| 1175 | v, span, checkGCTrigger = c.nextFree(tinySpanClass) | 1186 | v, span, checkGCTrigger = c.nextFree(tinySpanClass) |
| 1176 | } | 1187 | } |
| 1177 | x := unsafe.Pointer(v) | 1188 | x := unsafe.Pointer(v) |
| 1178 | - (*[2]uint64)(x)[0] = 0 | 1189 | + if !goexperiment.ClearSpan || span.needzero != 0 { |
| 1179 | - (*[2]uint64)(x)[1] = 0 | 1190 | + (*[2]uint64)(x)[0] = 0 |
| 1191 | + (*[2]uint64)(x)[1] = 0 | ||
| 1192 | + } | ||
| 1180 | // See if we need to replace the existing tiny block with the new one | 1193 | // See if we need to replace the existing tiny block with the new one |
| 1181 | // based on amount of remaining free space. | 1194 | // based on amount of remaining free space. |
| 1182 | if !raceenabled && (size < c.tinyoffset || c.tiny == 0) { | 1195 | if !raceenabled && (size < c.tinyoffset || c.tiny == 0) { |
🟡 Medium Priority
TryClearSpanFunc 通过 allocmcache() 获取 mcache,其 alloc[spc] 指向全局 emptymspan(见 mcache.go:95 初始化逻辑)。测试直接修改 emptymspan.needzero 并对其调用 clearSpanFunc,这会触发 memclrNoHeapPointers(unsafe.Pointer(0), 0)。虽然各平台 memclrNoHeapPointers 实现对 size=0 均安全返回,但修改全局 emptymspan 破坏了测试隔离性,并行测试下可能触发 race detector 报警,且 emptymspan.needzero 被永久修改可能影响后续依赖该值的逻辑。
同一段代码其他问题
导出的变量 Class_to_size / Size_to_class8 / Size_to_class128 未被使用:这三个变量在该文件及整个 diff 范围内均未被任何测试引用,属于无用的导出,增加维护负担并可被误认为有外部依赖。