Thư viện chuẩn
Benchmark
tinybench của Node.js so với testing.B (Go), criterion (Rust), ContinuousClock (Swift) và JMH (Java) để đo hiệu năng hàm.
- Phiên bản tối thiểu
- Node.js ≥ 20Go ≥ 1.24Rust ≥ 1.86Swift ≥ 5.7Java ≥ 8
- Đã chạy thử trên
- Node.js 24.12.0Go 1.27.1Rust 1.98.1Swift 6.2.4Java 25.0.4.1
Code Node.js là ES module: lưu file .mjs hoặc đặt "type": "module" trong package.json.
Go có benchmark built-in trong package testing từ rất sớm. Node.js không có benchmark runner tích hợp, nên ví dụ dưới dùng thư viện tinybench — nhỏ gọn, không dependency, cùng ý tưởng "thêm task vào suite rồi chạy". Rust cũng không có benchmark ổn định trong std (chỉ có trên nightly), nên dùng crate criterion — đòi hỏi một thư mục benches/ riêng khai trong Cargo.toml, không chạy như một file đơn lẻ được. Swift không có thư viện benchmark chuẩn nào; ví dụ dưới tự đo bằng ContinuousClock của std — đơn giản nhưng không có warm-up/thống kê như ba ngôn ngữ kia (xem ghi chú). Java dùng JMH (Java Microbenchmark Harness), công cụ tiêu chuẩn trong hệ sinh thái Java để tránh các bẫy đo lường của JIT.
So sánh đệ quy và vòng lặp (Fibonacci)
import { Bench } from 'tinybench'
const bench = new Bench({ name: 'fib' })
bench
.add('fib#recursion', () => {
fibRec(10)
})
.add('fib#loop', () => {
fibLoop(10)
})
await bench.run()
console.log(bench.name)
console.table(bench.table())
function fibRec(n) {
if (n <= 1) {
return n
}
return fibRec(n-1) + fibRec(n-2)
}
function fibLoop(n) {
let f = [0, 1]
for (let i = 2; i <= n; i++) {
f[i] = f[i-1] + f[i-2]
}
return f[n]
}package example
import "testing"
func BenchmarkFibRec(b *testing.B) {
for b.Loop() { // Go 1.24+: thay cho for i := 0; i < b.N; i++
fibRec(10)
}
}
func BenchmarkFibLoop(b *testing.B) {
for b.Loop() {
fibLoop(10)
}
}
func fibRec(n int) int {
if n <= 1 {
return n
}
return fibRec(n-1) + fibRec(n-2)
}
func fibLoop(n int) int {
f := make([]int, n+1, n+2)
if n < 2 {
f = f[0:2]
}
f[0] = 0
f[1] = 1
for i := 2; i <= n; i++ {
f[i] = f[i-1] + f[i-2]
}
return f[n]
}// Cargo.toml: [dev-dependencies] criterion = "0.8"
// Cargo.toml: cần thêm khối [[bench]] name = "fib", harness = false
// Cargo.toml: package.name phải là "example" (khớp với `use example::...` bên dưới) — đổi tên khác thì phải sửa `use` theo
// src/lib.rs
pub fn fib_rec(n: u64) -> u64 {
if n <= 1 {
n
} else {
fib_rec(n - 1) + fib_rec(n - 2)
}
}
pub fn fib_loop(n: u64) -> u64 {
let mut f = vec![0u64; n as usize + 1];
if n >= 1 {
f[1] = 1;
}
for i in 2..=n as usize {
f[i] = f[i - 1] + f[i - 2];
}
f[n as usize]
}
// benches/fib.rs
use std::hint::black_box;
use criterion::{criterion_group, criterion_main, Criterion};
use example::{fib_loop, fib_rec};
fn benchmark(c: &mut Criterion) {
c.bench_function("fib#recursion", |b| b.iter(|| fib_rec(black_box(10))));
c.bench_function("fib#loop", |b| b.iter(|| fib_loop(black_box(10))));
}
criterion_group!(benches, benchmark);
criterion_main!(benches);import Foundation
func fibRec(_ n: Int) -> Int {
n <= 1 ? n : fibRec(n - 1) + fibRec(n - 2)
}
func fibLoop(_ n: Int) -> Int {
guard n >= 2 else { return n } // 2...n sẽ crash nếu n < 2 (lowerBound > upperBound)
var f = [0, 1]
for i in 2...n {
f.append(f[i - 1] + f[i - 2])
}
return f[n]
}
func measure(_ label: String, iterations: Int = 1_000_000, _ body: () -> Void) {
let elapsed = ContinuousClock().measure {
for _ in 0..<iterations { body() }
}
let perOp = elapsed / iterations
print("\(label)\t\(perOp.formatted(.units(allowed: [.nanoseconds], width: .narrow)))/op")
}
measure("fib#recursion") { _ = fibRec(10) }
measure("fib#loop") { _ = fibLoop(10) }package bench;
import java.util.concurrent.TimeUnit;
import org.openjdk.jmh.annotations.Benchmark;
import org.openjdk.jmh.annotations.BenchmarkMode;
import org.openjdk.jmh.annotations.Mode;
import org.openjdk.jmh.annotations.OutputTimeUnit;
import org.openjdk.jmh.annotations.Scope;
import org.openjdk.jmh.annotations.State;
// Maven: org.openjdk.jmh:jmh-core:1.37 (+ jmh-generator-annprocess:1.37 làm annotation processor)
@State(Scope.Thread)
@BenchmarkMode(Mode.AverageTime)
@OutputTimeUnit(TimeUnit.NANOSECONDS)
public class FibBenchmark {
int n = 10; // field thay vì literal, tránh JIT tính sẵn (constant-fold) mất cả phép đo
@Benchmark
public int fibRec() {
return fibRec(n);
}
@Benchmark
public int fibLoop() {
return fibLoop(n);
}
static int fibRec(int n) {
return n <= 1 ? n : fibRec(n - 1) + fibRec(n - 2);
}
static int fibLoop(int n) {
int[] f = new int[n + 1];
f[1] = 1;
for (int i = 2; i <= n; i++) {
f[i] = f[i - 1] + f[i - 2];
}
return f[n];
}
}$ node examples/benchmark_test.js
# rút gọn từ console.table(); số ns/op thay đổi giữa các lần chạy
fib
fib#recursion 413.13 ns/op (avg)
fib#loop 44.75 ns/op (avg)$ go test -bench=. -benchmem examples/benchmark_test.go
# đã bỏ phần header goos/goarch/cpu; ns/op và allocs thay đổi giữa các lần chạy
BenchmarkFibRec-12 6266443 171.4 ns/op 0 B/op 0 allocs/op
BenchmarkFibLoop-12 56055340 20.10 ns/op 96 B/op 1 allocs/op
PASS
ok command-line-arguments 2.701s$ cargo bench
# đã bỏ phần warm-up/outliers/goodput; số liệu thay đổi giữa các lần chạy
fib#recursion time: [142.51 ns 142.75 ns 142.99 ns]
fib#loop time: [24.489 ns 24.530 ns 24.565 ns]$ swiftc -O main.swift -o main && ./main
fib#recursion 221ns/op
fib#loop 133ns/op$ mvn clean install && java -jar target/benchmarks.jar FibBenchmark
# đã bỏ phần warm-up/cảnh báo Blackhole; Score/Error thay đổi giữa các lần chạy
Benchmark Mode Cnt Score Error Units
FibBenchmark.fibLoop avgt 3 9.271 ± 0.343 ns/op
FibBenchmark.fibRec avgt 3 133.513 ± 1.932 ns/opTham khảo: github.com/miguelmota/golang-for-nodejs-developers#benchmarking