Tinybench là một thư viện đo lường hiệu năng (benchmarking) nhẹ và nhỏ gọn, cung cấp khả năng mở rộng mạnh mẽ thông qua cơ chế tùy chỉnh TimestampProvider. Việc hiểu và triển khai tính năng này cho phép các nhà phát triển điều chỉnh độ chính xác của bộ đếm thời gian sao cho phù hợp nhất với môi trường runtime cụ thể, từ các ứng dụng Node.js hiệu năng cao đến các môi trường trình duyệt đặc thù.
Thông thường, Tinybench cung cấp sẵn các phương thức đo thời gian dựa trên performance.now() hoặc process.hrtime.bigint(). Tuy nhiên, trong các tình huống yêu cầu độ chính xác cực cao (nano-giây) hoặc tương thích với các hệ thống nhúng có bộ đếm thời gian phần cứng riêng, việc tự định nghĩa một TimestampProvider là giải pháp tối ưu nhất.
Cấu trúc giao diện TimestampProvider
Để tạo ra một nhà cung cấp dấu thời gian tùy chỉnh, bạn cần tuân thủ giao diện TimestampProvider. Giao diện này định nghĩa các phương thức thiết yếu để thư viện tương tác với thời gian hệ thống:
- name: Định danh cho provider.
- fn: Hàm trả về giá trị thời gian hiện tại.
- toMs: Hàm chuyển đổi giá trị thời gian từ đơn vị của provider sang mili-giây.
- fromMs: Hàm chuyển đổi từ mili-giây sang đơn vị của provider.
Triển khai Provider tùy chỉnh
Dưới đây là ví dụ về cách tạo hai loại provider khác nhau: một loại sử dụng Date.now() cho các yêu cầu cơ bản và một loại mô phỏng độ phân giải cao sử dụng BigInt.
1. Provider chuẩn sử dụng Date.now()
import type { TimestampProvider, TimestampValue } from 'tinybench';
// Provider sử dụng Date.now(), đơn vị là mili-giây
const standardClock: TimestampProvider = {
name: 'standard-clock',
fn: () => Date.now(),
toMs: (val: TimestampValue) => Number(val),
fromMs: (ms: number) => ms
};
2. Provider độ phân giải cao (High Resolution)
Đối với các phép đo cần độ chính xác cao hơn, chúng ta có thể chuyển đổi giá trị của performance.now() sang nano-giây giả định để lưu trữ dưới dạng BigInt.
// Provider chuyển đổi performance.now() sang đơn vị nano-giây lưu trữ bằng BigInt
const highResClock: TimestampProvider = {
name: 'high-res-nano',
fn: () => {
// Nhân với 1 triệu để chuyển đổi ms -> ns
return BigInt(Math.floor(performance.now() * 1_000_000));
},
toMs: (val: TimestampValue) => {
// Chia cho 1 triệu để chuyển đổi ns -> ms
return Number(val) / 1_000_000;
},
fromMs: (ms: number) => {
return BigInt(ms * 1_000_000);
}
};
Tích hợp vào Benchmark
Sau khi đã định nghĩa xong provider, bạn có thể truyền nó vào cấu hình khởi tạo của lớp Bench. Việc này giúp thay đổi hoàn toàn cách thức bộ đếm thời gian hoạt động bên trong các bài kiểm tra.
import { Bench } from 'tinybench';
import { highResClock } from './custom-providers';
const suite = new Bench({
time: 2000, // Thời gian chạy mỗi bài kiểm tra (ms)
timestampProvider: highResClock, // Sử dụng provider tùy chỉnh
iterations: 100 // Số lần lặp lại tối thiểu
});
// Thêm một bài kiểm tra mẫu
suite.add('data-transformation', () => {
const data = new Array(100).fill(0);
data.map((x, i) => x + i);
});
// Chạy benchmark
await suite.run();
// Hiển thị kết quả
console.table(suite.table());
Lựa chọn Provider động dựa trên Runtime
Trong các dự án đa nền tảng, việc cứng hóa (hard-coding) một provider cụ thể có thể gây lỗi khi chạy trên môi trường khác (ví dụ: code Node.js chạy trên Browser). Tinybench hỗ trợ cơ chế tự động phát hiện thông qua tùy chọn 'auto', nhưng bạn cũng có thể triển khai logic lựa chọn riêng:
import { isBun, isNode } from 'tinybench';
function getBestProvider(): TimestampProvider {
if (typeof Bun !== 'undefined') {
// Sử dụng API tối ưu của Bun nếu có
return { name: 'bun-native', fn: () => Bun.nanoseconds(), toMs: (v) => Number(v) / 1e6, fromMs: (m) => BigInt(m * 1e6) };
}
if (typeof process !== 'undefined' && process.hrtime) {
// Fallback cho Node.js
return { name: 'node-hrtime', fn: () => process.hrtime.bigint(), toMs: (v) => Number(v) / 1e6, fromMs: (m) => BigInt(m * 1e6) };
}
// Mặc định cho Browser
return { name: 'browser-perf', fn: () => performance.now(), toMs: (v) => Number(v), fromMs: (m) => m };
}
const suite = new Bench({
timestampProvider: getBestProvider()
});
Xử lý các vấn đề thường gặp
Lệch đơn vị thời gian: Lỗi phổ biến nhất là sai sót trong hàm chuyển đổi toMs và fromMs. Hãy đảm bảo hệ số chuyển đổi là nghịch đảo của nhau. Ví dụ, nếu fn trả về nano-giây (10-9s), thì toMs phải chia cho 1.000.000.
Tràn số (Overflow): Khi sử dụng BigInt cho các hệ thống chạy liên tục trong thời gian dài, hãy cân nhắc việc reset hoặc xử lý giá trị trả về để tránh các số quá lớn gây khó khăn khi hiển thị hoặc tính toán thống kê.
Hiệu suất của chính hàm đo thời gian: Hàm fn trong provider sẽ được gọi rất nhiều lần. Tránh thực hiện các phép toán phức tạp hoặc cấp phát bộ nhớ (memory allocation) bên trong hàm này, vì nó sẽ làm sai lệch kết quả benchmark thực tế của code bạn đang đo.