/* * Copyright 2015 Ben Manes. All Rights Reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.github.benmanes.caffeine.cache;
/** * A probabilistic multiset for estimating the popularity of an element within a time window. The * maximum frequency of an element is limited to 15 (4-bits) and an aging process periodically * halves the popularity of all elements. * * @author ben.manes@gmail.com (Ben Manes) */ finalclassFrequencySketch<E> {
/* * This class maintains a 4-bit CountMinSketch [1] with periodic aging to provide the popularity * history for the TinyLfu admission policy [2]. The time and space efficiency of the sketch * allows it to cheaply estimate the frequency of an entry in a stream of cache access events. * * The counter matrix is represented as a single-dimensional array holding 16 counters per slot. A * fixed depth of four balances the accuracy and cost, resulting in a width of four times the * length of the array. To retain an accurate estimation, the array's length equals the maximum * number of entries in the cache, increased to the closest power-of-two to exploit more efficient * bit masking. This configuration results in a confidence of 93.75% and an error bound of * e / width. * * To improve hardware efficiency, an item's counters are constrained to a 64-byte block, which is * the size of an L1 cache line. This differs from the theoretical ideal where counters are * uniformly distributed to minimize collisions. In that configuration, the memory accesses are * not predictable and lack spatial locality, which may cause the pipeline to need to wait for * four memory loads. Instead, the items are uniformly distributed to blocks, and each counter is * uniformly selected from a distinct 16-byte segment. While the runtime memory layout may result * in the blocks not being cache-aligned, the L2 spatial prefetcher tries to load aligned pairs of * cache lines, so the typical cost is only one memory access. * * The frequency of all entries is aged periodically using a sampling window based on the maximum * number of entries in the cache. This is referred to as the reset operation by TinyLfu and keeps * the sketch fresh by dividing all counters by two and subtracting based on the number of odd * counters found. The O(n) cost of aging is amortized, ideal for hardware prefetching, and uses * inexpensive bit manipulations per array location. * * [1] An Improved Data Stream Summary: The Count-Min Sketch and its Applications * http://dimacs.rutgers.edu/~graham/pubs/papers/cm-full.pdf * [2] TinyLFU: A Highly Efficient Cache Admission Policy * https://dl.acm.org/citation.cfm?id=3149371 * [3] Hash Function Prospector: Three round functions * https://github.com/skeeto/hash-prospector#three-round-functions */
int sampleSize; int blockMask; long[] table; int size;
/** * Creates a lazily initialized frequency sketch, requiring {@link #ensureCapacity} be called * when the maximum size of the cache has been determined. */ @SuppressWarnings("NullAway.Init") publicFrequencySketch() {}
/** * Initializes and increases the capacity of this {@code FrequencySketch} instance, if necessary, * to ensure that it can accurately estimate the popularity of elements given the maximum size of * the cache. This operation forgets all previous counts when resizing. * * @param maximumSize the maximum size of the cache */ @SuppressWarnings("Varifier") publicvoidensureCapacity(long maximumSize) { requireArgument(maximumSize >= 0); intmaximum= (int) Math.min(maximumSize, Integer.MAX_VALUE >>> 1); if ((table != null) && (table.length >= maximum)) { return; }
/** * Returns if the sketch has not yet been initialized, requiring that {@link #ensureCapacity} is * called before it begins to track frequencies. */ publicbooleanisNotInitialized() { return (table == null); }
/** * Returns the estimated number of occurrences of an element, up to the maximum (15). * * @param e the element to count occurrences of * @return the estimated number of occurrences of the element; possibly zero but never negative */ @SuppressWarnings("Varifier") publicintfrequency(E e) { if (isNotInitialized()) { return0; }
@Varintfrequency= Integer.MAX_VALUE; intblockHash= spread(e.hashCode()); intcounterHash= rehash(blockHash); intblock= (blockHash & blockMask) << 3; for (inti=0; i < 4; i++) { inth= counterHash >>> (i << 3); intindex= (h >>> 1) & 15; intoffset= h & 1; intslot= block + offset + (i << 1); intcount= (int) ((table[slot] >>> (index << 2)) & 0xfL); frequency = Math.min(frequency, count); } return frequency; }
/** * Increments the popularity of the element if it does not exceed the maximum (15). The popularity * of all elements will be periodically down sampled when the observed events exceed a threshold. * This process provides a frequency aging to allow expired long term entries to fade away. * * @param e the element to add */ @SuppressWarnings({"ShortCircuitBoolean", "UnnecessaryLocalVariable"}) publicvoidincrement(E e) { if (isNotInitialized()) { return; }
if (added && (++size == sampleSize)) { reset(); } }
/** Applies a supplemental hash function to defend against a poor quality hash. */ staticintspread(@Varint x) { x ^= x >>> 17; x *= 0xed5ad4bb; x ^= x >>> 11; x *= 0xac4c1b51; x ^= x >>> 15; return x; }
/** Applies another round of hashing for additional randomization. */ staticintrehash(@Varint x) { x *= 0x31848bab; x ^= x >>> 14; return x; }
/** * Increments the specified counter by 1 if it is not already at the maximum value (15). * * @param i the table index (16 counters) * @param j the counter to increment * @return if incremented */ booleanincrementAt(int i, int j) { intoffset= j << 2; longmask= (0xfL << offset); if ((table[i] & mask) != mask) { table[i] += (1L << offset); returntrue; } returnfalse; }
/** Reduces every counter by half of its original value. */ voidreset() { @Varintcount=0; for (inti=0; i < table.length; i++) { count += Long.bitCount(table[i] & ONE_MASK); table[i] = (table[i] >>> 1) & RESET_MASK; } size = (size - (count >>> 2)) >>> 1; } }
/** * Initializes and increases the capacity of this {@code FrequencySketch} instance, if necessary, * to ensure that it can accurately estimate the popularity of elements given the maximum size of * the cache. This operation forgets all previous counts when resizing. * * @param maximumSize the maximum size of the cache */ @SuppressWarnings("Varifier") publicvoidensureCapacity(long maximumSize) { requireArgument(maximumSize >= 0); intmaximum= (int) Math.min(maximumSize, Integer.MAX_VALUE >>> 1); // 这个是最大容量是 maximumSize 最大不超过2^30 -1 大概10亿左右 if ((table != null) && (table.length >= maximum)) { return; }
/** Returns the smallest power of two greater than or equal to {@code x}. */ staticintceilingPowerOfTwo(int x) { // From Hacker's Delight, Chapter 3, Harry S. Warren Jr. return1 << -Integer.numberOfLeadingZeros(x - 1); } // 把x对齐到最近的2的次幂, x-1防止x本身已经是2的幂 Integer.numberOfLeadingZeros 这个是返回最高位1前面0的个数
1 2 3 4 5 6 7 8 9 10 11 12 13 14
@IntrinsicCandidate publicstaticintnumberOfLeadingZeros(int i) { // HD, Count leading 0's if (i <= 0) return i == 0 ? 32 : 0; intn=31; if (i >= 1 << 16) { n -= 16; i >>>= 16; } if (i >= 1 << 8) { n -= 8; i >>>= 8; } if (i >= 1 << 4) { n -= 4; i >>>= 4; } if (i >= 1 << 2) { n -= 2; i >>>= 2; } return n - (i >>> 1); } // @IntrinsicCandidate 告诉 HotSpot:**“这个方法可以被虚拟机替换成一条机器指令”**。在 x86 上就是 `LZCNT`(如果 CPU 支持)或 `BSR`+简单换算,几乎一个时钟周期完成,比 Java 代码更快。 // 这几个if其实就是二分法了,先算高16 再往下找 然后返回前面0的个数,可以方便
/** * Returns the estimated number of occurrences of an element, up to the maximum (15). * * @param e the element to count occurrences of * @return the estimated number of occurrences of the element; possibly zero but never negative */ @SuppressWarnings("Varifier") publicintfrequency(E e) { if (isNotInitialized()) { // 缓存还没被启用就当成从未访问过 return0; }
/** Applies a supplemental hash function to defend against a poor quality hash. */ staticintspread(@Varint x) { // hash扰乱函数,确保hash的随机性 这个常数确实有点吊 x ^= x >>> 17; x *= 0xed5ad4bb; x ^= x >>> 11; x *= 0xac4c1b51; x ^= x >>> 15; return x; }
/** Applies another round of hashing for additional randomization. */ staticintrehash(@Varint x) { // 再hash 随后 4 轮循环每次 counterHash >>> (i<<3) 取不同字节,因为经过二次扰动,4 个字节之间几乎独立,保证 4 个采样计数器不会系统性偏向同一值,TinyLFU 的“最小值”估计才更准。 x *= 0x31848bab; x ^= x >>> 14; return x; }
/** * Increments the popularity of the element if it does not exceed the maximum (15). The popularity * of all elements will be periodically down sampled when the observed events exceed a threshold. * This process provides a frequency aging to allow expired long term entries to fade away. * * @param e the element to add */ @SuppressWarnings({"ShortCircuitBoolean", "UnnecessaryLocalVariable"}) publicvoidincrement(Object e) { // 核心计数的方法 if (isNotInitialized()) { return; }
if (added && (++size == sampleSize)) { reset(); } }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
/** * Increments the specified counter by 1 if it is not already at the maximum value (15). * * @param i the table index (16 counters) * @param j the counter to increment * @return if incremented */ booleanincrementAt(int i, int j) { //这段算计数器加1,这里也有个很精妙的操作,无锁原子更新 intoffset= j << 2; // 等于j * 4 longmask= (0xfL << offset); //16 if ((table[i] & mask) != mask) { 不是1111 table[i] += (1L << offset); // 直接加1 returntrue; } returnfalse; }