FastIPC FastIPC v1.0.0
v1.0.0MITx86-64-v3 · ARM64 Linux · Apple Silicon Linux · Windows · macOSZig · C · C++ · Python · C# · Java · Rust · Lua · JavaScript · Go

fastipc

Shared-memory IPC between processes, in any language.

A small Zig library for sending messages between processes on the same machine. Each connection is a pair of lock-free rings that both processes map, plus a local socket that tells each side when the other one has gone. You can use it from C, C++, Zig, Python, C#, Java, Rust, Lua, JavaScript or Go.

Pythonpip install fipc-python
C#dotnet add package Fipc
Javaio.github.fastipc:fipc:1.0.0
Rustcargo add fipc
Lualuarocks install fipc
JSnpm install fipc-js
Gogo get github.com/fastipc/fastipc/bindings/go/fipc@latest
HEAD TO HEADagainst the fastest of the others
Intel Core i9 laptopMacBook Air M1
CaseLinux x86‑64WindowsmacOSLinux ARM64
Round trip, 16 BLatency (ns, median)1.18×Aeron1.19×Aeron1.00×Aeron1.00×Aeron
Stream, 16 BThroughput (messages/s)0.89×*Aeron1.11×Aeron1.42×Aeron1.43×Aeron
Stream, 1 KiBThroughput (messages/s)1.54×Aeron1.32×Aeron1.14×Aeron1.11×Aeron
Broadcast to 4Throughput (messages/s)0.98×*Aeron1.18×Aeron1.21×Aeron1.71×Aeron
Work queue 4 × 4Throughput (messages/s)1.89×ZeroMQ2.04×ZeroMQ5.71×ZeroMQ—
Service callsThroughput (calls/s)3.60×iceoryx23.30×iceoryx23.18×iceoryx23.10×iceoryx2

Intel Core i9-13900H laptop, 6 P-cores (Hyper‑Threading) and 8 E-cores, 32 GB, running Windows 11 and Linux in WSL2 (20 vCPUs).
Apple M1 MacBook Air, 4 P-cores and 4 E-cores, 8 GB, running macOS and Linux in a VM (4 vCPUs, 4 GB).

Each cell is FastIPC's rate divided by the fastest other library's rate on the same machine, with that library named under the number. For the round trip it is the other library's time divided by FastIPC's. Above 1×, FastIPC is faster. The libraries compared are iceoryx2, Aeron, ZeroMQ, NNG, Boost.Interprocess, cpp-ipc and the OS's sockets, each in its best configuration. A dash means the case needs more processes than the VM's 4 vCPUs can run. Charts, every case and the method.
* In every benchmark, Aeron's receiver polls in a loop and never sleeps, which keeps a core 100% busy; on Windows its media driver spins too (Aeron in detail).

Small on purpose

from the architecture

The core is small, and it covers what two local processes usually need from each other: messages of any size, zero-copy, broadcasts, work queues, services with replies, and a prompt report when the other process exits or crashes.

8,324 lines of Zig

The whole library, small enough to read

Everything behind fipc.h, for all three operating systems, is 8,324 lines of Zig. The count includes only code that ends up in the library. Blank lines, comments, tests, test hooks and compile-time checks are left out.

fipc_stream_connect · fipc_stream_accept

Either order, any restart

Start the server or the client first. fipc_stream_connect waits, within its timeout, for a server to listen and accept. Restart either side at any time. The other side receives every message the old process completed, then gets FIPC_DISCONNECTED, and its next fipc_stream_accept or fipc_stream_connect pairs it with the new one.

fipc_result_t

Results you can act on

Every call returns one of eleven result codes. Each code tells you the state of the connection and what to do next. If a send times out because the ring is full, or a receive times out because no message arrived, nothing was sent or taken, and you can call it again. A message too large for your receive buffer stays queued, so you can read it again with a bigger one. A disconnect means the peer is gone, and a server usually handles it by waiting to accept a new connection. All eleven codes.

one copy · one swap

Cheap on the hot path

Sending a message is one copy into shared memory, then a store and an atomic swap to publish it. No locks are taken. The operating system is involved only when the receiver has stopped spinning and gone to sleep, and then the wake-up is a single system call. A background thread watches for the other process to exit, so that check costs the hot path nothing. A 16-byte round trip between two processes takes 125 to 163 ns on the machines we measured, and no library we compared was faster.

Any language on the other end

one protocol · 100 pairs, tested

A process in any of the ten languages can talk to a process in any other, because every binding wraps the same native library and speaks the same protocol. The two sides only have to agree on the connection name and the message format.

All the servers and clients in the quick start do the same thing. The client sends the message ping and the server answers PING. python devtool.py test interop runs every server against every client, each in its own process, on Linux, Windows and macOS.

