Kiến trúc

Vì sao vẫn nhanh trên một chiếc laptop bình thường.

Màn hình giao dịch rất nặng: lịch sử dài, tick không ngừng, nhiều biểu đồ trên một trang. TradeCanvas chỉ làm đúng phần việc mỗi khung hình cần, trên luồng chính, không có code của bên thứ ba. Trang này cho thấy cách làm, và những chỗ vẫn đang được cải thiện.

Đang phát triển: engine đã đủ nhanh để dùng thật, nhưng chưa nhanh như nó sẽ đạt được. Phần cuối liệt kê những việc chúng tôi biết còn phải làm.

01

Gói nhỏ, phụ thuộc một chiều

Mỗi gói chỉ phụ thuộc vào các gói bên dưới. Không có code nào ngoài TradeCanvas chạy trong trang của bạn: các component framework nhận React, Vue hoặc Svelte làm peer, còn bộ vẽ WebGL là một chunk riêng, chỉ tải khi có biểu đồ cần đến.

0 thư viện bên thứ ba khi chạy

02

Đường đi của một tick

Một mức giá từ feed của bạn lên đến màn hình trong một khung hình. Mỗi tầng chỉ xử lý phần đã thay đổi.

  1. 1Feed của bạnDataAdapterWebSocket, REST, một công ty chứng khoán: bất cứ thứ gì có lịch sử và luồng dữ liệu.
  2. 2Quản lý luồngStreamManagerNạp lịch sử trước rồi mới chạy trực tiếp. Phản hồi của yêu cầu cũ bị bỏ; feed im lặng bị phát hiện trong vòng 30 giây.
  3. 3Gom tickTickAggregatorTick vào một bộ đệm vòng 1.000 phần tử và được gộp vào nến của khung thời gian.
  4. 4Chỉ báoIndicatorEngineTính lại từ nến cuối, không phải từ đầu chuỗi.
  5. 5ViewportViewportChỉ duyệt các nến đang hiện; thang giá chỉ lấy từ chúng.
  6. 6Vòng vẽRenderLoopBao nhiêu tick đến đi nữa, cảnh cũng chỉ vẽ tối đa một lần mỗi khung hình.
  7. 7Canvas 2D hoặc WebGLRenderEngineHai canvas, thêm một canvas GPU bên dưới khi bật WebGL.
03

Một tick chỉ tốn một nến, không phải cả lịch sử

Khi tick làm thay đổi nến cuối, mỗi chỉ báo tính tiếp từ nến đó (và vài nến trước mà công thức cần nhìn lại) thay vì tính lại cả chuỗi. Nếu các nến nó nhớ không còn khớp, nó quay về tính lại toàn bộ, nên không bao giờ bỏ sót một lần sửa dữ liệu.

Khoảng 20 chỉ báo có hàm cập nhật tăng dần riêng, và 31 chỉ báo khác dựng trên một lớp nền tính tiếp từng điểm. Chỉ báo đọc đường của chỉ báo khác cũng tính tiếp được.

0 / 64 Đo trên 100.000 nến với bốn chỉ báo: khoảng 0,001 ms mỗi tick
04

Khi thu nhỏ: mỗi pixel một cột

Khi nến hẹp hơn một pixel, vẽ từng cây là phí công mà mắt không thấy. Nến, thanh OHLC, high-low và khối lượng được vẽ thành mỗi pixel một cột, với giá mở đầu tiên, giá cao nhất, giá thấp nhất và giá đóng cuối cùng của các nến bên dưới. Đường và vùng giữ khoảng hai điểm mỗi pixel (LTTB); đường và dải chỉ báo vẽ một đoạn cho mỗi cột pixel.

nến đang hiện
313
nến mỗi pixel
0,39
hình được vẽ
313

Một nến không bao giờ hẹp hơn một phần tư pixel CSS, nên biểu đồ chứa một triệu nến khi thu nhỏ hết cỡ chỉ hiện tối đa khoảng bốn nến mỗi pixel: khoảng 6.400 nến trên biểu đồ rộng 1.600 px. Bài đo một triệu nến đo việc kéo qua phần còn lại.

05

Hai canvas, nên rê chuột gần như không tốn gì

Cảnh chính (lưới, chuỗi giá, chỉ báo, hình vẽ, lệnh) nằm trên một canvas; đường chữ thập, chú giải, nhãn trục và đồng hồ đếm ngược nằm trên một canvas mỏng phía trên. Rê chuột chỉ vẽ lại canvas trên cùng. Dữ liệu mới vẽ lại cảnh chính một lần, ở khung hình kế tiếp.

