View Javadoc
1   /*
2    * Copyright 2021 The Netty Project
3    *
4    * The Netty Project licenses this file to you under the Apache License,
5    * version 2.0 (the "License"); you may not use this file except in compliance
6    * with the License. You may obtain a copy of the License at:
7    *
8    *   https://www.apache.org/licenses/LICENSE-2.0
9    *
10   * Unless required by applicable law or agreed to in writing, software
11   * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
12   * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
13   * License for the specific language governing permissions and limitations
14   * under the License.
15   */
16  package io.netty5.buffer.api.pool;
17  
18  import java.util.concurrent.ConcurrentHashMap;
19  
20  /**
21   * SizeClasses requires {@code pageShifts} to be defined prior to inclusion,
22   * and it in turn defines:
23   * <p>
24   *   LOG2_SIZE_CLASS_GROUP: Log of size class count for each size doubling.
25   *   LOG2_MAX_LOOKUP_SIZE: Log of max size class in the lookup table.
26   *   sizeClasses: Complete table of [index, log2Group, log2Delta, nDelta, isMultiPageSize,
27   *                 isSubPage, log2DeltaLookup] tuples.
28   *     index: Size class index.
29   *     log2Group: Log of group base size (no deltas added).
30   *     log2Delta: Log of delta to previous size class.
31   *     nDelta: Delta multiplier.
32   *     isMultiPageSize: 'yes' if a multiple of the page size, 'no' otherwise.
33   *     isSubPage: 'yes' if a subpage size class, 'no' otherwise.
34   *     log2DeltaLookup: Same as log2Delta if a lookup table size class, 'no'
35   *                      otherwise.
36   * <p>
37   *   nSubpages: Number of subpages size classes.
38   *   nSizes: Number of size classes.
39   *   nPSizes: Number of size classes that are multiples of pageSize.
40   *
41   *   smallMaxSizeIdx: Maximum small size class index.
42   *
43   *   lookupMaxclass: Maximum size class included in lookup table.
44   *   log2NormalMinClass: Log of minimum normal size class.
45   * <p>
46   *   The first size class and spacing are 1 << LOG2_QUANTUM.
47   *   Each group has 1 << LOG2_SIZE_CLASS_GROUP of size classes.
48   *
49   *   size = 1 << log2Group + nDelta * (1 << log2Delta)
50   *
51   *   The first size class has an unusual encoding, because the size has to be
52   *   split between group and delta*nDelta.
53   *
54   *   If pageShift = 13, sizeClasses looks like this:
55   *
56   *   (index, log2Group, log2Delta, nDelta, isMultiPageSize, isSubPage, log2DeltaLookup)
57   * <p>
58   *   ( 0,     4,        4,         0,       no,             yes,        4)
59   *   ( 1,     4,        4,         1,       no,             yes,        4)
60   *   ( 2,     4,        4,         2,       no,             yes,        4)
61   *   ( 3,     4,        4,         3,       no,             yes,        4)
62   * <p>
63   *   ( 4,     6,        4,         1,       no,             yes,        4)
64   *   ( 5,     6,        4,         2,       no,             yes,        4)
65   *   ( 6,     6,        4,         3,       no,             yes,        4)
66   *   ( 7,     6,        4,         4,       no,             yes,        4)
67   * <p>
68   *   ( 8,     7,        5,         1,       no,             yes,        5)
69   *   ( 9,     7,        5,         2,       no,             yes,        5)
70   *   ( 10,    7,        5,         3,       no,             yes,        5)
71   *   ( 11,    7,        5,         4,       no,             yes,        5)
72   *   ...
73   *   ...
74   *   ( 72,    23,       21,        1,       yes,            no,        no)
75   *   ( 73,    23,       21,        2,       yes,            no,        no)
76   *   ( 74,    23,       21,        3,       yes,            no,        no)
77   *   ( 75,    23,       21,        4,       yes,            no,        no)
78   * <p>
79   *   ( 76,    24,       22,        1,       yes,            no,        no)
80   */
81  abstract class SizeClasses implements SizeClassesMetric {
82      private static final ConcurrentHashMap<SizeClassKey, SizeClassValue> CACHE =
83              new ConcurrentHashMap<>();
84  
85      static final int LOG2_QUANTUM = 4;
86  
87      private static final int LOG2_SIZE_CLASS_GROUP = 2;
88      private static final int LOG2_MAX_LOOKUP_SIZE = 12;
89  
90      private static final int LOG2GROUP_IDX = 1;
91      private static final int LOG2DELTA_IDX = 2;
92      private static final int NDELTA_IDX = 3;
93      private static final int PAGESIZE_IDX = 4;
94      private static final int SUBPAGE_IDX = 5;
95      private static final int LOG2_DELTA_LOOKUP_IDX = 6;
96  
97      private static final byte no = 0, yes = 1;
98  
99      protected SizeClasses(int pageSize, int pageShifts, int chunkSize, int directMemoryCacheAlignment) {
100         this.pageSize = pageSize;
101         this.pageShifts = pageShifts;
102         this.chunkSize = chunkSize;
103         this.directMemoryCacheAlignment = directMemoryCacheAlignment;
104 
105         SizeClassValue value = CACHE.computeIfAbsent(
106                 new SizeClassKey(pageSize, pageShifts, chunkSize, directMemoryCacheAlignment),
107                 key -> new SizeClassValue(key, directMemoryCacheAlignment));
108         nSizes = value.nSizes;
109         nSubpages = value.nSubpages;
110         nPSizes = value.nPSizes;
111         smallMaxSizeIdx = value.smallMaxSizeIdx;
112         lookupMaxSize = value.lookupMaxSize;
113         pageIdx2sizeTab = value.pageIdx2sizeTab;
114         sizeIdx2sizeTab = value.sizeIdx2sizeTab;
115         size2idxTab = value.size2idxTab;
116     }
117 
118     protected final int pageSize;
119     protected final int pageShifts;
120     protected final int chunkSize;
121     protected final int directMemoryCacheAlignment;
122 
123     final int nSizes;
124     final int nSubpages;
125     final int nPSizes;
126     final int smallMaxSizeIdx;
127 
128     private final int lookupMaxSize;
129     private final int[] pageIdx2sizeTab;
130 
131     // lookup table for sizeIdx <= smallMaxSizeIdx
132     private final int[] sizeIdx2sizeTab;
133 
134     // lookup table used for size <= lookupMaxclass
135     // spacing is 1 << LOG2_QUANTUM, so the size of array is lookupMaxclass >> LOG2_QUANTUM
136     private final int[] size2idxTab;
137 
138     @Override
139     public int sizeIdx2size(int sizeIdx) {
140         return sizeIdx2sizeTab[sizeIdx];
141     }
142 
143     @Override
144     public int sizeIdx2sizeCompute(int sizeIdx) {
145         int group = sizeIdx >> LOG2_SIZE_CLASS_GROUP;
146         int mod = sizeIdx & (1 << LOG2_SIZE_CLASS_GROUP) - 1;
147 
148         int groupSize = group == 0? 0 :
149                 1 << LOG2_QUANTUM + LOG2_SIZE_CLASS_GROUP - 1 << group;
150 
151         int shift = group == 0? 1 : group;
152         int lgDelta = shift + LOG2_QUANTUM - 1;
153         int modSize = mod + 1 << lgDelta;
154 
155         return groupSize + modSize;
156     }
157 
158     @Override
159     public long pageIdx2size(int pageIdx) {
160         return pageIdx2sizeTab[pageIdx];
161     }
162 
163     @Override
164     public long pageIdx2sizeCompute(int pageIdx) {
165         int group = pageIdx >> LOG2_SIZE_CLASS_GROUP;
166         int mod = pageIdx & (1 << LOG2_SIZE_CLASS_GROUP) - 1;
167 
168         long groupSize = group == 0? 0 :
169                 1L << pageShifts + LOG2_SIZE_CLASS_GROUP - 1 << group;
170 
171         int shift = group == 0? 1 : group;
172         int log2Delta = shift + pageShifts - 1;
173         int modSize = mod + 1 << log2Delta;
174 
175         return groupSize + modSize;
176     }
177 
178     @Override
179     public int size2SizeIdx(int size) {
180         if (size == 0) {
181             return 0;
182         }
183         if (size > chunkSize) {
184             return nSizes;
185         }
186 
187         size = alignSizeIfNeeded(size, directMemoryCacheAlignment);
188 
189         if (size <= lookupMaxSize) {
190             //size-1 / MIN_TINY
191             return size2idxTab[size - 1 >> LOG2_QUANTUM];
192         }
193 
194         int x = PoolThreadCache.log2((size << 1) - 1);
195         int shift = x < LOG2_SIZE_CLASS_GROUP + LOG2_QUANTUM + 1
196                 ? 0 : x - (LOG2_SIZE_CLASS_GROUP + LOG2_QUANTUM);
197 
198         int group = shift << LOG2_SIZE_CLASS_GROUP;
199 
200         int log2Delta = x < LOG2_SIZE_CLASS_GROUP + LOG2_QUANTUM + 1
201                 ? LOG2_QUANTUM : x - LOG2_SIZE_CLASS_GROUP - 1;
202 
203         int deltaInverseMask = -1 << log2Delta;
204         int mod = (size - 1 & deltaInverseMask) >> log2Delta &
205                   (1 << LOG2_SIZE_CLASS_GROUP) - 1;
206 
207         return group + mod;
208     }
209 
210     @Override
211     public int pages2pageIdx(int pages) {
212         return pages2pageIdxCompute(pages, false);
213     }
214 
215     @Override
216     public int pages2pageIdxFloor(int pages) {
217         return pages2pageIdxCompute(pages, true);
218     }
219 
220     private int pages2pageIdxCompute(int pages, boolean floor) {
221         int pageSize = pages << pageShifts;
222         if (pageSize > chunkSize) {
223             return nPSizes;
224         }
225 
226         int x = PoolThreadCache.log2((pageSize << 1) - 1);
227 
228         int shift = x < LOG2_SIZE_CLASS_GROUP + pageShifts
229                 ? 0 : x - (LOG2_SIZE_CLASS_GROUP + pageShifts);
230 
231         int group = shift << LOG2_SIZE_CLASS_GROUP;
232 
233         int log2Delta = x < LOG2_SIZE_CLASS_GROUP + pageShifts + 1?
234                 pageShifts : x - LOG2_SIZE_CLASS_GROUP - 1;
235 
236         int deltaInverseMask = -1 << log2Delta;
237         int mod = (pageSize - 1 & deltaInverseMask) >> log2Delta &
238                   (1 << LOG2_SIZE_CLASS_GROUP) - 1;
239 
240         int pageIdx = group + mod;
241 
242         if (floor && pageIdx2sizeTab[pageIdx] > pages << pageShifts) {
243             pageIdx--;
244         }
245 
246         return pageIdx;
247     }
248 
249     // Round size up to the nearest multiple of alignment.
250     private static int alignSizeIfNeeded(int size, int alignment) {
251         if (alignment <= 0) {
252             return size;
253         }
254         int delta = size & alignment - 1;
255         return delta == 0? size : size + alignment - delta;
256     }
257 
258     @Override
259     public int normalizeSize(int size) {
260         if (size == 0) {
261             return sizeIdx2sizeTab[0];
262         }
263         size = alignSizeIfNeeded(size, directMemoryCacheAlignment);
264 
265         if (size <= lookupMaxSize) {
266             int ret = sizeIdx2sizeTab[size2idxTab[size - 1 >> LOG2_QUANTUM]];
267             assert ret == normalizeSizeCompute(size);
268             return ret;
269         }
270         return normalizeSizeCompute(size);
271     }
272 
273     private static int normalizeSizeCompute(int size) {
274         int x = PoolThreadCache.log2((size << 1) - 1);
275         int log2Delta = x < LOG2_SIZE_CLASS_GROUP + LOG2_QUANTUM + 1
276                 ? LOG2_QUANTUM : x - LOG2_SIZE_CLASS_GROUP - 1;
277         int delta = 1 << log2Delta;
278         int delta_mask = delta - 1;
279         return size + delta_mask & ~delta_mask;
280     }
281 
282     private static final class SizeClassKey {
283         final int pageSize;
284         final int pageShifts;
285         final int chunkSize;
286         final int directMemoryCacheAlignment;
287 
288         private SizeClassKey(int pageSize, int pageShifts, int chunkSize, int directMemoryCacheAlignment) {
289             this.pageSize = pageSize;
290             this.pageShifts = pageShifts;
291             this.chunkSize = chunkSize;
292             this.directMemoryCacheAlignment = directMemoryCacheAlignment;
293         }
294 
295         @Override
296         public boolean equals(Object o) {
297             if (this == o) {
298                 return true;
299             }
300             if (o == null || getClass() != o.getClass()) {
301                 return false;
302             }
303 
304             SizeClassKey that = (SizeClassKey) o;
305 
306             if (pageSize != that.pageSize) {
307                 return false;
308             }
309             if (pageShifts != that.pageShifts) {
310                 return false;
311             }
312             if (chunkSize != that.chunkSize) {
313                 return false;
314             }
315             return directMemoryCacheAlignment == that.directMemoryCacheAlignment;
316         }
317 
318         @Override
319         public int hashCode() {
320             int result = pageSize;
321             result = 31 * result + pageShifts;
322             result = 31 * result + chunkSize;
323             result = 31 * result + directMemoryCacheAlignment;
324             return result;
325         }
326     }
327 
328     private static final class SizeClassValue {
329         final SizeClassKey key;
330         final int nSizes;
331         final int nSubpages;
332         final int nPSizes;
333         final int smallMaxSizeIdx;
334         final int lookupMaxSize;
335         final int[] pageIdx2sizeTab;
336         final int[] sizeIdx2sizeTab;
337         final int[] size2idxTab;
338 
339         SizeClassValue(SizeClassKey key, int directMemoryCacheAlignment) {
340             this.key = key;
341             int group = PoolThreadCache.log2(key.chunkSize) + 1 - LOG2_QUANTUM;
342 
343             //generate size classes
344             //[index, log2Group, log2Delta, nDelta, isMultiPageSize, isSubPage, log2DeltaLookup]
345             short[][] sizeClasses = new short[group << LOG2_SIZE_CLASS_GROUP][7];
346 
347             int normalMaxSize = -1;
348             int nSizes = 0;
349             int size = 0;
350 
351             int log2Group = LOG2_QUANTUM;
352             int log2Delta = LOG2_QUANTUM;
353             int ndeltaLimit = 1 << LOG2_SIZE_CLASS_GROUP;
354 
355             //First small group, nDelta start at 0.
356             //first size class is 1 << LOG2_QUANTUM
357             for (int nDelta = 0; nDelta < ndeltaLimit; nDelta++, nSizes++) {
358                 short[] sizeClass = newSizeClass(nSizes, log2Group, log2Delta, nDelta, key);
359                 sizeClasses[nSizes] = sizeClass;
360                 size = sizeOf(sizeClass, directMemoryCacheAlignment);
361             }
362 
363             log2Group += LOG2_SIZE_CLASS_GROUP;
364 
365             //All remaining groups, nDelta start at 1.
366             for (; size < key.chunkSize; log2Group++, log2Delta++) {
367                 for (int nDelta = 1; nDelta <= ndeltaLimit && size < key.chunkSize; nDelta++, nSizes++) {
368                     short[] sizeClass = newSizeClass(nSizes, log2Group, log2Delta, nDelta, key);
369                     sizeClasses[nSizes] = sizeClass;
370                     size = normalMaxSize = sizeOf(sizeClass, directMemoryCacheAlignment);
371                 }
372             }
373 
374             //chunkSize must be normalMaxSize
375             assert key.chunkSize == normalMaxSize;
376 
377             int smallMaxSizeIdx = 0;
378             int lookupMaxSize = 0;
379             int nPSizes = 0;
380             int nSubpages = 0;
381             for (int idx = 0; idx < nSizes; idx++) {
382                 short[] sz = sizeClasses[idx];
383                 if (sz[PAGESIZE_IDX] == yes) {
384                     nPSizes++;
385                 }
386                 if (sz[SUBPAGE_IDX] == yes) {
387                     nSubpages++;
388                     smallMaxSizeIdx = idx;
389                 }
390                 if (sz[LOG2_DELTA_LOOKUP_IDX] != no) {
391                     lookupMaxSize = sizeOf(sz, directMemoryCacheAlignment);
392                 }
393             }
394             this.smallMaxSizeIdx = smallMaxSizeIdx;
395             this.lookupMaxSize = lookupMaxSize;
396             this.nPSizes = nPSizes;
397             this.nSubpages = nSubpages;
398             this.nSizes = nSizes;
399 
400             //generate lookup table
401             sizeIdx2sizeTab = newIdx2SizeTab(sizeClasses, nSizes, directMemoryCacheAlignment);
402             pageIdx2sizeTab = newPageIdx2sizeTab(sizeClasses, nSizes, nPSizes, directMemoryCacheAlignment);
403             size2idxTab = newSize2idxTab(lookupMaxSize, sizeClasses);
404         }
405 
406         //calculate size class
407         private static short[] newSizeClass(int index, int log2Group, int log2Delta, int nDelta, SizeClassKey key) {
408             short isMultiPageSize;
409             if (log2Delta >= key.pageShifts) {
410                 isMultiPageSize = yes;
411             } else {
412                 int pageSize = 1 << key.pageShifts;
413                 int size = (1 << log2Group) + (1 << log2Delta) * nDelta;
414 
415                 isMultiPageSize = size == size / pageSize * pageSize? yes : no;
416             }
417 
418             int log2Ndelta = nDelta == 0? 0 : PoolThreadCache.log2(nDelta);
419 
420             byte remove = 1 << log2Ndelta < nDelta? yes : no;
421 
422             int log2Size = log2Delta + log2Ndelta == log2Group? log2Group + 1 : log2Group;
423             if (log2Size == log2Group) {
424                 remove = yes;
425             }
426 
427             short isSubpage = log2Size < key.pageShifts + LOG2_SIZE_CLASS_GROUP? yes : no;
428 
429             int log2DeltaLookup = log2Size < LOG2_MAX_LOOKUP_SIZE ||
430                     log2Size == LOG2_MAX_LOOKUP_SIZE && remove == no
431                     ? log2Delta : no;
432 
433             return new short[] {
434                     (short) index, (short) log2Group, (short) log2Delta,
435                     (short) nDelta, isMultiPageSize, isSubpage, (short) log2DeltaLookup
436             };
437         }
438 
439         private static int[] newIdx2SizeTab(short[][] sizeClasses, int nSizes, int directMemoryCacheAlignment) {
440             int[] sizeIdx2sizeTab = new int[nSizes];
441 
442             for (int i = 0; i < nSizes; i++) {
443                 short[] sizeClass = sizeClasses[i];
444                 sizeIdx2sizeTab[i] = sizeOf(sizeClass, directMemoryCacheAlignment);
445             }
446             return sizeIdx2sizeTab;
447         }
448 
449         private static int calculateSize(int log2Group, int nDelta, int log2Delta) {
450             return (1 << log2Group) + (nDelta << log2Delta);
451         }
452 
453         private static int sizeOf(short[] sizeClass, int directMemoryCacheAlignment) {
454             int log2Group = sizeClass[LOG2GROUP_IDX];
455             int log2Delta = sizeClass[LOG2DELTA_IDX];
456             int nDelta = sizeClass[NDELTA_IDX];
457 
458             int size = calculateSize(log2Group, nDelta, log2Delta);
459 
460             return alignSizeIfNeeded(size, directMemoryCacheAlignment);
461         }
462 
463         private static int[] newPageIdx2sizeTab(short[][] sizeClasses, int nSizes, int nPSizes,
464                                                 int directMemoryCacheAlignment) {
465             int[] pageIdx2sizeTab = new int[nPSizes];
466             int pageIdx = 0;
467             for (int i = 0; i < nSizes; i++) {
468                 short[] sizeClass = sizeClasses[i];
469                 if (sizeClass[PAGESIZE_IDX] == yes) {
470                     pageIdx2sizeTab[pageIdx++] = sizeOf(sizeClass, directMemoryCacheAlignment);
471                 }
472             }
473             return pageIdx2sizeTab;
474         }
475 
476         private static int[] newSize2idxTab(int lookupMaxSize, short[][] sizeClasses) {
477             int[] size2idxTab = new int[lookupMaxSize >> LOG2_QUANTUM];
478             int idx = 0;
479             int size = 0;
480 
481             for (int i = 0; size <= lookupMaxSize; i++) {
482                 int log2Delta = sizeClasses[i][LOG2DELTA_IDX];
483                 int times = 1 << log2Delta - LOG2_QUANTUM;
484 
485                 while (size <= lookupMaxSize && times-- > 0) {
486                     size2idxTab[idx++] = i;
487                     size = idx + 1 << LOG2_QUANTUM;
488                 }
489             }
490             return size2idxTab;
491         }
492     }
493 }