a Rust server, a Python client
# terminal 1, in bindings/rust
cargo run --example server

# terminal 2, next to client.py
python client.py
# b'PING'

The two programs are the quick start's server.rs and client.py, unchanged.

server ↓
client →
ZigCC++PythonC#JavaRustLuaJavaScriptGo
Zig✓✓✓✓✓✓✓✓✓✓
C✓✓✓✓✓✓✓✓✓✓
C++✓✓✓✓✓✓✓✓✓✓
Python✓✓✓✓✓✓✓✓✓✓
C#✓✓✓✓✓✓✓✓✓✓
Java✓✓✓✓✓✓✓✓✓✓
Rust✓✓✓✓✓✓✓✓✓✓
Lua✓✓✓✓✓✓✓✓✓✓
JavaScript✓✓✓✓✓✓✓✓✓✓
Go✓✓✓✓✓✓✓✓✓✓
Servers in rows, clients in columns. Every pair, including a language with itself, is tested with the server started first. One pair per server is also tested with the client started first.

What it gives you

from include/fipc.h
lock-free rings

Rings in shared memory

Messages go through lock-free ring buffers that both processes map. A connection has two rings, one per direction, each with one writer and one reader (SPSC). The write and read positions sit on separate cache lines, so the writer and the reader don't false-share.

futex · event

Spin, then sleep

A waiting side spins briefly, then sleeps on a futex (Linux), an os_sync address wait (macOS) or a named event (Windows). A publish costs one store and one locked swap. The sleep protocol is built so that a wake-up is never lost.

acquire · commit · release

Zero-copy, or any size

You can write a message directly into the ring and read it in place, 16-byte aligned. The copying calls take messages of any size. A message larger than the ring is sent in pieces and arrives whole. Nothing is allocated per message.

FIPC_DISCONNECTED

Knows when the peer is gone

A local control connection per connection (an abstract AF_UNIX socket, a named pipe, a socket file on macOS) reports the peer's end as soon as its process exits or closes the connection. There is no heartbeat or timeout to tune, and a crash leaves nothing to clean up.

one machine

Local only, on purpose

Zero-copy messages and a peer's end reported without a heartbeat both depend on shared memory and on one kernel that sees both processes. Neither holds across a network, so FastIPC has no network transport. A component that moves to another machine can switch to a network library such as ZeroMQ, whose PAIR, PUB/SUB, PUSH/PULL and REQ/REP sockets correspond to the stream, the broadcast, the queue and the service.

fipc_broadcast_*

Broadcasts (pub/sub)

One publisher and up to 1,024 subscribers, in any processes. Each message is stored once, in memory that every subscriber maps. A lossy subscriber never holds the publisher back. A gated one misses nothing and can read messages in place.

fipc_queue_*

Work queues

Up to 63 producers and 64 consumers, in any processes. Each message goes to one consumer. If that consumer's process ends before acknowledging it, the message goes to another.

fipc_service_*

Services (request and reply)

Up to 64 clients and one server or a pool of up to 64, in any processes. Each request goes to one server, and the replies come back to each client in the order it sent its requests. A request reserves room for its reply, so a server never waits for a client. If a server ends before replying, the request is answered with FIPC_ABANDONED.

protocol v1 · channel v1

A specified protocol

Everything on the wire is written down, so two builds interoperate bit for bit. A bug in one process can't crash the other: each side bounds-checks what it reads from shared memory, which costs a few integer comparisons per message, and a corrupt ring returns FIPC_INVALID.

30 functions

One header

One header of 30 functions in front of a library you can read end to end (how small). The tests are about twice its size.

tests · TSan · chaos · fuzz

Tested under failure

Unit tests and a C-API conformance suite, run in one process and across processes, including peers killed at every step of the handshake. Both also run under ThreadSanitizer. Chaos, soak and fuzz harnesses kill and pause peers, churn connections and feed in corrupt data.

How it works

from docs/protocol.md

A server listens on a name and accepts one client at a time; a client connects to the name. Only processes of the same user can connect.

