如何在一般筆電上保持快速。
交易畫面很吃重:漫長的歷史資料、永不停歇的 tick、同一頁面上的多張圖表。TradeCanvas 每格畫面只做必要的工作,全在主執行緒上,不含任何第三方程式碼。本頁說明它是怎麼做到的,以及哪些地方仍在改進。
持續開發中:引擎已經快到可以上線,但還沒有達到它將來的速度。最後一節列出我們已知尚待處理的部分。
小巧的套件,單一方向
每個套件只相依於它下層的套件。TradeCanvas 以外的程式碼都不會在你的頁面中執行:框架元件將 React、Vue 或 Svelte 作為 peer 相依套件,而 WebGL 渲染器是獨立的區塊,只在圖表需要時才載入。
0 第三方執行期相依套件
一筆 tick 的路徑
價格從你的行情來源抵達,在一格畫面內就呈現在螢幕上。每個階段只處理有變動的部分。
- 1你的行情來源
DataAdapterWebSocket、REST、券商:任何提供歷史資料與串流的來源。 - 2串流管理器
StreamManager先載入歷史資料,再接上即時資料。舊請求的回應會被捨棄;行情來源沉寂時,30 秒內就會察覺。 - 3Tick 彙整器
TickAggregator每筆 tick 先進入容量 1,000 的環形緩衝區,再併入其週期對應的K線。 - 4指標
IndicatorEngine從最後一根K線重新計算,而不是從序列開頭。 - 5可視區
Viewport只走訪螢幕上的K線;價格刻度也只根據它們計算。 - 6繪製迴圈
RenderLoop不論抵達多少筆 tick,每個動畫影格最多只繪製一次場景。 - 7Canvas 2D 或 WebGL
RenderEngine兩層 Canvas,開啟 WebGL 時其下方還有一層 GPU Canvas。
一筆 tick 只花一根K線的成本,而不是整段歷史
當一筆 tick 改變最後一根K線時,每個指標都從這根K線(以及其公式需要回看的前幾根)接續計算,而不是重算整個序列。如果它記住的K線已不再相符,就會退回完整重算,因此不會漏掉任何修正。
約 20 個指標有自己的增量更新,另有 31 個建立在可逐點接續的基礎上。讀取另一個指標線條的指標也能接續計算。
縮小時:每像素一欄
當K線窄於一個像素時,逐根繪製只是在做肉眼看不到的工作。蠟燭圖、美國線、高低線與成交量會以每像素一欄繪製,取其下方K線的第一個開盤價、最高的最高價、最低的最低價與最後的收盤價。折線與面積圖每像素保留約兩個點(LTTB);指標線與通道則每個像素欄繪製一段。
- 畫面中的K線
- 313
- 每像素的K線數
- 0.39
- 繪製的圖形
- 313
K線的寬度永遠不會小於四分之一個 CSS 像素,因此一張含一百萬根K線的圖表縮小到最遠時,每像素最多約顯示四根K線:在 1,600 px 寬的圖表上約 6,400 根。平移瀏覽其餘部分,正是百萬K線基準測試所量測的內容。
兩層 Canvas,讓懸停幾乎不花成本
場景(格線、序列、指標、繪圖、委託)位於一層 Canvas;十字游標、圖例、座標軸標籤與倒數計時位於其上方一層輕量的 Canvas。移動滑鼠只會重繪上方那一層。資料更新則在下一格畫面重繪場景一次。
每一層全尺寸 Canvas 都會讓瀏覽器每格畫面多一次合成,因此引擎盡量讓層數少:只供 2D 繪製的後層 Canvas 只在有需要時才存在。
以記錄實現 WebGL,而非重寫
圖表以 Canvas 2D 呼叫繪製。開啟 WebGL 時,這些呼叫會送到記錄器,由它把路徑、填色與筆畫轉成以裝置像素為單位的 GPU 指令。GPU 無法畫得一模一樣的內容會依序留在 Canvas 2D 上,因此無論哪種方式,畫面都相同。
- 路徑、填色與筆畫
- 以實例化繪製的K線、成交量與格線
- 序列存放在一個 Float32Array 中,每根K線六個浮點數;一筆 tick 只改寫尾端
- 文字
- 圖片與圖樣
- 陰影、濾鏡、特殊裁切
- GPU 無法處理的第一步之後的所有內容
renderer: 'auto'會向瀏覽器要求沒有重大效能警示的 WebGL 內容,因此 WebGL 只能以軟體執行的電腦會維持使用 Canvas 2D。- 同一頁面上最多 8 張圖表持有 WebGL 內容(
setMaxWebGLCharts);其餘在佇列中等待,並以 Canvas 2D 繪製。 - 如果 GPU 丟失了內容,圖表會繼續以 Canvas 2D 繪製,並在內容恢復時重新以 WebGL 建立。
- 著色器與 CPU 以相同規則把K線取整到完整的裝置像素,因此兩種渲染器會把K線放在相同的像素上。
為效能較弱的電腦而打造
- 預設使用 Canvas 2D在更多裝置上量測過之前,WebGL 需要自行開啟。在內建顯示晶片、高像素比的環境下,瓶頸是瀏覽器本身的合成,而不是繪製。
- 每格畫面只調整一次大小調整大小的事件會合併為每格畫面一次,尺寸沒有變化時則什麼都不做。
- 分頁載入歷史資料往回捲動時,更早的K線每次載入 500 根,一次只發一個請求,失敗後會延後重試。
- 不會閃現載入畫面載入遮罩會等待 200 ms,因此快速切換商品時畫面不會閃爍。
- 下載體積小headless 圖表經 gzip 後為 176 KB,完整元件為 315 KB,使用 WebGL 渲染器時再加 18 KB。
- 原地跳動的看板bo-grid 報價看板把 tick 合併為每格畫面一次修補,並只重繪有變動的儲存格。