• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

ben-manes / caffeine / #5173

29 Dec 2025 05:27AM UTC coverage: 0.0% (-100.0%) from 100.0%
#5173

push

github

ben-manes
speed up development ci build

0 of 3838 branches covered (0.0%)

0 of 7869 relevant lines covered (0.0%)

0.0 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

0.0
/caffeine/src/main/java/com/github/benmanes/caffeine/cache/FrequencySketch.java
1
/*
2
 * Copyright 2015 Ben Manes. All Rights Reserved.
3
 *
4
 * Licensed under the Apache License, Version 2.0 (the "License");
5
 * you may not use this file except in compliance with the License.
6
 * You may obtain a copy of the License at
7
 *
8
 *     http://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,
12
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
 * See the License for the specific language governing permissions and
14
 * limitations under the License.
15
 */
16
package com.github.benmanes.caffeine.cache;
17

18
import static com.github.benmanes.caffeine.cache.Caffeine.requireArgument;
19

20
import com.google.errorprone.annotations.Var;
21

22
/**
23
 * A probabilistic multiset for estimating the popularity of an element within a time window. The
24
 * maximum frequency of an element is limited to 15 (4-bits) and an aging process periodically
25
 * halves the popularity of all elements.
26
 *
27
 * @author ben.manes@gmail.com (Ben Manes)
28
 */