SERVER PROCESS your threads fipc_send · fipc_send_acquire fipc_recv · fipc_recv_acquire one thread sends and one receives, at the same time fipc_stream_accept, then a watcher accepts, creates the segment, sends SEGMENT, waits for READY; the watcher reads until EOF CLIENT PROCESS your threads fipc_stream_connect, then the same calls as the server: either side may send and receive fipc_stream_connect, then a watcher connects, checks and maps the segment, sends READY; the watcher reads until EOF SHARED SEGMENT one per connection, mapped by both header, 640 B: indices, sleep flags s2c ring · server → client frames c2s ring · client → server ring size: a power of two, 1 KiB to 2 GiB write read write read control connection Linux: abstract AF_UNIX socket · Windows: named pipe · macOS: socket file 1 · SEGMENT (64 B): capacity, session id 2 · READY (64 B) 3 · then silent; its end means the peer is gone
Linux passes the segment as a sealed memfd with the SEGMENT frame and wakes sleepers with a futex on the ring's sleep flag. Windows uses a pagefile-backed section and four auto-reset named events per connection. macOS passes an unnamed POSIX shared memory object with SEGMENT and wakes sleepers with os_sync_wait_on_address. Linux and Windows leave nothing behind after a crash; on macOS a crashed owner's socket file and lock file stay until that name's next create removes them, and never get in the way.
shared memory, the data pathcontrol: setup and the peer's end
  1. Set up

    The server's fipc_stream_accept accepts the client, creates a zeroed segment and offers it in a SEGMENT frame. The client's fipc_stream_connect checks the frame and the segment's header, maps it and answers READY. Both run on the calling threads, so a client and its server run on different threads or processes. fipc_stream_connect can return before the server's fipc_stream_accept does; messages sent meanwhile wait in the ring.

  2. Talk

    Messages go through the rings and never touch the control connection. A frame is a 16-byte header and a piece, and never wraps, so every piece is contiguous and the zero-copy calls can hand out pointers into the ring.

  3. End

    When a process closes the connection or exits for any reason, the kernel closes its end of the control connection. The other side reads EOF. Its sends then return FIPC_DISCONNECTED at once, and its receives do so after delivering every message the peer completed.

Performance

from bench/alternatives

FastIPC, iceoryx2, Aeron, ZeroMQ, NNG, Boost.Interprocess, cpp-ipc and the operating system's sockets, run through the same cases on the same machines, each library in its fastest configuration. One sender and one receiver process for a stream and a round trip, four subscribers for a broadcast, one client and one server for a service.

Stream: 16-byte messages per second, one way

FastIPC
50.3M
52.5M
82.1M
72.4M
Aeron
56.5M*
47.3M
57.6M
50.6M
ZeroMQ
7.84M
8.44M
7.38M
9.73M
Boost.Interprocess
7.22M
3.57M
10.2M
5.82M
iceoryx2
3.63M
3.23M
6.27M
5.59M
cpp-ipc
2.34M
1.01M
—
2.59M
Unix socket
2.17M
986.9K
2.00M
2.33M
TCP loopback
558.8K
115.2K
548.6K
572.3K
NNG
19.4K
52.5K
92.8K
135.4K

Round trip, 16 B, p50 (shorter is better; log scale)

FastIPC
163 ns
160 ns
125 ns
125 ns
Aeron
192 ns
191 ns
125 ns
125 ns
iceoryx2
854 ns
897 ns
583 ns
625 ns
Boost.Interprocess
1.6 µs
680 ns
375 ns
1.5 µs
cpp-ipc
1.2 µs
2.3 µs
—
1.0 µs
Unix socket
1.7 µs
3.2 µs
1.5 µs
1.6 µs
TCP loopback
3.7 µs
14 µs
9.0 µs
2.9 µs
ZeroMQ
78 µs
53 µs
43 µs
30 µs
NNG
118 µs
47 µs
24 µs
135 µs

Broadcast to 4 subscribers, lossless: 16-byte messages per second

FastIPC
44.4M
38.3M
50.7M
40.8M
Aeron
45.6M*
32.5M
41.9M
23.8M
iceoryx2
1.36M
1.29M
972.2K
1.51M
cpp-ipc
13.3K
45.1K
—
12.0K

Service, 1 client × 1 server: 16-byte calls per second

FastIPC
3.36M
3.01M
4.27M
3.85M
iceoryx2
933.1K
912.5K
1.34M
1.24M
ZeroMQ
12.3K
18.2K
23.0K
24.9K
NNG
8.32K
19.6K
40.4K
7.20K

Intel Core i9-13900H laptop, 6 P-cores (Hyper‑Threading) and 8 E-cores, 32 GB, running Windows 11 and Linux in WSL2 (20 vCPUs).
Apple M1 MacBook Air, 4 P-cores and 4 E-cores, 8 GB, running macOS and Linux in a VM (4 vCPUs, 4 GB). Machines and method

Medians of 3 runs, no CPU pinning; hover a bar for the configuration. Every case, work queues, the libraries' features, waking a sleeping receiver, and what each language's binding costs.

Quick start

one exchange, every language

Each language has a server file and a client file. Run the server in one process and the client in another. The client sends ping and the server answers in upper case. Every pair makes the same exchange, so a client in any language works with a server in any other (every pair is tested).

