HN user

enduku

324 karma

Blog: www.densebit.com Twitter: @densebit Email: thynktank@gmail.com GitHub: https://github.com/xtellect

Posts18
Comments67
View on HN
github.com 2mo ago

Vibe, A single-header lock-free networking library for Linux

enduku
4pts0
github.com 2mo ago

Fc, a lossless compressor for floating-point streams

enduku
108pts33
github.com 2mo ago

Show HN: Vibe, a single-header C networking library for Linux

enduku
4pts0
github.com 2mo ago

Cactus, a work-stealing parallel recursion runtime for C

enduku
14pts2
github.com 3mo ago

Show HN: jsoon, a streaming JSON parser and query engine in C

enduku
3pts0
github.com 3mo ago

Cactus, a work-stealing parallel recursion runtime for C

enduku
4pts3
github.com 3mo ago

A single-file C allocator with explicit heaps and tuning knobs

enduku
57pts44
www.youtube.com 2y ago

How branch prediction works in CPUs [video]

enduku
2pts0
www.brooker.co.za 2y ago

Not Just Scale – Why thinking in distributed systems is important

enduku
14pts0
hanlab.mit.edu 2y ago

TinyChat: Large Language Model on the Edge

enduku
2pts1
www.zdnet.com 3y ago

The Hyena code is able to handle data that makes GPT models throw OOM error

enduku
1pts0
en.wikipedia.org 3y ago

Gio Wiederhold the database design pioneer passes away

enduku
3pts0
streaming.media.ccc.de 3y ago

50 years of C, the good, the bad and the ugly [video]

enduku
156pts254
github.com 3y ago

Kawipiko – fast static HTTP server in Go

enduku
68pts22
arxiv.org 4y ago

Do Ideas Have Shape?

enduku
2pts1
www.densebit.com 4y ago

The Design and Topology of an Algorithm

enduku
1pts0
software.intel.com 4y ago

Some C and C++ refactoring tips/pitfalls

enduku
2pts1
densebit.com 4y ago

Autogenerating New Algorithms

enduku
2pts0

yeah the site's clearly vibecoded and isn't opensource, but i also think this is a genuinely interesting design space and more people should be building in it. APL (https://www.dyalog.com/), BQN (https://mlochbaum.github.io/BQN/), J/Jd (https://code.jsoftware.com/wiki/Jd/Overview), Klong (https://news.ycombinator.com/item?id=10586872), Kerf (https://news.ycombinator.com/item?id=9782520), RayforceDB (https://news.ycombinator.com/item?id=45889607), k/q (https://kx.com/) glad there's a new entrant.

Fair points on both. Thanks for aasking.

The 120 MiB/s encode ceiling is the cost of the mode competition. that's where the ratio comes from. At 800-1600 MiB/s off a digitizer, fc is the bottleneck no matter what transport sits behind it; for that regime zstd-3 or lz4 are the better fit, or fc further down the pipeline on aggregated/decimated data.

You're also right on int/short. fc's modes look for IEEE-754 bit patterns, so doubles that started life as rescaled ints lose the structure those modes exploit. A native int16/int32 path is on the list.

For the wiring itself: I have a sister single-header library, vibe (https://github.com/xtellect/vibe), built for this exact pattern: length-prefixed TCP/IPC framing on Linux, with a `telemetry_sink` example close to the edge-sensor --> cloud-ingest case. Producer compresses with fc, ships framed bytes through vibe, consumer decompresses. Doesn't solve the throughput ceiling, but handles the producer/consumer setup cleanly.

edit: i think the comments is flagged automatically because I used `vibe` (bad name I know) :)

I have an XOR128-style mode and a byte-transpose/byte-split-like mode, but I should not claim that as a proper Chimp128 or Arrow Parquet byte-stream-split comparison yet. I willadd direct baselines for Chimp128 and Arrow/Parquet BSS+zstd to the harness.

I’m regarding that term loosely here- in this case it is 'try several representations/codecs for a block and store the winner.' Similar ideas show up in columnar formats choosing encodings per column/page, OpenZL selectors (asother commenters pointed here), and shuffle/transpose + backend-compressor pipelines. fc’s version is much narrower: a tournament among f64-specific modes per block.

That’s a fair description. One mode does not dominate in my current harness; the winning mode varies quite a bit by dataset/block. If real workloads show one or two modes dominate, I’d rather simplify the portfolio :) For now the extra encode CPU is intentional: spend time once, get smaller blocks and fast parallel decode.

