1use crate::thread_id::thread_id;
9use crossbeam_utils::CachePadded;
10use std::fmt;
11use std::sync::atomic::{AtomicIsize, Ordering};
12
13fn make_padded_counter() -> CachePadded<AtomicIsize> {
14 CachePadded::new(AtomicIsize::new(0))
15}
16
17#[cfg(feature = "bench-tools")]
18fn make_counter_cell() -> AtomicIsize {
19 AtomicIsize::new(0)
20}
21
22pub struct Counter {
27 cells: Vec<CachePadded<AtomicIsize>>,
28}
29
30impl fmt::Debug for Counter {
31 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
32 f.debug_struct("Counter")
33 .field("sum", &self.sum())
34 .field("cells", &self.cells.len())
35 .finish()
36 }
37}
38
39impl Counter {
40 #[inline]
44 pub fn new(count: usize) -> Self {
45 let count = count.next_power_of_two();
46 Self {
47 cells: (0..count).map(|_| make_padded_counter()).collect(),
48 }
49 }
50
51 #[inline]
53 pub fn add(&self, value: isize) {
54 self.add_with_ordering(value, Ordering::Relaxed)
55 }
56
57 #[inline]
59 pub fn inc(&self) {
60 self.add(1)
61 }
62
63 #[inline]
64 fn add_with_thread_id(&self, thread_id: usize, value: isize, ordering: Ordering) {
65 let idx = thread_id & (self.cells.len() - 1);
66 let cell = if cfg!(debug_assertions) {
68 self.cells.get(idx).expect("index out of bounds")
69 } else {
70 unsafe { self.cells.get_unchecked(idx) }
71 };
72 cell.fetch_add(value, ordering);
73 }
74
75 #[inline]
77 pub fn add_with_ordering(&self, value: isize, ordering: Ordering) {
78 self.add_with_thread_id(thread_id(), value, ordering);
79 }
80
81 #[cfg(feature = "bench-tools")]
83 #[doc(hidden)]
84 #[inline]
85 pub fn inc_many(counters: &[Counter]) {
86 Self::add_many(counters, 1);
87 }
88
89 #[cfg(feature = "bench-tools")]
91 #[doc(hidden)]
92 #[inline]
93 pub fn add_many(counters: &[Counter], value: isize) {
94 Self::add_many_with_ordering(counters, value, Ordering::Relaxed);
95 }
96
97 #[cfg(feature = "bench-tools")]
99 #[doc(hidden)]
100 #[inline]
101 pub fn add_many_with_ordering(counters: &[Counter], value: isize, ordering: Ordering) {
102 let thread_id = thread_id();
103 for counter in counters {
104 counter.add_with_thread_id(thread_id, value, ordering);
105 }
106 }
107
108 #[inline]
116 pub fn sum(&self) -> isize {
117 self.sum_with_ordering(Ordering::Relaxed)
118 }
119
120 #[inline]
122 pub fn sum_with_ordering(&self, ordering: Ordering) -> isize {
123 self.cells.iter().map(|c| c.load(ordering)).sum()
124 }
125
126 #[inline]
139 pub fn swap(&self) -> isize {
140 self.cells
141 .iter()
142 .map(|c| c.swap(0, Ordering::Relaxed))
143 .sum()
144 }
145}
146
147#[cfg(feature = "bench-tools")]
149#[doc(hidden)]
150pub struct CounterSet {
151 cells: Vec<AtomicIsize>,
152 counters: usize,
153 stride: usize,
154 shard_mask: usize,
155}
156
157#[cfg(feature = "bench-tools")]
158impl CounterSet {
159 pub fn new(shards: usize, counters: usize) -> Self {
165 assert!(counters >= 1, "counters must be >= 1");
166 let shards = shards.next_power_of_two();
167 let cells_per_padded_counter =
168 std::mem::size_of::<CachePadded<AtomicIsize>>() / std::mem::size_of::<AtomicIsize>();
169 let row_padding = cells_per_padded_counter.max(1);
170 let stride = counters.div_ceil(row_padding) * row_padding;
171 let cells = (0..(shards * stride))
172 .map(|_| make_counter_cell())
173 .collect();
174
175 Self {
176 cells,
177 counters,
178 stride,
179 shard_mask: shards - 1,
180 }
181 }
182
183 #[inline]
185 pub fn len(&self) -> usize {
186 self.counters
187 }
188
189 #[inline]
191 pub fn is_empty(&self) -> bool {
192 self.counters == 0
193 }
194
195 #[inline]
196 fn current_shard_offset(&self) -> usize {
197 (thread_id() & self.shard_mask) * self.stride
198 }
199
200 #[inline]
201 fn cell_at(&self, index: usize) -> &AtomicIsize {
202 if cfg!(debug_assertions) {
203 self.cells.get(index).expect("index out of bounds")
204 } else {
205 unsafe { self.cells.get_unchecked(index) }
208 }
209 }
210
211 #[inline]
213 pub fn inc(&self, counter_idx: usize) {
214 self.add(counter_idx, 1);
215 }
216
217 #[inline]
219 pub fn add(&self, counter_idx: usize, value: isize) {
220 assert!(counter_idx < self.counters, "counter index out of bounds");
221 let offset = self.current_shard_offset();
222 self.cell_at(offset + counter_idx)
223 .fetch_add(value, Ordering::Relaxed);
224 }
225
226 #[inline]
228 pub fn inc_all(&self) {
229 self.add_all(1);
230 }
231
232 #[inline(always)]
234 pub fn add_all(&self, value: isize) {
235 let offset = self.current_shard_offset();
236 let row = if cfg!(debug_assertions) {
237 self.cells
238 .get(offset..offset + self.counters)
239 .expect("row index out of bounds")
240 } else {
241 unsafe { std::slice::from_raw_parts(self.cells.as_ptr().add(offset), self.counters) }
244 };
245 for cell in row {
246 cell.fetch_add(value, Ordering::Relaxed);
247 }
248 }
249
250 #[inline]
252 pub fn add_indices(&self, indexes: &[usize], value: isize) {
253 let offset = self.current_shard_offset();
254 if cfg!(debug_assertions) {
255 for index in indexes {
256 assert!(*index < self.counters, "counter index out of bounds");
257 }
258 }
259 for index in indexes {
260 self.cell_at(offset + *index)
261 .fetch_add(value, Ordering::Relaxed);
262 }
263 }
264
265 #[inline]
267 pub fn add_index_values(&self, updates: &[(usize, isize)]) {
268 let offset = self.current_shard_offset();
269 if cfg!(debug_assertions) {
270 for (index, _) in updates {
271 assert!(*index < self.counters, "counter index out of bounds");
272 }
273 }
274 for (index, value) in updates {
275 self.cell_at(offset + *index)
276 .fetch_add(*value, Ordering::Relaxed);
277 }
278 }
279
280 #[inline]
282 pub fn add_all_indexed(&self, value: isize) {
283 let offset = self.current_shard_offset();
284 for counter_idx in 0..self.counters {
285 self.cell_at(offset + counter_idx)
286 .fetch_add(value, Ordering::Relaxed);
287 }
288 }
289
290 #[inline(always)]
292 pub fn add_values(&self, values: &[isize]) {
293 assert_eq!(values.len(), self.counters, "values must match counters");
294 let offset = self.current_shard_offset();
295 match values {
296 [a] => {
297 self.cell_at(offset).fetch_add(*a, Ordering::Relaxed);
298 }
299 [a, b] => {
300 self.cell_at(offset).fetch_add(*a, Ordering::Relaxed);
301 self.cell_at(offset + 1).fetch_add(*b, Ordering::Relaxed);
302 }
303 [a, b, c] => {
304 self.cell_at(offset).fetch_add(*a, Ordering::Relaxed);
305 self.cell_at(offset + 1).fetch_add(*b, Ordering::Relaxed);
306 self.cell_at(offset + 2).fetch_add(*c, Ordering::Relaxed);
307 }
308 [a, b, c, d] => {
309 self.cell_at(offset).fetch_add(*a, Ordering::Relaxed);
310 self.cell_at(offset + 1).fetch_add(*b, Ordering::Relaxed);
311 self.cell_at(offset + 2).fetch_add(*c, Ordering::Relaxed);
312 self.cell_at(offset + 3).fetch_add(*d, Ordering::Relaxed);
313 }
314 [a, b, c, d, e] => {
315 self.cell_at(offset).fetch_add(*a, Ordering::Relaxed);
316 self.cell_at(offset + 1).fetch_add(*b, Ordering::Relaxed);
317 self.cell_at(offset + 2).fetch_add(*c, Ordering::Relaxed);
318 self.cell_at(offset + 3).fetch_add(*d, Ordering::Relaxed);
319 self.cell_at(offset + 4).fetch_add(*e, Ordering::Relaxed);
320 }
321 [a, b, c, d, e, f] => {
322 self.cell_at(offset).fetch_add(*a, Ordering::Relaxed);
323 self.cell_at(offset + 1).fetch_add(*b, Ordering::Relaxed);
324 self.cell_at(offset + 2).fetch_add(*c, Ordering::Relaxed);
325 self.cell_at(offset + 3).fetch_add(*d, Ordering::Relaxed);
326 self.cell_at(offset + 4).fetch_add(*e, Ordering::Relaxed);
327 self.cell_at(offset + 5).fetch_add(*f, Ordering::Relaxed);
328 }
329 [a, b, c, d, e, f, g] => {
330 self.cell_at(offset).fetch_add(*a, Ordering::Relaxed);
331 self.cell_at(offset + 1).fetch_add(*b, Ordering::Relaxed);
332 self.cell_at(offset + 2).fetch_add(*c, Ordering::Relaxed);
333 self.cell_at(offset + 3).fetch_add(*d, Ordering::Relaxed);
334 self.cell_at(offset + 4).fetch_add(*e, Ordering::Relaxed);
335 self.cell_at(offset + 5).fetch_add(*f, Ordering::Relaxed);
336 self.cell_at(offset + 6).fetch_add(*g, Ordering::Relaxed);
337 }
338 [a, b, c, d, e, f, g, h] => {
339 self.cell_at(offset).fetch_add(*a, Ordering::Relaxed);
340 self.cell_at(offset + 1).fetch_add(*b, Ordering::Relaxed);
341 self.cell_at(offset + 2).fetch_add(*c, Ordering::Relaxed);
342 self.cell_at(offset + 3).fetch_add(*d, Ordering::Relaxed);
343 self.cell_at(offset + 4).fetch_add(*e, Ordering::Relaxed);
344 self.cell_at(offset + 5).fetch_add(*f, Ordering::Relaxed);
345 self.cell_at(offset + 6).fetch_add(*g, Ordering::Relaxed);
346 self.cell_at(offset + 7).fetch_add(*h, Ordering::Relaxed);
347 }
348 values => {
349 let row = if cfg!(debug_assertions) {
350 self.cells
351 .get(offset..offset + self.counters)
352 .expect("row index out of bounds")
353 } else {
354 unsafe {
357 std::slice::from_raw_parts(self.cells.as_ptr().add(offset), self.counters)
358 }
359 };
360 for (cell, value) in row.iter().zip(values.iter().copied()) {
361 cell.fetch_add(value, Ordering::Relaxed);
362 }
363 }
364 }
365 }
366
367 #[inline]
369 pub fn sum(&self, counter_idx: usize) -> isize {
370 assert!(counter_idx < self.counters, "counter index out of bounds");
371 let shards = self.cells.len() / self.stride;
372 (0..shards)
373 .map(|shard| {
374 self.cell_at((shard * self.stride) + counter_idx)
375 .load(Ordering::Relaxed)
376 })
377 .sum()
378 }
379
380 #[inline]
382 pub fn sum_all(&self) -> isize {
383 let shards = self.cells.len() / self.stride;
384 let mut total = 0isize;
385 for shard in 0..shards {
386 let offset = shard * self.stride;
387 for counter_idx in 0..self.counters {
388 total += self.cell_at(offset + counter_idx).load(Ordering::Relaxed);
389 }
390 }
391 total
392 }
393}
394
395#[cfg(feature = "bench-tools")]
397#[doc(hidden)]
398pub struct CounterSetBuffer<'a> {
399 counters: &'a CounterSet,
400 deltas: Vec<isize>,
401 all_delta: isize,
402 ops_since_flush: usize,
403 flush_every: usize,
404 op_dirty: bool,
405}
406
407#[cfg(feature = "bench-tools")]
408impl<'a> CounterSetBuffer<'a> {
409 #[inline]
411 pub fn new(counters: &'a CounterSet, flush_every: usize) -> Self {
412 assert!(flush_every >= 1, "flush_every must be >= 1");
413 Self {
414 counters,
415 deltas: vec![0; counters.len()],
416 all_delta: 0,
417 ops_since_flush: 0,
418 flush_every,
419 op_dirty: false,
420 }
421 }
422
423 #[inline]
425 pub fn inc(&mut self, counter_idx: usize) {
426 self.add(counter_idx, 1);
427 }
428
429 #[inline]
431 pub fn add(&mut self, counter_idx: usize, value: isize) {
432 assert!(
433 counter_idx < self.deltas.len(),
434 "counter index out of bounds"
435 );
436 self.deltas[counter_idx] += value;
437 self.op_dirty = true;
438 }
439
440 #[inline(always)]
442 pub fn inc_all(&mut self) {
443 self.all_delta += 1;
444 self.finish_group_op();
445 }
446
447 #[inline(always)]
449 pub fn add_all(&mut self, value: isize) {
450 self.all_delta += value;
451 self.finish_group_op();
452 }
453
454 #[inline]
456 pub fn add_values(&mut self, values: &[isize]) {
457 assert_eq!(
458 values.len(),
459 self.deltas.len(),
460 "values must match counters"
461 );
462 for (delta, value) in self.deltas.iter_mut().zip(values.iter().copied()) {
463 *delta += value;
464 }
465 self.finish_group_op();
466 }
467
468 #[inline]
470 pub fn finish_op(&mut self) {
471 if !self.op_dirty {
472 return;
473 }
474
475 self.op_dirty = false;
476 self.finish_group_op();
477 }
478
479 #[inline(always)]
480 fn finish_group_op(&mut self) {
481 self.ops_since_flush += 1;
482 if self.ops_since_flush >= self.flush_every {
483 self.flush();
484 }
485 }
486
487 #[inline]
489 pub fn flush(&mut self) {
490 if self.ops_since_flush == 0 && !self.op_dirty {
491 return;
492 }
493
494 if self.all_delta != 0 {
495 self.counters.add_all(self.all_delta);
496 self.all_delta = 0;
497 }
498 if self.deltas.iter().any(|delta| *delta != 0) {
499 self.counters.add_values(&self.deltas);
500 self.deltas.fill(0);
501 }
502 self.ops_since_flush = 0;
503 self.op_dirty = false;
504 }
505}
506
507#[cfg(test)]
508mod tests {
509 use super::*;
510
511 #[test]
512 fn basic_test() {
513 let counter = Counter::new(1);
514 counter.add(1);
515 assert_eq!(counter.sum(), 1);
516 }
517
518 #[test]
519 fn increment_multiple_times() {
520 let counter = Counter::new(1);
521 counter.add(1);
522 counter.add(1);
523 counter.add(1);
524 assert_eq!(counter.sum(), 3);
525 }
526
527 #[test]
528 fn test_inc() {
529 let counter = Counter::new(4);
530 counter.inc();
531 counter.inc();
532 assert_eq!(counter.sum(), 2);
533 }
534
535 #[test]
536 fn test_swap() {
537 let counter = Counter::new(4);
538 counter.add(100);
539 let val = counter.swap();
540 assert_eq!(val, 100);
541 assert_eq!(counter.sum(), 0);
542 }
543
544 #[test]
545 fn two_threads_incrementing_concurrently() {
546 let counter = Counter::new(2);
547
548 std::thread::scope(|s| {
549 for _ in 0..2 {
550 s.spawn(|| {
551 counter.add(1);
552 });
553 }
554 });
555
556 assert_eq!(counter.sum(), 2);
557 }
558
559 #[test]
560 fn multiple_threads_incrementing_many_times() {
561 const WRITE_COUNT: isize = 1_000_000;
562 const THREAD_COUNT: isize = 8;
563
564 let counter = Counter::new(THREAD_COUNT as usize);
565
566 std::thread::scope(|s| {
567 for _ in 0..THREAD_COUNT {
568 s.spawn(|| {
569 for _ in 0..WRITE_COUNT {
570 counter.add(1);
571 }
572 });
573 }
574 });
575
576 assert_eq!(counter.sum(), THREAD_COUNT * WRITE_COUNT);
577 }
578
579 #[test]
580 fn debug_format() {
581 let counter = Counter::new(8);
582 counter.add(42);
583 let debug = format!("{counter:?}");
584 assert!(debug.contains("sum: 42"));
585 assert!(debug.contains("cells: 8"));
586 }
587
588 #[cfg(feature = "bench-tools")]
589 #[test]
590 fn inc_many_updates_all_counters() {
591 let counters = vec![Counter::new(4), Counter::new(4), Counter::new(4)];
592
593 Counter::inc_many(&counters);
594 Counter::add_many(&counters, 2);
595
596 for counter in &counters {
597 assert_eq!(counter.sum(), 3);
598 }
599 }
600
601 #[cfg(feature = "bench-tools")]
602 #[test]
603 fn counter_set_updates_grouped_counters() {
604 let counters = CounterSet::new(4, 3);
605
606 counters.inc(0);
607 counters.add(1, 2);
608 counters.inc_all();
609 counters.add_values(&[2, 3, 4]);
610 counters.add_indices(&[0, 2], 1);
611 counters.add_index_values(&[(1, 2), (2, 3)]);
612
613 assert_eq!(counters.len(), 3);
614 assert!(!counters.is_empty());
615 assert_eq!(counters.sum(0), 5);
616 assert_eq!(counters.sum(1), 8);
617 assert_eq!(counters.sum(2), 9);
618 assert_eq!(counters.sum_all(), 22);
619 }
620
621 #[cfg(feature = "bench-tools")]
622 #[test]
623 fn counter_set_buffer_flushes_grouped_and_individual_updates() {
624 let counters = CounterSet::new(4, 3);
625
626 {
627 let mut buffer = CounterSetBuffer::new(&counters, 2);
628
629 buffer.inc(0);
630 buffer.add(1, 2);
631 buffer.finish_op();
632 assert_eq!(counters.sum_all(), 0);
633
634 buffer.inc_all();
635 assert_eq!(counters.sum(0), 2);
636 assert_eq!(counters.sum(1), 3);
637 assert_eq!(counters.sum(2), 1);
638
639 buffer.add(2, 4);
640 buffer.finish_op();
641 buffer.flush();
642 }
643
644 assert_eq!(counters.sum(0), 2);
645 assert_eq!(counters.sum(1), 3);
646 assert_eq!(counters.sum(2), 5);
647 assert_eq!(counters.sum_all(), 10);
648 }
649}