Installzig fetch --save git+https://github.com/fastipc/fastipc
server.zig
const std = @import("std");
const fastipc = @import("fastipc");

pub fn main(init: std.process.Init) !void {
    // rings of 1 MiB each way
    const listener = try fastipc.streamCreate(
        init.gpa,
        "my_channel",
        1 << 20,
    );
    defer listener.close();
    // waits for a client
    const conn = try listener.accept(.none);
    defer conn.close();

    var buf: [256]u8 = undefined;
    // .none: no timeout
    const len = try conn.recv(&buf, .none);
    const reply = std.ascii.upperString(&buf, buf[0..len]);
    try conn.send(reply, .none);
}
client.zig
const std = @import("std");
const fastipc = @import("fastipc");

pub fn main(init: std.process.Init) !void {
    const five_s: std.Io.Timeout = .{ .duration = .{
        .raw = .fromSeconds(5),
        .clock = .awake,
    } };
    const conn = try fastipc.streamConnect(
        init.gpa,
        "my_channel",
        five_s,
    );
    defer conn.close();
    try conn.send("ping", .none);
    var buf: [256]u8 = undefined;
    const len = try conn.recv(&buf, .none);
    std.debug.print("{s}\n", .{buf[0..len]}); // PING
}

Zig 0.17.0. Your build.zig imports the dependency's module: exe.root_module.addImport("fastipc", dep.module("fastipc")) (an example project). Errors are Zig error sets and timeouts std.Io.Timeout (.none waits as long as it takes). More on the Zig module.

One library, ten languages

30 fipc_* functions, one ABI

Zig

module fastipc

The library's own language. Listener, Queue, Service and Conn over your std.Io, with Zig error sets and std.Io.Timeout, compiled into your program. Streams, broadcasts, work queues and services, copied and zero-copy.

Install, examples →

C

fipc.h

The whole API in one header: 30 functions, eleven result codes. Built with the Zig build system or any C compiler.

C API, zero-copy →

C++

fipc.hpp

A header-only C++20 wrapper with RAII listeners and connections, fipc::result<T> without exceptions, std::chrono timeouts, zero-copy slots.

Build, examples →

Python

fipc

Listener and Conn over CFFI's ABI mode, so no compiler is needed. Results other than OK raise FipcError.

Install, examples →

C#

Fipc

FipcListener and FipcConnection are built on SafeHandles and can be disposed from any thread. Underneath is a raw P/Invoke layer, Fipc.

Install, examples →

Java

fipc

Listener and Connection over the FFM API, Java 22+, with no JNI. Safe to close from any thread; failures throw FipcException.

Install, examples →

Rust

fipc

Listener and Connection, safe and Send + Sync, over a raw FFI crate. The borrow checker keeps the threading and zero-copy rules; errors are a Result.

Install, examples →

Lua

fipc

One module over LuaJIT's FFI, with no compiler and no C module. Listeners and connections with copied and zero-copy messages; a failed call returns nil and the error's name.

Install, examples →

JavaScript

fipc

One Node-API addon for Node.js, Bun and Deno. Calls on Listener and Connection that may wait return Promises that never block the event loop, and each has a Sync form. Zero-copy views detach when they end. TypeScript types are included.

Install, examples →

Go

fipc

One package that loads the library through purego, so no cgo is needed. Listener and Conn are safe from any goroutine, like a net.Conn. Errors work with errors.Is, timeouts are time.Durations, and a context can cancel a call.

Install, examples →

Platform support

from docs/platform-support.md

FastIPC supports recent systems only. That lets the code use modern APIs and instructions directly, with no fallbacks or feature probes.

SupportedWhat it means
CPUx86-64 with x86-64-v3 (AVX2, BMI1/2, FMA, MOVBE) and PREFETCHW on Linux and Windows; ARM64 (ARMv8.0-A) on Linux; Apple Silicon on macOSIntel Haswell (2013) and later, AMD Zen (2017) and later; Raspberry Pi 3/4/5, AWS Graviton, Ampere, NVIDIA Jetson; Apple M1 and later
Linuxglibc 2.34 or newer, x86-64 and ARM64Ubuntu 22.04, Debian 12, RHEL 9 and later (kernels 5.14+)
WindowsWindows 11, Windows Server 2022 and later, x64Windows 10 left support in October 2025
macOSmacOS 14.4 or newer, on Apple Siliconthe first with os_sync_wait_on_address; the dylib is ad hoc signed, not notarized
BuildZig 0.17.0Only to build from source; the Python, NuGet, Maven, crates.io, LuaRocks and npm packages and the Go module carry the native library
Not supported32-bit systems, Windows on ARM, Intel Macs, musl-based Linux, x86-64 CPUs without AVX2