counters(3)

πŸ“¦
version 29.0.6 Β· 3 versions
, in ubuntu stonking

✨ counters(3) β€” Counter Functions ✨

okay bestie, welcome to the counters module from Erlang's own manual (page counters(3), filed under Erlang Module Definition, and yes this is the whole thing, no skips, no cap) πŸ’…


πŸ’– DESCRIPTION πŸ’–

So basically this module hands you a bunch of functions for doing operations on shared mutable counter variables β€” yes, mutable, we're being chaotic today 🫢. The way it does that is that it doesn't use any software level locking at all, which is exactly why it's so efficient when a million processes all want a piece of the same counter. It's giving main character energy.

The counters live inside arrays, and here's the semantics of those arrays, read 'em and weep:

  • Counters are 64 bit signed integers.
  • Counters wrap around at overflow and underflow operations. (yeah they just loop back around, iconic behavior honestly)
  • Counters are initialized to zero.
  • Write operations guarantee atomicity. No intermediate results can be seen from a single write operation. periodt.
  • You can create two types of counter arrays with the options atomics or write_concurrency. The atomics counters have good allround performance with nice consistent semantics, while write_concurrency counters offer even better concurrent write performance β€” at the expense of some potential read inconsistencies. Go look at new/2 for the details πŸ‘‘
  • Indexes into counter arrays are one-based. A counter array of size N contains N counters with index from 1 to N. (yes bestie, 1, not 0, we said what we said)

πŸ¦‹ DATA TYPES πŸ¦‹

  • -opaque counters_ref() β€” this is the thing that identifies a counter array, and it's what you get handed back from new/2. Treat it like a little ticket πŸŽ€

πŸŽ€ FUNCTIONS πŸŽ€

🌸 add(Ref, Ix, Incr) -> ok

when
    Ref :: counters_ref(),
    Ix :: pos_integer(),
    Incr :: integer().

Adds Incr to the counter sitting at index Ix. That's it, that's the whole job ✨


🌈 get(Ref, Ix) -> integer()

when Ref :: counters_ref(), Ix :: pos_integer().

Reads the counter value. Just reads it, no notes πŸ“–


πŸ“ info(Ref) -> Info

when
    Ref :: counters_ref(),
    Info :: #{size := Size, memory := Memory},
    Size :: non_neg_integer(),
    Memory :: non_neg_integer().

Returns info about a counter array, wrapped up all cute in a map πŸ’Œ

The map has the following keys (at least β€” so like, minimum these, could be more someday):

  • size β€” the number of counters in the array.
  • memory β€” the approximate memory consumption for the array, in bytes. Approximate, bestie, don't come for me with a calculator πŸ”₯

🧚 new(Size, Opts) -> counters_ref()

when
    Size :: pos_integer(),
    Opts :: [Opt],
    Opt :: atomics | write_concurrency.

Creates a brand new counter array of Size counters, and every single counter in it starts at zero. fresh start, glow up, all of that πŸ’œ

The argument Opts is a list of these possible options:

  • atomics β€” (this is the Default, so if you don't pick anything, you get this one) Counters will be sequentially consistent. So if write operation A is done sequentially before write operation B, then a concurrent reader may see the result of none of them, only A, or both A and B. It cannot see the result of only B. Very demure, very mindful, very consistent 🌸
  • write_concurrency β€” this one is an optimization to achieve very efficient concurrent add and sub operations, and it pays for that with potential read inconsistency and memory consumption per counter. So like... tradeoffs, queen πŸ’…

Read operations may see sequentially inconsistent results with regard to concurrent write operations. Even if write operation A is done sequentially before write operation B, a concurrent reader may see any combination of A and B, including only B. A read operation is only guaranteed to see all writes done sequentially before the read. No writes are ever lost, though β€” they will eventually all be seen. (not lost, just fashionably late 😭)

The typical use case for write_concurrency is when concurrent calls to add and sub toward the same counters are very frequent, while calls to get and put are much less frequent. The lack of absolute read consistency must also be acceptable. So know what you're signing up for, fr fr.

Counters are not tied to the current process, and they get automatically garbage collected when they're no longer referenced. they clean up after themselves, we love a self-sufficient icon 🫢


πŸ‘‘ put(Ref, Ix, Value) -> ok

when
    Ref :: counters_ref(),
    Ix :: pos_integer(),
    Value :: integer().

Writes Value to the counter at index Ix.

NOTE: Despite its name, the write_concurrency optimization does not improve put. A call to put is a relatively heavy operation compared to the very lightweight and scalable add and sub. The cost for a put with write_concurrency is like a get plus a put without write_concurrency. so yeah, the name is a lil misleading, don't @ me, I'm just the messenger πŸ’€

πŸ¦„ sub(Ref, Ix, Decr) -> ok

when
    Ref :: counters_ref(),
    Ix :: pos_integer(),
    Decr :: integer().

Subtracts Decr from the counter at index Ix. math but make it concurrent ⭐


and that's the whole page, bestie!! no crumbs left, we kept every single option, every type spec, every warning, all of it πŸ’–

signing off with the receipts: this page is brought to you by Ericsson AB, running erts 17.0.6, and it's the counters(3) page. slay, go increment something responsibly πŸŒˆπŸŽ€

πŸ“¦ where this page comes from