Thanks, this looks super relevant. I think the transferable part is the per-block selectrover predictors, strides, deltas, exponent/mantissa-ish structure, byte transpose, fallback raw/LZ, etc.sddl2 looks like a natural place to try some of that.

It is intended t obe mainly source agnostic (will try to add custom source predictors too). The idea is to treat input as an ordered stream of doubles and look for numeric structure like repeats, smooth deltas, fixed increments, or low-entropy bits. Target presentlyis scientific/time-series/simulation/analytics data, not photos or sound.

I built "fc", a C library for compressing streams of 64-bit floating-point values without quantization.

It is not trying to replace zstd or lz4. The idea is narrower: take blocks of doubles, try a set of float-specific predictors/transforms/coders, and emit whichever representation is smallest for that block.

It is aimed at time-series, scientific, simulation, and analytics data where the numbers often have structure: smooth curves, repeated values, fixed increments, periodic signals, predictable deltas, or low-entropy mantissas.

The API is intentionally small: "fc_enc", "fc_dec", a config struct, and a few counters to inspect which modes won. Decode is parallel and meant to be fast; encode spends more CPU searching for a better representation.

Current caveats: x86-64 only for now, tuned for IEEE-754 doubles, research-grade rather than production-hardened.

Repo: https://github.com/xtellect/fc

Yes: contention and locality.

In Cactus the fast path is local. A worker pushes its own continuation onto its own deque, runs the child, and later tries to reclaim that continuation locally. Other workers only touch that deque when they become idle and steal.

With one global deque, every fork/pop/steal hits the same shared structure, making it a cache-coherency hotspot.

Per-worker deques make the common case mostly uncontended; stealing is only the load-balancing fallback.

So a global deque is simpler, but it scales worse.

Do your own writing 4 months ago

I feel like LLMs are just forcing me to realize what writing actually is. For me, writing is basically a mental cache clear. I write things down so I can process them fully and then safely forget them.

If I let an LLM generate the text, that cognitive resolution never happens. I can't offload a thought i haven't actually formed - hence am troubld to safely forget about it.

Using AI for that is like hiring someone to lift weights for you and expecting to get stronger (I remember Slavoj Žižek equating it to a mechanical lovemaking in his recent talk somewhere).

The real trap isn't that we/writers willbe replaced; it's that we'll read the eloquent output of a model and quietly trick ourselves into believing we possess the deep comprehension it just spit out.

It reminds me of the shift from painting to photography. We thought the point of painting was to perfectly replicate reality, right up until the camera automated it. That stripped away the illusion and revealed what the art was actually for.

If the goal is just to pump out boilerplate, sure, let AIdo it. But if the goal is to figure out what I actually think, I still have to do the tedious, frustrating work of writing it out myself .

No AI was used. I see no problems with using AI to write code whatsoever, but this isn't that. The formatting is my screw-up. I ran clang-format with a bad config, then tried to hand-fix the result and made it worse. The parenthesization is from defensive macro expansion that I inlined for the build and never cleaned up . The inline (smoke) test in the Makefile was a lazy hack from my local workflow that I forgot to replace before pushing and a proper test suite exists but the names/sections are in Telugu, my native language . I'll fix both and add.

Fair points on both - the 5ns is the L2 hit case. I should have stated the range (30-60ns?) instead of the best case. And yes, fixing the tcmalloc case is on my list - thanks for pointing that out. And also to be clear, the goal was never to beat jemalloc or tcmalloc on raw throughput. I wanted t oshow that one doesn't have t ogive up competitive performnce to get explicit heaps, hard caps and teardown semantics.

