leanprover / leanprover/lean4

Slow HTTP Server Performance

Open
#13,469 4 comments 1 reaction 0 assignees View on GitHub

Nobody has claimed this yet.

enhancement nice to have P-medium
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_manager mutex
  • ~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

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. 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

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.