@@ -17,6 +17,7 @@ func init() {
17
17
register ("GCFairness" , GCFairness )
18
18
register ("GCFairness2" , GCFairness2 )
19
19
register ("GCSys" , GCSys )
20
+ register ("GCPhys" , GCPhys )
20
21
}
21
22
22
23
func GCSys () {
@@ -124,3 +125,85 @@ func GCFairness2() {
124
125
}
125
126
fmt .Println ("OK" )
126
127
}
128
+
129
+ var maybeSaved []byte
130
+
131
+ func GCPhys () {
132
+ // In this test, we construct a very specific scenario. We first
133
+ // allocate N objects and drop half of their pointers on the floor,
134
+ // effectively creating N/2 'holes' in our allocated arenas. We then
135
+ // try to allocate objects twice as big. At the end, we measure the
136
+ // physical memory overhead of large objects.
137
+ //
138
+ // The purpose of this test is to ensure that the GC scavenges free
139
+ // spans eagerly to ensure high physical memory utilization even
140
+ // during fragmentation.
141
+ const (
142
+ // Unfortunately, measuring actual used physical pages is
143
+ // difficult because HeapReleased doesn't include the parts
144
+ // of an arena that haven't yet been touched. So, we just
145
+ // make objects and size sufficiently large such that even
146
+ // 64 MB overhead is relatively small in the final
147
+ // calculation.
148
+ //
149
+ // Currently, we target 480MiB worth of memory for our test,
150
+ // computed as size * objects + (size*2) * (objects/2)
151
+ // = 2 * size * objects
152
+ //
153
+ // Size must be also large enough to be considered a large
154
+ // object (not in any size-segregated span).
155
+ size = 1 << 20
156
+ objects = 240
157
+ )
158
+ // Save objects which we want to survive, and condemn objects which we don't.
159
+ // Note that we condemn objects in this way and release them all at once in
160
+ // order to avoid having the GC start freeing up these objects while the loop
161
+ // is still running and filling in the holes we intend to make.
162
+ saved := make ([][]byte , 0 , objects )
163
+ condemned := make ([][]byte , 0 , objects / 2 + 1 )
164
+ for i := 0 ; i < objects ; i ++ {
165
+ // Write into a global, to prevent this from being optimized away by
166
+ // the compiler in the future.
167
+ maybeSaved = make ([]byte , size )
168
+ if i % 2 == 0 {
169
+ saved = append (saved , maybeSaved )
170
+ } else {
171
+ condemned = append (condemned , maybeSaved )
172
+ }
173
+ }
174
+ condemned = nil
175
+ // Clean up the heap. This will free up every other object created above
176
+ // (i.e. everything in condemned) creating holes in the heap.
177
+ runtime .GC ()
178
+ // Allocate many new objects of 2x size.
179
+ for i := 0 ; i < objects / 2 ; i ++ {
180
+ saved = append (saved , make ([]byte , size * 2 ))
181
+ }
182
+ // Clean up the heap again just to put it in a known state.
183
+ runtime .GC ()
184
+ // heapBacked is an estimate of the amount of physical memory used by
185
+ // this test. HeapSys is an estimate of the size of the mapped virtual
186
+ // address space (which may or may not be backed by physical pages)
187
+ // whereas HeapReleased is an estimate of the amount of bytes returned
188
+ // to the OS. Their difference then roughly corresponds to the amount
189
+ // of virtual address space that is backed by physical pages.
190
+ var stats runtime.MemStats
191
+ runtime .ReadMemStats (& stats )
192
+ heapBacked := stats .HeapSys - stats .HeapReleased
193
+ // If heapBacked exceeds the amount of memory actually used for heap
194
+ // allocated objects by 10% (post-GC HeapAlloc should be quite close to
195
+ // the size of the working set), then fail.
196
+ //
197
+ // In the context of this test, that indicates a large amount of
198
+ // fragmentation with physical pages that are otherwise unused but not
199
+ // returned to the OS.
200
+ overuse := (float64 (heapBacked ) - float64 (stats .HeapAlloc )) / float64 (stats .HeapAlloc )
201
+ if overuse > 0.1 {
202
+ fmt .Printf ("exceeded physical memory overuse threshold of 10%%: %3.2f%%\n " +
203
+ "(alloc: %d, sys: %d, rel: %d, objs: %d)\n " , overuse * 100 , stats .HeapAlloc ,
204
+ stats .HeapSys , stats .HeapReleased , len (saved ))
205
+ return
206
+ }
207
+ fmt .Println ("OK" )
208
+ runtime .KeepAlive (saved )
209
+ }
0 commit comments