Slow HTTP Server Performance
Nobody has claimed this yet.
- Dominant language
- Lean
- Stars
- 9.2k
- Forks
- 990
- Avg merge
- 1d 17h
- Merged PRs (30d)
- 175
Description
I was benchmarking Std.Http and noticed that performance under concurrency degrades significantly. Profiling shows that a large portion of the cost comes from synchronization (mutexes and event loop coordination) rather than actual work.
Baseline Benchmarks
| Server | Concurrency | Req/s | Avg Latency |
|---|---|---|---|
| Node | 1 | 33836 | 0.01 ms |
| Node | 30 | 127256 | 0.01 ms |
| Node | 100 | 125440 | 0.11 ms |
| Lean4 | 1 | 5325 | 0.01 ms |
| Lean4 | 30 | 11825 | 2.03 ms |
| Lean4 | 100 | 12284 | 7.65 ms |
Profiling (hello world, 100 concurrent connections)
On the worker threads:
- ~12% on
task_managermutex - ~21% on polling + dispatch (
Selectable.one, channel ops, promise resolution that locks the task manager mutex) - ~15% on RC with atomics overhead (
mark_mt+lean_dec_ref_cold+mi_free)
For a simple request, we hit at least ~6 different mutexes per request cycle.
So overall, a significant portion of time is spent on synchronization/infrastructure.
Experiments
I tried more aggressive changes:
- thread-local queues with work stealing for tasks
- multiple
uv_loops - various smaller optimizations
Results:
| Server | Concurrency | Req/s | Avg Latency |
|---|---|---|---|
| Lean4 Experiments | 1 | 8884 | 0.01 ms |
| Lean4 Experiments | 30 | 23234 | 0.49 ms |
| Lean4 Experiments | 100 | 23186 | 1 ms |
This leads to significant improvements, especially under higher concurrency.
What seems to be the problem
The main issues appear to be:
- too much assumed cross-thread sharing
- atomic RC everywhere
- many mutexes in the async path (event loop,
task_manager, channels, etc.)
For something as simple as a hello world handler, most of the time is not spent doing actual work.
Idea / Proposal
It would likely help if async-related data could stay thread-local in the common case (even if it can still move between threads when needed by work stealing or something like this in a new task_manager design).
That would allow:
- avoiding
mark_mt/ atomic RC in the common case - reducing reliance on mutexes
- only paying synchronization costs when data is actually shared
This likely requires changes in both task_manager and event_loop_t.
- This is based on some small experiments, it's not a full design.
Contributor guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Research direction
Start by locating the task_manager and event_loop_t implementations and reviewing the async HTTP path they coordinate. Reproduce the reported concurrency benchmarks and inspect the profiling results before evaluating the proposed thread-local and work-stealing direction. Done should include a measured improvement under concurrent load without breaking existing HTTP behavior.
Written by the indexing model from the issue text.
Assessment
- Domain
- backend-api-design, networking, performance
- Issue type
- Refactor
- Difficulty
- 5/5
- Estimated time
- Over a week
- Activity status
- Quiet
- Clarity
- Needs clarification
- Newbie friendliness
- 35/100