# libfiber vs libco vs libmill vs Go: Performance Benchmark Comparison

> Discover the libfiber performance benchmark against libco, libmill, and Go. See how libfiber achieves superior transactions per second while maintaining POSIX compatibility. Read the comparison now.

- Repository: [iQIYI/libfiber](https://github.com/iqiyi/libfiber)
- Tags: performance
- Published: 2026-03-04

---

**According to the echo-IO benchmark in the iqiyi/libfiber repository, libfiber achieves up to 231,722 transactions per second with 100 concurrent fibers, significantly outperforming libco (147,310 TPS), libmill (201,171 TPS), and the Go runtime (154,504 TPS) while maintaining full POSIX compatibility.**

The iqiyi/libfiber repository provides a production-grade native coroutine library for C/C++ designed for high-concurrency network programming. This libfiber vs libco vs libmill vs golang performance benchmark analysis examines the standardized echo-IO test results stored in [`doc/benchmark.txt`](https://github.com/iqiyi/libfiber/blob/main/doc/benchmark.txt) to reveal how stackful coroutines compare against Go's preemptive goroutine scheduler in raw throughput metrics.

## Echo-IO Benchmark Results

The benchmark executes 10 million echo operations between client-server pairs, measuring **transactions per second (TPS)** across varying concurrency levels. The raw data is located in [`doc/benchmark.txt`](https://github.com/iqiyi/libfiber/blob/main/doc/benchmark.txt) at lines 27-68.

| Library | 100 Fibers (TPS) | 200 Fibers (TPS) | 500 Fibers (TPS) | 1,000 Fibers (TPS) |
|---------|------------------|------------------|------------------|--------------------|
| **libfiber** | 231,722 | 214,434 | 196,511 | 169,750 |
| **libco** | 147,310 | 135,781 | 128,509 | 106,070 |
| **libmill** | 201,171 | 154,527 | 148,741 | 132,678 |
| **Go runtime** | 154,504 | 160,226 | 140,219 | 123,268 |

At 1,000 concurrent fibers, libfiber sustains approximately **169,750 TPS**, compared to libco's 106,070 TPS and libmill's 132,678 TPS. The gap widens at lower concurrency levels, where libfiber's optimized event loop and shared-stack architecture deliver superior cache locality.

## Architectural Comparison

The performance gaps stem from fundamental design choices in **stack management**, **scheduling models**, and **I/O integration**.

### libfiber: Native Coroutines with Shared-Stack Mode

libfiber implements lightweight stackful coroutines in C/C++ with an optional **shared-stack** mode that reduces memory pressure. As implemented in [`cpp/src/fiber.cpp`](https://github.com/iqiyi/libfiber/blob/main/cpp/src/fiber.cpp), the library uses a single-threaded cooperative scheduler that can run independent schedulers per thread. It hooks standard POSIX I/O functions—including `read`, `write`, `connect`, and `sleep`—via the detours mechanism defined in [`cpp/src/detours/detours.h`](https://github.com/iqiyi/libfiber/blob/main/cpp/src/detours/detours.h), allowing legacy blocking code to execute as non-blocking coroutines without modification.

### libco: Pure C Stackful Coroutines

Tencent's libco provides stackful coroutines where each fiber owns a private stack. It uses a cooperative scheduler with **per-thread queues** and requires explicit `co_resume` calls. Unlike libfiber, libco does **not** provide built-in system-call hooking; developers must manually integrate external event loops (epoll/kqueue) and replace blocking calls manually.

### libmill: Sustrik's Coroutine Primitives

libmill offers stackful coroutines with a small C runtime that supports multi-threaded mode via `ml_thread`. Instead of hooking system calls, it provides distinct non-blocking I/O primitives (`ml_socket`, `ml_accept`, etc.) that internally use epoll/kqueue. This requires code changes to use the library-specific API surface rather than standard POSIX sockets.

### Go Runtime: Preemptive M-P-G Scheduler

The Go runtime implements a **preemptive** scheduler using the M-P-G model (OS threads M, processors P, goroutines G). Goroutine stacks grow dynamically, and the scheduler can migrate goroutines across threads for true multi-core parallelism. Go's built-in netpoller abstracts epoll/kqueue/IOCP, providing async semantics without external hooking.

## Why libfiber Leads in Throughput

Three technical advantages explain libfiber's performance lead in the [`doc/benchmark.txt`](https://github.com/iqiyi/libfiber/blob/main/doc/benchmark.txt) metrics:

- **Automatic System-Call Hooking**: By intercepting blocking calls in-place through the detours mechanism, libfiber eliminates wrapper API overhead. The coroutine yields precisely when the OS would block, reducing context-switch latency compared to libmill's explicit API or libco's manual integration.
- **Shared-Stack Memory Efficiency**: The shared-stack mode minimizes memory consumption, allowing more fibers to remain cache-resident. This improves context-switch speed relative to libco's fixed private stacks and Go's dynamically growing stacks.
- **Optimized Event Loop**: The library selects the most efficient event mechanism per platform—including `io_uring` on Linux, epoll, kqueue, and IOCP—reducing latency compared to the generic loops used by libco and libmill.

## Implementation Examples

The repository's `samples/` and `benchmark/` directories demonstrate equivalent echo-server implementations across all four frameworks.

### libfiber Echo Server (C)

```c
#include "fiber/lib_fiber.h"
#include "patch.h"

static void echo_fiber(ACL_FIBER *fb, void *ctx) {
    SOCKET client = *(SOCKET *)ctx;
    char buf[4096];
    int n;

    while ((n = acl_fiber_recv(client, buf, sizeof(buf), 0)) > 0) {
        acl_fiber_send(client, buf, n, 0);
    }
    socket_close(client);
    free(ctx);
}

int main(void) {
    const char *ip = "127.0.0.1";
    int port = 9000;
    SOCKET lfd = socket_listen(ip, port);
    socket_init();
    
    acl_fiber_create([](ACL_FIBER*,void*){ /* accept loop */ }, NULL, 128000);
    acl_fiber_schedule_with(FIBER_EVENT_KERNEL);
    
    socket_end();
}

```

This example from [`samples/cpp/server/main.cpp`](https://github.com/iqiyi/libfiber/blob/main/samples/cpp/server/main.cpp) leverages `acl_fiber_schedule_with` to enter the event loop and uses hooked socket operations.

### libco Echo Server (C)

```c
#include "co.h"

static void *co_echo(void *arg) {
    int client = *(int *)arg;
    char buf[1024];
    int n;
    while ((n = recv(client, buf, sizeof(buf), 0)) > 0) {
        send(client, buf, n, 0);
    }
    close(client);
    free(arg);
    return NULL;
}

int main() {
    /* listen socket setup omitted */
    while (1) {
        int *c = malloc(sizeof(int));
        *c = accept(lfd, NULL, NULL);
        co_create(128*1024, co_echo, c);
    }
    co_eventloop(co_get_epoll_handle(), -1);
}

```

Adapted from the `benchmark/libco` directory, this requires manual event loop integration via `co_eventloop`.

### libmill Echo Server (C)

```c
#include <ml.h>

static void echo(void *arg) {
    ml_handle h = *(ml_handle *)arg;
    char buf[1024];
    int n;
    while ((n = ml_read(h, buf, sizeof(buf), 0)) > 0)
        ml_write(h, buf, n, 0);
    ml_close(h);
    free(arg);
}

int main() {
    ml_handle l = ml_socket();
    ml_bind(l, "0.0.0.0", "9000");
    ml_listen(l, 128);
    while (1) {
        ml_handle *c = malloc(sizeof(ml_handle));
        *c = ml_accept(l, NULL);
        ml_go(echo, c);
    }
}

```

This `benchmark/libmill` example uses library-specific primitives (`ml_socket`, `ml_go`) rather than POSIX sockets.

### Go Echo Server

```go
package main

import (
    "io"
    "log"
    "net"
)

func handle(conn net.Conn) {
    defer conn.Close()
    io.Copy(conn, conn)
}

func main() {
    ln, err := net.Listen("tcp", ":9000")
    if err != nil { log.Fatal(err) }
    for {
        c, err := ln.Accept()
        if err != nil { continue }
        go handle(c)
    }
}

```

The Go implementation uses the standard library's netpoller, which provides async I/O without explicit hooking.

## Summary

- **libfiber** achieves the highest throughput (231,722 TPS at 100 fibers) by combining shared-stack coroutines with automatic POSIX hooking and platform-optimized event loops.
- **libco** trades performance (147,310 TPS) for simplicity, requiring manual event loop integration and lacking system-call interception.
- **libmill** delivers moderate performance (201,171 TPS) but forces migration to a non-standard API surface.
- **Go** provides the best multi-core parallelism through preemptive scheduling but achieves lower single-threaded throughput (154,504 TPS) compared to libfiber's cooperative model.
- The benchmark data in [`doc/benchmark.txt`](https://github.com/iqiyi/libfiber/blob/main/doc/benchmark.txt) confirms that libfiber's hooking mechanism and memory efficiency provide measurable advantages for high-concurrency C/C++ applications.

## Frequently Asked Questions

### What benchmark test was used to compare these coroutine libraries?

The comparison used an echo-IO benchmark executing 10 million echo operations between client-server pairs, measuring transactions per second (TPS) across 100, 200, 500, and 1,000 concurrent fibers. The test configuration and raw results are documented in [`doc/benchmark.txt`](https://github.com/iqiyi/libfiber/blob/main/doc/benchmark.txt) at lines 27-68.

### Why does libfiber outperform Go's goroutines in this benchmark?

libfiber uses a cooperative scheduler with automatic system-call hooking that eliminates context-switch overhead, while Go's preemptive M-P-G scheduler incurs synchronization costs for cross-thread goroutine migration. Additionally, libfiber's shared-stack mode reduces memory pressure compared to Go's dynamically growing stacks, improving cache locality during the echo-IO test.

### Can existing C/C++ code use libfiber without modification?

Yes. According to the [`README.md`](https://github.com/iqiyi/libfiber/blob/main/README.md) and implementation in `cpp/src/detours/`, libfiber hooks standard POSIX APIs including `read`, `write`, `connect`, `sleep`, and DNS functions. This allows existing blocking code to run as non-blocking coroutines without changing the application logic or switching to library-specific I/O primitives like those required by libmill.

### How does libfiber's shared-stack mode improve performance?

The shared-stack mode, implemented in [`cpp/src/fiber.cpp`](https://github.com/iqiyi/libfiber/blob/main/cpp/src/fiber.cpp), allows multiple fibers to share a single stack region rather than allocating fixed private stacks like libco. This reduces memory consumption significantly, allowing more fibers to fit in CPU cache and decreasing context-switch latency, which directly contributes to the higher TPS observed in the 1,000-fiber benchmark (169,750 TPS vs. libco's 106,070 TPS).