Hiệu năng
Quy trình Canvas2D hai canvas chỉ vẽ lại phần đã thay đổi, và mọi bước trong mỗi khung hình
chỉ phụ thuộc vào những gì đang hiện trên màn hình, không phụ thuộc vào lượng lịch sử đã nạp. Số liệu bên dưới lấy từ pnpm bench (một nhân) và từ việc profile widget đang chạy; Feature Lab đo thời gian các lần chuyển thực tế ngay khi bạn bấm.
● DemoHeatmap thanh khoảnDữ liệu thị trường giả lập, sinh ngay trong trình duyệt của bạnKhung hình: không đổi từ 500 đến 100,000 nến
- Hai canvas — một canvas cảnh (lưới, chuỗi giá, chỉ báo, đối tượng trên biểu đồ, trục) và một canvas mỏng phía trên cho con trỏ chữ thập, chú thích và các phần hiển thị khác bám theo con trỏ. Khi rê chuột, chỉ canvas phía trên được vẽ lại (~0.2 ms), và trình duyệt chỉ ghép hai bề mặt chứ không phải bốn.
- Render theo khoảng đang hiện — mọi bộ render, phần tự co giãn thang và khoảng giá của chỉ báo chỉ duyệt các nến đang nằm trong khung nhìn.
- Không tạo rác mỗi khung hình — ảnh chụp trạng thái khung nhìn được cache giữa các lần thay đổi; bộ định dạng số và ngày được dùng lại thay vì tạo mới cho từng nhãn.
- Trục giá tự đổi độ rộng — việc khớp trục theo nhãn dài nhất tốn ~0.05 ms và chỉ bố trí lại khi độ rộng thực sự thay đổi.
Tick trực tiếp: chỉ báo tính tăng dần
Một tick chỉ làm thay đổi nến đang hình thành, nên chỉ báo có update() riêng chỉ tính lại đúng nến đó: ATR, Bollinger Bands, Chande Kroll Stop, DEMA, EMA, Envelope, KAMA, MACD, MA Cross, McGinley Dynamic, OBV, RSI, SMA, SMMA, Stochastic, Ulcer Index, Volume Oscillator, VWMA, WMA. Thêm 31 chỉ báo dựng trên PointwiseIndicator cũng tính tiếp từ nến cuối theo cách đó, và chỉ báo đọc đường của chỉ báo khác cũng vậy. Khi các nến mà chỉ báo ghi nhớ không còn khớp, nó tính lại toàn bộ. BB + EMA + RSI + MACD:
| Lịch sử | Tính lại toàn bộ | update() tăng dần |
|---|---|---|
| 20,000 nến | ~5 ms | ~0.0005 ms |
| 100,000 nến | ~27 ms | ~0.001 ms |
Chỉ báo tuỳ chỉnh cũng có thể dùng cơ chế này — xem Plugin → cập nhật trực tiếp nhanh.
Đổi mã hoặc khung thời gian
- Nạp toàn bộ dữ liệu với chi phí thấp — giá trị chỉ báo nằm trong một
IndicatorValueMap(dựa trên mảng, dựng rẻ hơn ~3× so với mộtMapdùng timestamp làm khoá), vàsetDatadùng lại các nến vẫn còn hợp lệ thay vì sao chép chúng. - Chỉ hiện trạng thái đang tải khi thật sự chậm — biểu đồ trước vẫn hiện; lớp phủ mờ chỉ xuất hiện nếu một lần chuyển kéo dài quá 200 ms, nên một lần chuyển qua mạng bình thường ~130 ms không bao giờ bị nháy.
- Không có dữ liệu cũ lẫn vào — các yêu cầu lịch sử đã bị thay thế sẽ bị bỏ và các socket cũ được tách ra, nên lần bấm cuối cùng luôn thắng.
- Lấy mẫu lại cục bộ — dữ liệu tĩnh đổi khung thời gian mà không cần tải lại; những lần lấy mẫu lại chậm sẽ vẽ lớp phủ mờ trước khi luồng chính bận.
Giảm mẫu LTTB
Biểu đồ đường và vùng giảm mẫu khoảng đang hiện xuống ~2 điểm mỗi pixel bằng Largest-Triangle-Three-Buckets khi số nến nhiều hơn hẳn số pixel — đường trông vẫn y hệt trong khi số điểm phải vẽ ít hơn hàng chục lần. Ở mức zoom bình thường thì không có tác dụng gì. Thuật toán được export để bạn tự dùng:
import { lttbDownsample } from '@tradecanvas/chart'
// indices preserving the shape of a 100k series, reduced to 1600 points
const idx = lttbDownsample(series.length, 1600, (i) => series[i].close) | Số điểm đang hiện → 1600 | Thời gian / khung hình | Thông lượng |
|---|---|---|
| 10,000 | ~0.025 ms | 39,600 / s |
| 100,000 | ~0.32 ms | 3,100 / s |
| 1,000,000 | ~2.6 ms | 380 / s |
Chỉ báo khi thu nhỏ
Khi mỗi nến hẹp hơn một pixel, đường, dải và histogram của chỉ báo được vẽ thành một đoạn cho mỗi cột pixel: từ điểm thấp nhất tới cao nhất trong cột, nối với cột trước, rộng bằng nét đường. Cách này trông gần như y hệt nét vẽ qua hàng nghìn điểm, nhưng tốn ít công raster hơn hẳn. Khi thu nhỏ trên 200.000 nến với Bollinger Bands, EMA, RSI và MACD, mỗi khung hình giảm từ khoảng 54 ms xuống khoảng 21 ms trên GPU tích hợp. node scripts/bench-render.mjs chạy các số đo này trên máy của bạn.
Bộ vẽ WebGL (bản xem trước)
renderer: 'webgl' vẽ vùng biểu đồ và các pane chỉ báo bằng WebGL 2, trên một canvas nằm dưới cảnh 2D: lưới, phiên, nến và khối lượng được vẽ trực tiếp; phần vẽ Canvas 2D của chỉ báo, đường so sánh và phần lớn các loại biểu đồ được ghi lại thành nét, vùng tô và hình chữ nhật trên GPU, khử răng cưa ở mép như Canvas 2D. Heatmap độ sâu, volume profile và market profile cũng được ghi lại như vậy, nằm dưới các cây nến; market profile có hiện bảng thống kê hoặc chữ TPO thì vẫn vẽ bằng Canvas 2D. Chữ, hình vẽ, lệnh giao dịch, trục và crosshair vẫn ở Canvas 2D, cùng với những gì GPU không vẽ giống được (để Canvas 2D vẽ theo đúng thứ tự, nên thứ tự lớp không đổi); plugin chỉ báo tự viết chạy được mà không cần sửa. Nến nằm trên đúng các pixel thiết bị như khi vẽ bằng Canvas 2D; đường và vùng tô chỉ khác ở vài điểm ảnh mép khử răng cưa. Mã WebGL nằm trong một chunk riêng (khoảng 17 KB sau gzip), chỉ tải khi dùng lần đầu; nơi không có WebGL 2, hoặc khi mất context, biểu đồ tiếp tục vẽ bằng Canvas 2D. Khi context quay lại, biểu đồ cũng quay lại WebGL. Mỗi trang có tối đa 8 biểu đồ vẽ bằng WebGL cùng lúc (setMaxWebGLCharts), vì Chrome và Safari chỉ giữ khoảng 16 context WebGL mỗi trang và bỏ cái cũ nhất khi vượt quá; biểu đồ vượt giới hạn vẽ bằng Canvas 2D cho tới khi có context được trả lại. Đã kiểm trên Chrome, Firefox và WebKit.
const chart = new Chart(el, { renderer: 'webgl' })
chart.on('rendererChange', (e) => console.log(e.payload))
// { renderer: 'webgl', reason?: 'contextRestored' }, or { renderer: 'canvas', reason: 'unsupported' | 'contextLost' | 'limit' }
await chart.setRenderer('canvas') // resolves to what draws now
setMaxWebGLCharts(4) // from '@tradecanvas/chart': at most 4 charts on the page draw with WebGL (8 by default) Thời gian mỗi khung hình khi kéo, trên GPU tích hợp (Intel UHD; 16.7 ms là 60 fps):
| Biểu đồ | Tỉ lệ điểm ảnh | Canvas 2D | WebGL |
|---|---|---|---|
| 1600×900, 500 nến + 4 chỉ báo | 2 | 27.4 ms | 19.6 ms |
| 1600×900, thu nhỏ trên 200,000 nến + 4 chỉ báo | 2 | 34.5 ms | 20.2 ms |
| 1600×900, thu nhỏ trên 1,000,000 nến + 4 chỉ báo | 2 | 34.5 ms | 16.7 ms |
| 1600×900, heatmap độ sâu, 240 ảnh chụp × 80 mức giá | 2 | 25.5 ms | 16.8 ms |
| Sáu biểu đồ, mỗi cái 500 nến và hai chỉ báo | 2 | 23.5 ms | 17.2 ms |
| 2560×1400, 2,000 nến + 4 chỉ báo | 1 | 41.6 ms | 17.7 ms |
| 2560×1400, 2,000 nến + 4 chỉ báo | 1.5 | 70.8 ms | 17.6 ms |
| 2560×1400, 2,000 nến + 4 chỉ báo | 2 | 114.5 ms | 29.1 ms |
| 2560×1400, 2,000 nến | 2 | 33.1 ms | 20.9 ms |
Phần lớn khung hình WebGL ở trên đạt 16.7 ms; số trung bình gồm cả vài khung dài hơn. Ở tỉ lệ 2 với biểu đồ 2560×1400, riêng việc trình duyệt ghép các lớp full-size đã mất khoảng 23 ms trên GPU này. node scripts/bench-render.mjs --renderer=webgl chạy các số đo này trên máy của bạn.
Một nến không bao giờ hẹp hơn một phần tư pixel CSS, nên “thu nhỏ trên 1,000,000 nến” hiện khoảng bốn nến mỗi pixel (khoảng 6.400 nến trên biểu đồ rộng 1600 px); cảnh đo kéo qua phần còn lại của một triệu nến.
Điện thoại và ứng dụng
Điện thoại có màn hình 3x, CPU chậm hơn và chạy pin. Biểu đồ vẽ ở mật độ điểm ảnh tối đa 2 (maxPixelRatio): ít hơn 56% điểm ảnh so với 3x, nét gần như không mềm đi. Nó ngừng vẽ khi ra khỏi màn hình hoặc khi trang bị ẩn (pauseWhenHidden), và ứng dụng nhúng nó trong WebView có thể tạm dừng bằng suspend(). Luồng dữ liệu gửi tick nhanh hơn số khung hình thì chỉ báo được tính lại một lần mỗi khung, trước khi có ai đọc. Khi trang hiện lại hoặc có mạng lại, luồng im lặng quá lâu sẽ kết nối lại ngay thay vì chờ heartbeat; biểu đồ bị huỷ trả lại bộ nhớ canvas ngay.
new Chart(host, {
maxPixelRatio: 2, // the default; Infinity draws at the screen's own
pauseWhenHidden: true, // the default: no frames off screen or on a hidden page
})
chart.suspend() // your app went to the background (a WebView): stop drawing
chart.resume() // back: one frame draws what changed Đo trên màn hình điện thoại (390 × 844 ở 3x, CPU chậm 4 lần, GPU tích hợp), một biểu đồ với bốn chỉ báo:
| Trước | Nay | |
|---|---|---|
| Vẽ khung cảnh, 300 nến | 34 ms | 25 ms |
| Realtime, 2 tick mỗi khung | 18.5 ms | 11–13 ms |
| Bộ nhớ canvas | 17.1 MB | 7.6 MB |
Widget trên màn hình cảm ứng: ô nhập 16 px để trang không tự zoom khi chạm vào, bảng trượt nằm trên bàn phím ảo, chiều cao theo đơn vị viewport động. Để tự đo trên máy: node scripts/bench-render.mjs --mobile.
Luồng chính
Biểu đồ tính chỉ báo trên luồng chính. IndicatorWorkerHost trong @tradecanvas/core có thể chạy calculate() trong một worker do bạn cung cấp, với API trả về Promise, thời gian chờ cho từng yêu cầu và phương án đồng bộ dự phòng cho SSR và test, nhưng biểu đồ chưa đưa chỉ báo của nó qua đây, và các gói chưa kèm script worker.