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.

Khung 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 các chỉ báo có cài đặt update() chỉ tính lại đúng nến đó (SMA, EMA, WMA, VWMA, Bollinger, Envelope, RSI, MACD, ATR, OBV, Stochastic). Các chỉ báo khác quay về 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ột Map dùng timestamp làm khoá), và setData dù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 → 1600Thời gian / khung hìnhThông lượng
10,000~0.025 ms39,600 / s
100,000~0.32 ms3,100 / s
1,000,000~2.6 ms380 / 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. 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 khớp Canvas 2D, lệch không quá 2/255; đườ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.

const chart = new Chart(el, { renderer: 'webgl' })

chart.on('rendererChange', (e) => console.log(e.payload))
// { renderer: 'webgl' }, or { renderer: 'canvas', reason: 'unsupported' | 'contextLost' }

await chart.setRenderer('canvas')   // resolves to what draws now

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 ảnhCanvas 2DWebGL
1600×900, 500 nến + 4 chỉ báo227.4 ms19.6 ms
1600×900, thu nhỏ trên 200,000 nến + 4 chỉ báo234.5 ms20.2 ms
Sáu biểu đồ, mỗi cái 500 nến và hai chỉ báo223.5 ms17.2 ms
2560×1400, 2,000 nến + 4 chỉ báo141.6 ms17.7 ms
2560×1400, 2,000 nến + 4 chỉ báo1.570.8 ms17.6 ms
2560×1400, 2,000 nến + 4 chỉ báo2114.5 ms29.1 ms
2560×1400, 2,000 nến233.1 ms20.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.

Ngoài luồng chính

IndicatorWorkerHost chạy phần tính toán chỉ báo trong một Web Worker với calculate() trả về Promise, thời gian chờ tối đa cho từng yêu cầu, và phương án chạy đồng bộ dự phòng cho SSR và test.