29
final class FrequencySketch {
30

31
  /*
32
   * This class maintains a 4-bit CountMinSketch [1] with periodic aging to provide the popularity
33
   * history for the TinyLfu admission policy [2]. The time and space efficiency of the sketch
34
   * allows it to cheaply estimate the frequency of an entry in a stream of cache access events.
35
   *
36
   * The counter matrix is represented as a single-dimensional array holding 16 counters per slot. A
37
   * fixed depth of four balances the accuracy and cost, resulting in a width of four times the
38
   * length of the array. To retain an accurate estimation, the array's length equals the maximum
39
   * number of entries in the cache, increased to the closest power-of-two to exploit more efficient
40
   * bit masking. This configuration results in a confidence of 93.75% and an error bound of
41
   * e / width.
42
   *
43
   * To improve hardware efficiency, an item's counters are constrained to a 64-byte block, which is
44
   * the size of an L1 cache line. This differs from the theoretical ideal where counters are
45
   * uniformly distributed to minimize collisions. In that configuration, the memory accesses are
46
   * not predictable and lack spatial locality, which may cause the pipeline to need to wait for
47
   * four memory loads. Instead, the items are uniformly distributed to blocks, and each counter is
48
   * uniformly selected from a distinct 16-byte segment. While the runtime memory layout may result
49
   * in the blocks not being cache-aligned, the L2 spatial prefetcher tries to load aligned pairs of
50
   * cache lines, so the typical cost is only one memory access.
51
   *
52
   * The frequency of all entries is aged periodically using a sampling window based on the maximum
53
   * number of entries in the cache. This is referred to as the reset operation by TinyLfu and keeps
54
   * the sketch fresh by dividing all counters by two and subtracting based on the number of odd
55
   * counters found. The O(n) cost of aging is amortized, ideal for hardware prefetching, and uses
56
   * inexpensive bit manipulations per array location.
57
   *
58
   * [1] An Improved Data Stream Summary: The Count-Min Sketch and its Applications
59
   * http://dimacs.rutgers.edu/~graham/pubs/papers/cm-full.pdf
60
   * [2] TinyLFU: A Highly Efficient Cache Admission Policy
61
   * https://dl.acm.org/citation.cfm?id=3149371
62
   * [3] Hash Function Prospector: Three round functions
63
   * https://github.com/skeeto/hash-prospector#three-round-functions
64
   */
65

66
  static final long RESET_MASK = 0x7777777777777777L;
67
  static final long ONE_MASK = 0x1111111111111111L;
68

69
  int sampleSize;
70
  int blockMask;
71
  long[] table;
72
  int size;
73

74
  /**
75
   * Creates a lazily initialized frequency sketch, requiring {@link #ensureCapacity} be called
76
   * when the maximum size of the cache has been determined.
77
   */
78
  @SuppressWarnings("NullAway.Init")
79
  public FrequencySketch() {}
×
80

81
  /**
82
   * Initializes and increases the capacity of this {@code FrequencySketch} instance, if necessary,
83
   * to ensure that it can accurately estimate the popularity of elements given the maximum size of
84
   * the cache. This operation forgets all previous counts when resizing.
85
   *
86
   * @param maximumSize the maximum size of the cache
87
   */
88
  @SuppressWarnings("Varifier")
89
  public void ensureCapacity(long maximumSize) {
90
    requireArgument(maximumSize >= 0);
×
91
    int maximum = (int) Math.min(maximumSize, Integer.MAX_VALUE >>> 1);
×
92
    if ((table != null) && (table.length >= maximum)) {
×
93
      return;
×
94
    }
95

96
    table = new long[Math.max(Caffeine.ceilingPowerOfTwo(maximum), 8)];
×
97
    sampleSize = (maximumSize == 0) ? 10 : (10 * maximum);
×
98
    blockMask = (table.length >>> 3) - 1;
×
99
    if (sampleSize <= 0) {
×
100
      sampleSize = Integer.MAX_VALUE;
×
101
    }
102
    size = 0;
×
103
  }
×
104

105
  /**
106
   * Returns if the sketch has not yet been initialized, requiring that {@link #ensureCapacity} is
107
   * called before it begins to track frequencies.
108
   */
109
  public boolean isNotInitialized() {
110
    return (table == null);
×
111
  }
112

113
  /**
114
   * Returns the estimated number of occurrences of an element, up to the maximum (15).
115
   *
116
   * @param e the element to count occurrences of
117
   * @return the estimated number of occurrences of the element; possibly zero but never negative
118
   */
119
  @SuppressWarnings("Varifier")
120
  public int frequency(Object e) {
121
    if (isNotInitialized()) {
×
122
      return 0;
×
123
    }
124

125
    @Var int frequency = Integer.MAX_VALUE;
×
126
    int blockHash = spread(e.hashCode());
×
127
    int counterHash = rehash(blockHash);
×
128
    int block = (blockHash & blockMask) << 3;
×
129
    for (int i = 0; i < 4; i++) {
×
130
      int h = counterHash >>> (i << 3);
×
131
      int index = (h >>> 1) & 15;
×
132
      int offset = h & 1;
×
133
      int slot = block + offset + (i << 1);
×
134
      int count = (int) ((table[slot] >>> (index << 2)) & 0xfL);
×
135
      frequency = Math.min(frequency, count);
×
136
    }
137
    return frequency;
×
138
  }
139

140
  /**
141
   * Increments the popularity of the element if it does not exceed the maximum (15). The popularity
142
   * of all elements will be periodically down sampled when the observed events exceed a threshold.
143
   * This process provides a frequency aging to allow expired long term entries to fade away.
144
   *
145
   * @param e the element to add
146
   */
147
  @SuppressWarnings({"ShortCircuitBoolean", "UnnecessaryLocalVariable"})
148
  public void increment(Object e) {
149
    if (isNotInitialized()) {
×
150
      return;
×
151
    }
152

153
    int blockHash = spread(e.hashCode());
×
154
    int counterHash = rehash(blockHash);
×
155
    int block = (blockHash & blockMask) << 3;
×
156

157
    // Loop unrolling improves throughput by 10m ops/s
158
    int h0 = counterHash;
×
159
    int h1 = counterHash >>> 8;
×
160
    int h2 = counterHash >>> 16;
×
161
    int h3 = counterHash >>> 24;
×
162

163
    int index0 = (h0 >>> 1) & 15;
×
164
    int index1 = (h1 >>> 1) & 15;
×
165
    int index2 = (h2 >>> 1) & 15;
×
166
    int index3 = (h3 >>> 1) & 15;
×
167

168
    int slot0 = block + (h0 & 1);
×
169
    int slot1 = block + (h1 & 1) + 2;
×
170
    int slot2 = block + (h2 & 1) + 4;
×
171
    int slot3 = block + (h3 & 1) + 6;
×
172

173
    boolean added =
×
174
          incrementAt(slot0, index0)
×
175
        | incrementAt(slot1, index1)
×
176
        | incrementAt(slot2, index2)
×
177
        | incrementAt(slot3, index3);
×
178

179
    if (added && (++size == sampleSize)) {
×
180
      reset();
×
181
    }
182
  }
×
183

184
  /** Applies a supplemental hash function to defend against a poor quality hash. */
185
  static int spread(@Var int x) {
186
    x ^= x >>> 17;
×
187
    x *= 0xed5ad4bb;
×
188
    x ^= x >>> 11;
×
189
    x *= 0xac4c1b51;
×
190
    x ^= x >>> 15;
×
191
    return x;
×
192
  }
193

194
  /** Applies another round of hashing for additional randomization. */
195
  static int rehash(@Var int x) {
196
    x *= 0x31848bab;
×
197
    x ^= x >>> 14;
×
198
    return x;
×
199
  }
200

201
  /**
202
   * Increments the specified counter by 1 if it is not already at the maximum value (15).
203
   *
204
   * @param i the table index (16 counters)
205
   * @param j the counter to increment
206
   * @return if incremented
207
   */
208
  boolean incrementAt(int i, int j) {
209
    int offset = j << 2;
×
210
    long mask = (0xfL << offset);
×
211
    if ((table[i] & mask) != mask) {
×
212
      table[i] += (1L << offset);
×
213
      return true;
×
214
    }
215
    return false;
×
216
  }
217

218
  /** Reduces every counter by half of its original value. */
219
  void reset() {
220
    @Var int count = 0;
×
221
    for (int i = 0; i < table.length; i++) {
×
222
      count += Long.bitCount(table[i] & ONE_MASK);
×
223
      table[i] = (table[i] >>> 1) & RESET_MASK;
×
224
    }
225
    size = (size - (count >>> 2)) >>> 1;
×
226
  }
×
227
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc