Earlier this year, we published a comprehensive CPU‑intensive cross‑language benchmark that put PHP 8.5 (with JIT on/off), Node.js 22, Go 1.24, and Python 3.12 head‑to‑head. The results were clear: Go dominated CPU‑bound tasks, Node.js held its own, and PHP with JIT landed in the middle. But a recurring question from readers was: “What about I/O? My app spends most of its time waiting for the database, not crunching numbers.”
That’s exactly what we benchmarked this time. We took the same four languages and added PHP 8.5 + Swoole with coroutines and connection pooling, then tested them under real database I/O latency – 50ms and 200ms SELECT SLEEP() delays. The results are dramatically different from the CPU benchmark, and they may change how you think about PHP in high‑I/O environments.
🧪 Test Environment
- Server: 4 vCPU, 8 GB RAM, Ubuntu 22.04
- Database: MySQL 8.4 (same host,
max_connections=151) - PHP: 8.5.7 with OPcache + JIT tracing, buffer 128M
- Swoole: 6.2.1, 20 workers, coroutine + PDO with connection pooling
- Node.js: 22.x, connection pool size 20
- Go: 1.24.x,
database/sqlwith connection pool size 20 - Python: 3.12.x,
aiomysqlwith connection pool size 20 - Load generator:
ab -c 100 -t 60(100 concurrent connections, 60 seconds), 3 runs per configuration
Business logic (identical across all languages):
- CPU: 1000 iterations of
sqrt($i)(same as the CPU benchmark) - I/O: One database query – either
SELECT 1(baseline) orSELECT SLEEP(0.05)/SELECT SLEEP(0.2)
Worker/connection configuration:
- PHP‑FPM: 20 static workers
- Swoole: 20 worker processes with coroutine support
- Node.js: 20 connection pool
- Go: 20 max open connections
- Python: 20 max connection pool
All services returned identical compute values (21065.833110879048), confirming the business logic executed consistently across all languages.
📊 Benchmark Results
50ms Database I/O Latency
| Language / Runtime | Avg RPS | P99 (ms) |
|---|---|---|
| PHP + Swoole | 1,912 | 62 |
| Node.js | 393 | 256 |
| Go | 394 | 914 |
| Python | 393 | 461 |
| PHP‑FPM | 388 | 264 |

Swoole achieves 4.9× higher RPS than the next best performer under 50ms I/O latency.
200ms Database I/O Latency
| Language / Runtime | Avg RPS | P99 (ms) |
|---|---|---|
| PHP + Swoole | 495 | 208 |
| Go | 108 | 2,001 |
| Node.js | 99 | 1,008 |
| Python | 99 | 2,600 |
| PHP‑FPM | 99 | 1,016 |

At 200ms latency, Swoole maintains 5× higher throughput than all other runtimes.
🔍 Analysis

Why Does Swoole Dominate I/O?
The answer lies in coroutines. Under traditional PHP‑FPM, each worker process blocks entirely while waiting for a database query to complete. With 20 workers and a 50ms query, the theoretical maximum is roughly 1000ms / 50ms × 20 = 400 RPS – and PHP‑FPM hits ~388 RPS, right at that limit.
Swoole, however, uses coroutines that yield during I/O waits, allowing a single worker to handle multiple concurrent requests. With 20 workers and coroutine concurrency, Swoole achieves ~1,912 RPS at 50ms latency – roughly 4.9× higher than the synchronous limit.
At 200ms latency, the gap widens even further. While other runtimes hover around the theoretical limit of ~100 RPS (5 requests/second × 20 workers), Swoole maintains ~495 RPS, demonstrating that coroutines effectively multiply the concurrency of each worker.
Why Is Go’s P99 Latency So High?
Go’s average RPS is competitive (~394 at 50ms), but its P99 latency spikes to 914ms – far higher than Node.js (256ms) and Python (461ms). This suggests that while most requests complete quickly, a small fraction experience significant delays.
Possible explanations:
- Connection pool contention: Go’s
database/sqlconnection pool may experience contention under sustained load. - Garbage collection pauses: Go’s GC can introduce occasional latency spikes.
- MySQL driver overhead: The
go-sql-driver/mysqldriver may behave differently under load.
Why Is PHP‑FPM Competitive at 50ms?
At 50ms latency, PHP‑FPM achieves ~388 RPS – nearly identical to Node.js, Go, and Python. This is because all synchronous runtimes are bounded by the same limit: the number of workers multiplied by the maximum throughput per worker. Under pure I/O wait, the language runtime matters less than the concurrency model.
The real separation happens when you introduce coroutines (Swoole) or truly non‑blocking I/O (Node.js’s event loop, Go’s goroutines). But even Node.js and Go, despite their async models, couldn’t match Swoole’s coroutine efficiency in this test.
📊 Visual Comparison
| Metric | PHP‑FPM | PHP+Swoole | Node.js | Go | Python |
|---|---|---|---|---|---|
| 50ms RPS | 388 | 1,912 | 393 | 394 | 393 |
| 50ms P99 | 264 | 62 | 256 | 914 | 461 |
| 200ms RPS | 99 | 495 | 99 | 108 | 99 |
| 200ms P99 | 1,016 | 208 | 1,008 | 2,001 | 2,600 |
🎯 Recommendations
Choose Swoole if…
- Your application is I/O‑heavy – database queries, API calls, Redis, or any external network requests.
- You want to stay in PHP but need Node.js‑ or Go‑level I/O performance.
- You’re willing to invest in learning coroutine‑safe programming patterns.
Choose Node.js if…
- You need a mature async ecosystem with excellent library support.
- Your team is already comfortable with JavaScript/TypeScript.
- You want consistent performance without coroutine‑specific pitfalls.
Choose Go if…
- You need predictable CPU performance and can tolerate occasional P99 latency spikes (or are willing to profile and tune).
- You’re building a microservice where goroutines and channels are a natural fit.
- You value static typing and fast compilation.
Choose Python if…
- You’re in a data‑science or ML‑adjacent environment.
- You need async I/O and are comfortable with
asyncioand async database drivers. - Performance is not your primary constraint.
Stick with PHP‑FPM if…
- Your application is CPU‑bound rather than I/O‑bound.
- You’re on shared hosting or a traditional LAMP stack.
- You don’t want to introduce additional complexity.
🔬 Key Takeaways
- Coroutines change everything for PHP. Swoole transforms PHP from a synchronous, blocking language into a concurrency powerhouse that rivals – and in this test, exceeds – Node.js and Go for I/O‑intensive workloads.
- Synchronous runtimes converge under I/O load. PHP‑FPM, Node.js, Go, and Python all delivered ~390 RPS at 50ms latency.
- Swoole’s advantage grows with latency. At 200ms, Swoole is ~5× faster than all others.
- Go’s P99 latency requires attention. While Go’s average RPS is solid, its tail latency suggests tuning may be needed for production.
- The right tool depends on your bottleneck. CPU‑bound? Go leads. I/O‑bound? Swoole is a game‑changer.
📁 Reproducibility
All test scripts, configuration files, and raw ab outputs are available in the GitHub repository.
To replicate:
git clone https://github.com/phpbenchlab/cross-lang-io-benchmark.git
cd cross-lang-io-benchmark
# Follow the README to start all services and run the benchmark scriptWe’ve included:
- PHP‑FPM with Nginx configuration
- PHP + Swoole with coroutine and connection pooling
- Node.js with
mysql2connection pool - Go with
database/sqland connection pool - Python with
aiomysqlasync connection pool - Full
abbenchmark script - Raw result files
🧵 Final Words
This benchmark tells a clear story: for I/O‑intensive PHP applications, Swoole is a game‑changer. It delivers performance that rivals – and in this test, surpasses – Node.js and Go, while keeping you in the PHP ecosystem. If your application spends most of its time waiting for databases or external APIs, Swoole deserves a serious look.
At the same time, don’t dismiss Go or Node.js – they have their own strengths in CPU‑bound tasks, developer ergonomics, and ecosystem maturity. The key takeaway is that the best language for your project depends on your workload’s bottleneck.
Have you tried Swoole in production? Or migrated from Node.js/Go to PHP for I/O performance? Share your experience in the comments!
Published on June 29, 2026 – PHP 8.5 I/O cross‑language benchmark.