Mỗi canvas toàn kích thước khiến trình duyệt phải trộn thêm một lớp mỗi khung hình, nên engine giữ số canvas ít nhất: canvas phụ cho phần chỉ vẽ được bằng 2D chỉ tồn tại khi có thứ cần đến nó.

06

WebGL bằng cách ghi lại, không phải viết lại

Biểu đồ vẽ bằng các lệnh Canvas 2D. Khi bật WebGL, các lệnh đó đi qua một bộ ghi, biến path, fill và stroke thành lệnh GPU theo điểm ảnh thiết bị. Thứ gì GPU không vẽ y hệt được thì ở lại Canvas 2D, đúng thứ tự, nên hình ảnh như nhau dù vẽ bằng cách nào.

  • renderer: 'auto' xin trình duyệt một context không bị cảnh báo hiệu năng lớn, nên máy nào chạy WebGL bằng phần mềm sẽ giữ Canvas 2D.
  • Tối đa 8 biểu đồ trên một trang giữ context WebGL (setMaxWebGLCharts); các biểu đồ còn lại xếp hàng chờ và vẽ bằng Canvas 2D.
  • Nếu GPU làm mất context, biểu đồ tiếp tục bằng Canvas 2D và dựng lại trên WebGL khi context quay về.
  • Nến được làm tròn về đúng điểm ảnh thiết bị theo cùng một quy tắc trong shader và trên CPU, nên cả hai bộ vẽ đặt nến lên cùng các điểm ảnh.
07

Làm cho máy yếu

  • Mặc định Canvas 2DWebGL là tuỳ chọn cho đến khi được đo trên nhiều thiết bị hơn. Trên card tích hợp với tỉ lệ điểm ảnh cao, nút thắt là khâu ghép lớp của trình duyệt chứ không phải khâu vẽ.
  • Mỗi khung hình một lần đổi kích thướcCác sự kiện đổi kích thước được gộp lại mỗi khung hình một lần, và kích thước không đổi thì không làm gì.
  • Lịch sử theo trangNến cũ hơn được tải 500 nến một lần khi bạn kéo về quá khứ, mỗi lần một yêu cầu, và giãn thời gian thử lại sau khi lỗi.
  • Không chớp màn hình tảiLớp phủ đang tải đợi 200 ms, nên đổi mã nhanh thì không bị chớp.
  • Nhẹ để tải vềBiểu đồ không giao diện nặng 176 KB sau gzip, widget đầy đủ 315 KB, bộ vẽ WebGL thêm 18 KB khi dùng.
  • Bảng giá nhảy tại chỗBảng giá bo-grid gộp tick thành một lần cập nhật mỗi khung hình và chỉ vẽ lại những ô đã đổi.
08

Những gì chúng tôi vẫn đang cải thiện

Engine chưa hoàn thiện. Đây là những chi phí chúng tôi đã thấy trong code, và là những việc tiếp theo cần làm.

  1. Chỉ báo cập nhật theo mỗi lần nến thay đổi, chưa gộp theo khung hình: feed gửi nhiều tick trong một khung hình sẽ tính lại nhiều lần hơn số lần vẽ.
  2. Nến đang lưu dưới dạng mảng đối tượng. Lưu theo cột bằng typed array sẽ tốn ít bộ nhớ hơn và duyệt nhanh hơn với lịch sử rất dài.
  3. Mọi thứ chạy trên luồng chính. Đã có sẵn một worker cho phần tính chỉ báo trong code nhưng chưa được nối vào biểu đồ.
  4. Heatmap độ sâu giữ các snapshot trong một mảng thường và quét lại mọi mức giá để tìm giá trị lớn nhất mỗi khung hình; volume profile cấp phát lại các ô mỗi lần vẽ.
  5. Vẫn còn vài đối tượng được tạo mỗi khung hình (path, mảng nhỏ), khiến bộ dọn rác phải làm việc.
  6. Ở 2560×1400 với tỉ lệ điểm ảnh 2 trên card tích hợp, riêng khâu ghép lớp đã mất khoảng 23 ms, nên khung hình WebGL ở đó trung bình 29 ms.
Đo trên máy của bạn →Trang audit chạy các cảnh này ngay trong trình duyệt của bạn: một triệu nến bằng Canvas 2D và WebGL, tick trực tiếp trên 100.000 nến, và bảng giá 1.000 mã.