lib/gpalloc/src/stats.zig

daab053ee43316e1809a84551d573ddd1e5bf3d2

  1 const std = @import("std");
  2 
  3 const native_u64_atomics = @bitSizeOf(usize) >= 64;
  4 
  5 const AtomicU32 = std.atomic.Value(u32);
  6 
  7 pub const AtomicU64 = if (native_u64_atomics) std.atomic.Value(u64) else SplitAtomicU64;
  8 pub const AtomicUsize = std.atomic.Value(usize);
  9 
 10 const SplitAtomicU64 = struct {
 11     low: AtomicU32,
 12     high: AtomicU32,
 13 
 14     pub fn init(value: u64) SplitAtomicU64 {
 15         return .{
 16             .low = AtomicU32.init(@truncate(value)),
 17             .high = AtomicU32.init(@intCast(value >> 32)),
 18         };
 19     }
 20 
 21     pub fn load(counter: *const SplitAtomicU64, comptime order: std.builtin.AtomicOrder) u64 {
 22         const high = counter.high.load(order);
 23         const low = counter.low.load(order);
 24         return (@as(u64, high) << 32) | low;
 25     }
 26 
 27     pub fn store(counter: *SplitAtomicU64, value: u64, comptime order: std.builtin.AtomicOrder) void {
 28         counter.low.store(@truncate(value), order);
 29         counter.high.store(@intCast(value >> 32), order);
 30     }
 31 
 32     pub fn fetchAdd(counter: *SplitAtomicU64, amount: u64, comptime order: std.builtin.AtomicOrder) u64 {
 33         const low_amount: u32 = @truncate(amount);
 34         const high_amount: u32 = @intCast(amount >> 32);
 35         const previous_low = counter.low.fetchAdd(low_amount, order);
 36         const carry: u32 = if (@as(u64, previous_low) + low_amount > std.math.maxInt(u32)) 1 else 0;
 37         const high_delta = high_amount +% carry;
 38         const previous_high = if (high_delta == 0)
 39             counter.high.load(order)
 40         else
 41             counter.high.fetchAdd(high_delta, order);
 42         return (@as(u64, previous_high) << 32) | previous_low;
 43     }
 44 };
 45 
 46 pub const Stats = struct {
 47     small_allocations: u64 = 0,
 48     small_frees: u64 = 0,
 49     large_allocations: u64 = 0,
 50     large_frees: u64 = 0,
 51     pages_allocated: u64 = 0,
 52     pages_freed: u64 = 0,
 53     empty_page_discards: u64 = 0,
 54     retained_empty_pages: usize = 0,
 55     discarded_small_bytes: usize = 0,
 56     active_small_bytes: usize = 0,
 57     active_large_bytes: usize = 0,
 58     mapped_small_bytes: usize = 0,
 59 };
 60 
 61 pub const AtomicStats = struct {
 62     small_allocations: AtomicU64 = AtomicU64.init(0),
 63     small_frees: AtomicU64 = AtomicU64.init(0),
 64     large_allocations: AtomicU64 = AtomicU64.init(0),
 65     large_frees: AtomicU64 = AtomicU64.init(0),
 66     pages_allocated: AtomicU64 = AtomicU64.init(0),
 67     pages_freed: AtomicU64 = AtomicU64.init(0),
 68     empty_page_discards: AtomicU64 = AtomicU64.init(0),
 69     retained_empty_pages: AtomicUsize = AtomicUsize.init(0),
 70     discarded_small_bytes: AtomicUsize = AtomicUsize.init(0),
 71     active_small_bytes: AtomicUsize = AtomicUsize.init(0),
 72     active_large_bytes: AtomicUsize = AtomicUsize.init(0),
 73     mapped_small_bytes: AtomicUsize = AtomicUsize.init(0),
 74 
 75     pub fn snapshot(stats: *const AtomicStats) Stats {
 76         return .{
 77             .small_allocations = stats.small_allocations.load(.monotonic),
 78             .small_frees = stats.small_frees.load(.monotonic),
 79             .large_allocations = stats.large_allocations.load(.monotonic),
 80             .large_frees = stats.large_frees.load(.monotonic),
 81             .pages_allocated = stats.pages_allocated.load(.monotonic),
 82             .pages_freed = stats.pages_freed.load(.monotonic),
 83             .empty_page_discards = stats.empty_page_discards.load(.monotonic),
 84             .retained_empty_pages = stats.retained_empty_pages.load(.monotonic),
 85             .discarded_small_bytes = stats.discarded_small_bytes.load(.monotonic),
 86             .active_small_bytes = stats.active_small_bytes.load(.monotonic),
 87             .active_large_bytes = stats.active_large_bytes.load(.monotonic),
 88             .mapped_small_bytes = stats.mapped_small_bytes.load(.monotonic),
 89         };
 90     }
 91 
 92     pub fn reset(stats: *AtomicStats) void {
 93         stats.small_allocations.store(0, .monotonic);
 94         stats.small_frees.store(0, .monotonic);
 95         stats.large_allocations.store(0, .monotonic);
 96         stats.large_frees.store(0, .monotonic);
 97         stats.pages_allocated.store(0, .monotonic);
 98         stats.pages_freed.store(0, .monotonic);
 99         stats.empty_page_discards.store(0, .monotonic);
100         stats.retained_empty_pages.store(0, .monotonic);
101         stats.discarded_small_bytes.store(0, .monotonic);
102         stats.active_small_bytes.store(0, .monotonic);
103         stats.active_large_bytes.store(0, .monotonic);
104         stats.mapped_small_bytes.store(0, .monotonic);
105     }
106 };
107 
108 pub fn addU64(counter: *AtomicU64, amount: u64) void {
109     _ = counter.fetchAdd(amount, .monotonic);
110 }
111 
112 pub fn addUsize(counter: *AtomicUsize, amount: usize) void {
113     _ = counter.fetchAdd(amount, .monotonic);
114 }
115 
116 pub fn subUsize(counter: *AtomicUsize, amount: usize) void {
117     const previous = counter.fetchSub(amount, .monotonic);
118     std.debug.assert(previous >= amount);
119 }
120 
121 test "split u64 counter carries across low word" {
122     var counter = SplitAtomicU64.init(std.math.maxInt(u32));
123     try std.testing.expectEqual(@as(u64, std.math.maxInt(u32)), counter.load(.monotonic));
124     _ = counter.fetchAdd(1, .monotonic);
125     try std.testing.expectEqual(@as(u64, std.math.maxInt(u32)) + 1, counter.load(.monotonic));
126     counter.store((@as(u64, 3) << 32) + 7, .monotonic);
127     try std.testing.expectEqual((@as(u64, 3) << 32) + 7, counter.load(.monotonic));
128 }