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
17fn make_counter_cell() -> AtomicIsize {
18 AtomicIsize::new(0)
19}
20
21pub struct Counter {
26 cells: Vec<CachePadded<AtomicIsize>>,
27}
28
29impl fmt::Debug for Counter {
30 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
31 f.debug_struct("Counter")
32 .field("sum", &self.sum())
33 .field("cells", &self.cells.len())
34 .finish()
35 }
36}
37
38impl Counter {
39 #[inline]
43 pub fn new(count: usize) -> Self {
44 let count = count.next_power_of_two();
45 Self {
46 cells: (0..count).map(|_| make_padded_counter()).collect(),
47 }
48 }
49
50 #[inline]
52 pub fn add(&self, value: isize) {
53 self.add_with_ordering(value, Ordering::Relaxed)
54 }
55
56 #[inline]
58 pub fn inc(&self) {
59 self.add(1)
60 }
61
62 #[inline]
63 fn add_with_thread_id(&self, thread_id: usize, value: isize, ordering: Ordering) {
64 let idx = thread_id & (self.cells.len() - 1);
65 let cell = if cfg!(debug_assertions) {
67 self.cells.get(idx).expect("index out of bounds")
68 } else {
69 unsafe { self.cells.get_unchecked(idx) }
70 };
71 cell.fetch_add(value, ordering);
72 }
73
74 #[inline]
76 pub fn add_with_ordering(&self, value: isize, ordering: Ordering) {
77 self.add_with_thread_id(thread_id(), value, ordering);
78 }
79
80 #[cfg(feature = "bench-tools")]
82 #[doc(hidden)]
83 #[inline]
84 pub fn inc_many(counters: &[Counter]) {
85 Self::add_many(counters, 1);
86 }
87
88 #[cfg(feature = "bench-tools")]
90 #[doc(hidden)]
91 #[inline]
92 pub fn add_many(counters: &[Counter], value: isize) {
93 Self::add_many_with_ordering(counters, value, Ordering::Relaxed);
94 }
95
96 #[cfg(feature = "bench-tools")]
98 #[doc(hidden)]
99 #[inline]
100 pub fn add_many_with_ordering(counters: &[Counter], value: isize, ordering: Ordering) {
101 let thread_id = thread_id();
102 for counter in counters {
103 counter.add_with_thread_id(thread_id, value, ordering);
104 }
105 }
106
107 #[inline]
115 pub fn sum(&self) -> isize {
116 self.sum_with_ordering(Ordering::Relaxed)
117 }
118
119 #[inline]
121 pub fn sum_with_ordering(&self, ordering: Ordering) -> isize {
122 self.cells.iter().map(|c| c.load(ordering)).sum()
123 }
124
125 #[inline]
138 pub fn swap(&self) -> isize {
139 self.cells
140 .iter()
141 .map(|c| c.swap(0, Ordering::Relaxed))
142 .sum()
143 }
144}
145
146pub struct CounterSet {
175 cells: Vec<AtomicIsize>,
176 counters: usize,
177 stride: usize,
178 shard_mask: usize,
179}
180
181impl CounterSet {
182 pub fn new(shards: usize, counters: usize) -> Self {
188 assert!(counters >= 1, "counters must be >= 1");
189 let shards = shards.next_power_of_two();
190 let cells_per_padded_counter =
191 std::mem::size_of::<CachePadded<AtomicIsize>>() / std::mem::size_of::<AtomicIsize>();
192 let row_padding = cells_per_padded_counter.max(1);
193 let stride = counters.div_ceil(row_padding) * row_padding;
194 let cells = (0..(shards * stride))
195 .map(|_| make_counter_cell())
196 .collect();
197
198 Self {
199 cells,
200 counters,
201 stride,
202 shard_mask: shards - 1,
203 }
204 }
205
206 #[inline]
208 pub fn len(&self) -> usize {
209 self.counters
210 }
211
212 #[inline]
214 pub fn is_empty(&self) -> bool {
215 self.counters == 0
216 }
217
218 #[inline]
219 fn current_shard_offset(&self) -> usize {
220 (thread_id() & self.shard_mask) * self.stride
221 }
222
223 #[inline]
224 fn cell_at(&self, index: usize) -> &AtomicIsize {
225 if cfg!(debug_assertions) {
226 self.cells.get(index).expect("index out of bounds")
227 } else {
228 unsafe { self.cells.get_unchecked(index) }
231 }
232 }
233
234 #[inline]
236 pub fn inc(&self, counter_idx: usize) {
237 self.add(counter_idx, 1);
238 }
239
240 #[inline]
242 pub fn add(&self, counter_idx: usize, value: isize) {
243 assert!(counter_idx < self.counters, "counter index out of bounds");
244 let offset = self.current_shard_offset();
245 self.cell_at(offset + counter_idx)
246 .fetch_add(value, Ordering::Relaxed);
247 }
248
249 #[inline]
251 pub fn inc_all(&self) {
252 self.add_all(1);
253 }
254
255 #[inline(always)]
257 pub fn add_all(&self, value: isize) {
258 let offset = self.current_shard_offset();
259 let row = if cfg!(debug_assertions) {
260 self.cells
261 .get(offset..offset + self.counters)
262 .expect("row index out of bounds")
263 } else {
264 unsafe { std::slice::from_raw_parts(self.cells.as_ptr().add(offset), self.counters) }
267 };
268 for cell in row {
269 cell.fetch_add(value, Ordering::Relaxed);
270 }
271 }
272
273 #[inline]
275 pub fn add_indices(&self, indexes: &[usize], value: isize) {
276 let offset = self.current_shard_offset();
277 for index in indexes {
278 assert!(*index < self.counters, "counter index out of bounds");
279 self.cell_at(offset + *index)
280 .fetch_add(value, Ordering::Relaxed);
281 }
282 }
283
284 #[inline]
286 pub fn add_index_values(&self, updates: &[(usize, isize)]) {
287 let offset = self.current_shard_offset();
288 for (index, value) in updates {
289 assert!(*index < self.counters, "counter index out of bounds");
290 self.cell_at(offset + *index)
291 .fetch_add(*value, Ordering::Relaxed);
292 }
293 }
294
295 #[inline]
297 pub fn add_all_indexed(&self, value: isize) {
298 let offset = self.current_shard_offset();
299 for counter_idx in 0..self.counters {
300 self.cell_at(offset + counter_idx)
301 .fetch_add(value, Ordering::Relaxed);
302 }
303 }
304
305 #[inline(always)]
307 pub fn add_values(&self, values: &[isize]) {
308 assert_eq!(values.len(), self.counters, "values must match counters");
309 let offset = self.current_shard_offset();
310 match values {
311 [a] => {
312 self.cell_at(offset).fetch_add(*a, Ordering::Relaxed);
313 }
314 [a, b] => {
315 self.cell_at(offset).fetch_add(*a, Ordering::Relaxed);
316 self.cell_at(offset + 1).fetch_add(*b, Ordering::Relaxed);
317 }
318 [a, b, c] => {
319 self.cell_at(offset).fetch_add(*a, Ordering::Relaxed);
320 self.cell_at(offset + 1).fetch_add(*b, Ordering::Relaxed);
321 self.cell_at(offset + 2).fetch_add(*c, Ordering::Relaxed);
322 }
323 [a, b, c, d] => {
324 self.cell_at(offset).fetch_add(*a, Ordering::Relaxed);
325 self.cell_at(offset + 1).fetch_add(*b, Ordering::Relaxed);
326 self.cell_at(offset + 2).fetch_add(*c, Ordering::Relaxed);
327 self.cell_at(offset + 3).fetch_add(*d, Ordering::Relaxed);
328 }
329 [a, b, c, d, e] => {
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 }
336 [a, b, c, d, e, f] => {
337 self.cell_at(offset).fetch_add(*a, Ordering::Relaxed);
338 self.cell_at(offset + 1).fetch_add(*b, Ordering::Relaxed);
339 self.cell_at(offset + 2).fetch_add(*c, Ordering::Relaxed);
340 self.cell_at(offset + 3).fetch_add(*d, Ordering::Relaxed);
341 self.cell_at(offset + 4).fetch_add(*e, Ordering::Relaxed);
342 self.cell_at(offset + 5).fetch_add(*f, Ordering::Relaxed);
343 }
344 [a, b, c, d, e, f, g] => {
345 self.cell_at(offset).fetch_add(*a, Ordering::Relaxed);
346 self.cell_at(offset + 1).fetch_add(*b, Ordering::Relaxed);
347 self.cell_at(offset + 2).fetch_add(*c, Ordering::Relaxed);
348 self.cell_at(offset + 3).fetch_add(*d, Ordering::Relaxed);
349 self.cell_at(offset + 4).fetch_add(*e, Ordering::Relaxed);
350 self.cell_at(offset + 5).fetch_add(*f, Ordering::Relaxed);
351 self.cell_at(offset + 6).fetch_add(*g, Ordering::Relaxed);
352 }
353 [a, b, c, d, e, f, g, h] => {
354 self.cell_at(offset).fetch_add(*a, Ordering::Relaxed);
355 self.cell_at(offset + 1).fetch_add(*b, Ordering::Relaxed);
356 self.cell_at(offset + 2).fetch_add(*c, Ordering::Relaxed);
357 self.cell_at(offset + 3).fetch_add(*d, Ordering::Relaxed);
358 self.cell_at(offset + 4).fetch_add(*e, Ordering::Relaxed);
359 self.cell_at(offset + 5).fetch_add(*f, Ordering::Relaxed);
360 self.cell_at(offset + 6).fetch_add(*g, Ordering::Relaxed);
361 self.cell_at(offset + 7).fetch_add(*h, Ordering::Relaxed);
362 }
363 values => {
364 let row = if cfg!(debug_assertions) {
365 self.cells
366 .get(offset..offset + self.counters)
367 .expect("row index out of bounds")
368 } else {
369 unsafe {
372 std::slice::from_raw_parts(self.cells.as_ptr().add(offset), self.counters)
373 }
374 };
375 for (cell, value) in row.iter().zip(values.iter().copied()) {
376 cell.fetch_add(value, Ordering::Relaxed);
377 }
378 }
379 }
380 }
381
382 #[inline]
384 pub fn sum(&self, counter_idx: usize) -> isize {
385 assert!(counter_idx < self.counters, "counter index out of bounds");
386 let shards = self.cells.len() / self.stride;
387 (0..shards)
388 .map(|shard| {
389 self.cell_at((shard * self.stride) + counter_idx)
390 .load(Ordering::Relaxed)
391 })
392 .sum()
393 }
394
395 #[inline]
397 pub fn sum_all(&self) -> isize {
398 let shards = self.cells.len() / self.stride;
399 let mut total = 0isize;
400 for shard in 0..shards {
401 let offset = shard * self.stride;
402 for counter_idx in 0..self.counters {
403 total += self.cell_at(offset + counter_idx).load(Ordering::Relaxed);
404 }
405 }
406 total
407 }
408}
409
410pub struct CounterSetBuffer<'a> {
451 counters: &'a CounterSet,
452 deltas: Vec<isize>,
453 all_delta: isize,
454 ops_since_flush: usize,
455 flush_every: usize,
456 op_dirty: bool,
457}
458
459impl<'a> CounterSetBuffer<'a> {
460 #[inline]
462 pub fn new(counters: &'a CounterSet, flush_every: usize) -> Self {
463 assert!(flush_every >= 1, "flush_every must be >= 1");
464 Self {
465 counters,
466 deltas: vec![0; counters.len()],
467 all_delta: 0,
468 ops_since_flush: 0,
469 flush_every,
470 op_dirty: false,
471 }
472 }
473
474 #[inline]
476 pub fn inc(&mut self, counter_idx: usize) {
477 self.add(counter_idx, 1);
478 }
479
480 #[inline]
482 pub fn add(&mut self, counter_idx: usize, value: isize) {
483 assert!(
484 counter_idx < self.deltas.len(),
485 "counter index out of bounds"
486 );
487 self.deltas[counter_idx] += value;
488 self.op_dirty = true;
489 }
490
491 #[inline(always)]
493 pub fn inc_all(&mut self) {
494 self.all_delta += 1;
495 self.finish_group_op();
496 }
497
498 #[inline(always)]
500 pub fn add_all(&mut self, value: isize) {
501 self.all_delta += value;
502 self.finish_group_op();
503 }
504
505 #[inline]
507 pub fn add_values(&mut self, values: &[isize]) {
508 assert_eq!(
509 values.len(),
510 self.deltas.len(),
511 "values must match counters"
512 );
513 for (delta, value) in self.deltas.iter_mut().zip(values.iter().copied()) {
514 *delta += value;
515 }
516 self.finish_group_op();
517 }
518
519 #[inline]
521 pub fn finish_op(&mut self) {
522 if !self.op_dirty {
523 return;
524 }
525
526 self.op_dirty = false;
527 self.finish_group_op();
528 }
529
530 #[inline(always)]
531 fn finish_group_op(&mut self) {
532 self.ops_since_flush += 1;
533 if self.ops_since_flush >= self.flush_every {
534 self.flush();
535 }
536 }
537
538 #[inline]
540 pub fn flush(&mut self) {
541 if self.ops_since_flush == 0 && !self.op_dirty {
542 return;
543 }
544
545 if self.all_delta != 0 {
546 self.counters.add_all(self.all_delta);
547 self.all_delta = 0;
548 }
549 if self.deltas.iter().any(|delta| *delta != 0) {
550 self.counters.add_values(&self.deltas);
551 self.deltas.fill(0);
552 }
553 self.ops_since_flush = 0;
554 self.op_dirty = false;
555 }
556}
557
558impl Drop for CounterSetBuffer<'_> {
559 fn drop(&mut self) {
560 self.flush();
561 }
562}
563
564#[cfg(test)]
565mod tests {
566 use super::*;
567
568 #[test]
569 fn basic_test() {
570 let counter = Counter::new(1);
571 counter.add(1);
572 assert_eq!(counter.sum(), 1);
573 }
574
575 #[test]
576 fn increment_multiple_times() {
577 let counter = Counter::new(1);
578 counter.add(1);
579 counter.add(1);
580 counter.add(1);
581 assert_eq!(counter.sum(), 3);
582 }
583
584 #[test]
585 fn test_inc() {
586 let counter = Counter::new(4);
587 counter.inc();
588 counter.inc();
589 assert_eq!(counter.sum(), 2);
590 }
591
592 #[test]
593 fn test_swap() {
594 let counter = Counter::new(4);
595 counter.add(100);
596 let val = counter.swap();
597 assert_eq!(val, 100);
598 assert_eq!(counter.sum(), 0);
599 }
600
601 #[test]
602 fn two_threads_incrementing_concurrently() {
603 let counter = Counter::new(2);
604
605 std::thread::scope(|s| {
606 for _ in 0..2 {
607 s.spawn(|| {
608 counter.add(1);
609 });
610 }
611 });
612
613 assert_eq!(counter.sum(), 2);
614 }
615
616 #[test]
617 fn multiple_threads_incrementing_many_times() {
618 const WRITE_COUNT: isize = 1_000_000;
619 const THREAD_COUNT: isize = 8;
620
621 let counter = Counter::new(THREAD_COUNT as usize);
622
623 std::thread::scope(|s| {
624 for _ in 0..THREAD_COUNT {
625 s.spawn(|| {
626 for _ in 0..WRITE_COUNT {
627 counter.add(1);
628 }
629 });
630 }
631 });
632
633 assert_eq!(counter.sum(), THREAD_COUNT * WRITE_COUNT);
634 }
635
636 #[test]
637 fn debug_format() {
638 let counter = Counter::new(8);
639 counter.add(42);
640 let debug = format!("{counter:?}");
641 assert!(debug.contains("sum: 42"));
642 assert!(debug.contains("cells: 8"));
643 }
644
645 #[cfg(feature = "bench-tools")]
646 #[test]
647 fn inc_many_updates_all_counters() {
648 let counters = vec![Counter::new(4), Counter::new(4), Counter::new(4)];
649
650 Counter::inc_many(&counters);
651 Counter::add_many(&counters, 2);
652
653 for counter in &counters {
654 assert_eq!(counter.sum(), 3);
655 }
656 }
657
658 #[test]
659 fn counter_set_updates_grouped_counters() {
660 let counters = CounterSet::new(4, 3);
661
662 counters.inc(0);
663 counters.add(1, 2);
664 counters.inc_all();
665 counters.add_values(&[2, 3, 4]);
666 counters.add_indices(&[0, 2], 1);
667 counters.add_index_values(&[(1, 2), (2, 3)]);
668
669 assert_eq!(counters.len(), 3);
670 assert!(!counters.is_empty());
671 assert_eq!(counters.sum(0), 5);
672 assert_eq!(counters.sum(1), 8);
673 assert_eq!(counters.sum(2), 9);
674 assert_eq!(counters.sum_all(), 22);
675 }
676
677 #[test]
678 fn counter_set_buffer_flushes_grouped_and_individual_updates() {
679 let counters = CounterSet::new(4, 3);
680
681 {
682 let mut buffer = CounterSetBuffer::new(&counters, 2);
683
684 buffer.inc(0);
685 buffer.add(1, 2);
686 buffer.finish_op();
687 assert_eq!(counters.sum_all(), 0);
688
689 buffer.inc_all();
690 assert_eq!(counters.sum(0), 2);
691 assert_eq!(counters.sum(1), 3);
692 assert_eq!(counters.sum(2), 1);
693
694 buffer.add(2, 4);
695 buffer.finish_op();
696 buffer.flush();
697 }
698
699 assert_eq!(counters.sum(0), 2);
700 assert_eq!(counters.sum(1), 3);
701 assert_eq!(counters.sum(2), 5);
702 assert_eq!(counters.sum_all(), 10);
703 }
704
705 #[test]
706 fn counter_set_buffer_flushes_on_drop() {
707 let counters = CounterSet::new(4, 2);
708
709 {
710 let mut buffer = CounterSetBuffer::new(&counters, 64);
711 buffer.inc(0);
712 buffer.add(1, 5);
713 buffer.finish_op();
714 }
715
716 assert_eq!(counters.sum(0), 1);
717 assert_eq!(counters.sum(1), 5);
718 }
719}