I am aware of dlmallc/mspaces and GNU Obstacks. Both were in a way, original inspirations for spaces. Though I hadn't looked at mspaces source in years, I remember its inline boundary tags enabling zero overhad per allocation and there were no alignment constraints on the allocator itself (and is hardened across countless archs, not just x64 :) Spaces uses 64kb aligned slabs and a metadata find is a bitop. so potentially, a buffer overflow can corrupt the heap metadata in mspaces while spaces eats a cache-line on free.

mspaces was one mutex per heap for entire task (no tlc or lockfree paths). Spaces has per thread-heaps, local caches (no atomic ops on same thrad alloc/free), and a lock-free Treiber stack (ABA tagging) for cross-thread frees. mspaces doesnt track large allocs (>= 256 or 512kb) that hit mmap, so unless one knows to explicitly call mspace_track_large_chunks(...), destroy_mspace silently leaks them all (I think obstacks is good this way but is not a general fit imo). In Spaces, a chunk_destroy walks and frees all the page types unconditionally.

Another small thing may matter is error callbacks: Spaces triggers a cb allowing the application to shed load/degrade gracefully. Effectively, the heap walking (inspection?) in msapces is a compile-time switch that holds the lock whole time and doesnt track mmap (direct) allocs, and shares the thresholds like mmap_threashold, etc. globally, whereas Spaces lets you tune everything per-heap. So I'd say Spaces is a better candidate for use cases mspaces bolts on: concurrent access, hard budgets, complete heap walking and per-heap tuning.

Thanks for taking a look, really appreciate the thoughtful feedback! You're absolutely right about Fibonacci. It's a terrible performance example since the work per fork is basically zero :) I included it as an 8-line API showcase, and because it's almost pure overhead it doubles as a brutal stress test for the runtime. The nqueens, matmul, and quicksort benchmarks in the repo are the real performance indicators, imo. And yeah, batching and granularity control matter a lot, there's a section in the README on tuning the serial cutoff.

On the utilization and context switch concerns, i feel they typically stem from centralized queues or allocator contention in child-stealing systems (like TBB). Cactus uses continuation-stealing instead: on FORK, the child runs immediately as a normal function call, and the parent's continuation (just 24 bytes: RBP/RSP/return address) goes onto a per-worker deque.

If nobody steals it, the parent reclaims it at JOIN with an atomic decrement. No allocation, no stack switch, no context switch on the fast path. A stack switch only happens when a thief actually steals work and grabs a recycled slab from the pool.

The other scalability killer is usually lock-based synchronization at join points. I tried to avoid this by using atomic counters for the worker/thief handoff instead of mutexes. You can test scaling yourself: CACTUS_NPROCS=1 build/cc/nqueens 14 vs CACTUS_NPROCS=N build/cc/nqueens 14.

That said, I totally agree an Executor model is the right tool for flat workloads. I built this specifically for recursive divide-and-conquer (game trees, mergesort, mesh refinement) where you'd otherwise have to manually flatten the recursion or risk deadlocking a fixed-size pool.

I wrote this because I wanted more explicit control over heaps when building different subsystems in C. Standard options like jemalloc and mimalloc are incredibly fast, but they act as black boxes. You can't easily cap a parser's memory at 256MB or wipe it all out in one go without writing a custom pool allocator.

Spaces takes a different approach. It uses 64KB-aligned slabs, and the metadata lookup is just a pointer mask (ptr & ~0xFFFF).

The trade-off is that every free() incurs an L1 cache miss to read the slab header, and there is a 64KB virtual memory floor per slab. But in exchange, you get zero-external-metadata regions, instant teardown of massive structures like ASTs, and performance that surprisingly keeps up with jemalloc on cross-thread workloads (I included the mimalloc-bench scripts in the repo).

It's Linux x86-64 only right now. I'm curious if systems folks think this chunk API is a pragmatic middle ground for memory management, or if the cache-miss penalty on free() makes the pointer-masking approach a dead end for general use.