아키텍처

평범한 노트북에서도 빠른 이유.

트레이딩 화면은 무겁습니다. 긴 히스토리, 멈추지 않는 틱, 한 페이지의 여러 차트. TradeCanvas는 프레임마다 필요한 일만, 메인 스레드에서, 서드파티 코드 없이 합니다. 이 페이지는 그 방법과 아직 개선 중인 부분을 보여 줍니다.

개발 중: 엔진은 실제로 쓰기에 충분히 빠르지만 아직 도달할 속도에는 이르지 않았습니다. 마지막 섹션에 남은 일을 적었습니다.

01

작은 패키지, 한 방향

각 패키지는 아래 패키지에만 의존합니다. TradeCanvas 밖의 코드는 페이지에서 실행되지 않습니다. 프레임워크 컴포넌트는 React, Vue, Svelte를 peer로 받고, WebGL 렌더러는 차트가 요청할 때만 불러오는 별도 청크입니다.

0 런타임 서드파티 의존성

02

틱 하나가 지나는 길

피드에서 온 가격이 한 프레임 안에 화면에 닿습니다. 각 단계는 바뀐 부분만 처리합니다.

  1. 1여러분의 피드DataAdapterWebSocket, REST, 증권사: 히스토리와 스트림이 있으면 무엇이든.
  2. 2스트림 매니저StreamManager히스토리를 먼저, 그다음 실시간. 이전 요청의 응답은 버리고, 조용해진 피드는 30초 안에 알아챕니다.
  3. 3틱 집계TickAggregator틱은 1,000개짜리 링 버퍼에 들어가 해당 타임프레임의 봉으로 합쳐집니다.
  4. 4지표IndicatorEngine시리즈 처음부터가 아니라 마지막 봉부터 다시 계산합니다.
  5. 5뷰포트Viewport화면에 보이는 봉만 순회하고, 가격 축도 그 봉들로만 정합니다.
  6. 6렌더 루프RenderLoop틱이 몇 개 오든 장면은 애니메이션 프레임당 최대 한 번만 그립니다.
  7. 7Canvas 2D 또는 WebGLRenderEngine캔버스 두 개, WebGL을 켜면 그 아래 GPU 캔버스 하나.
03

틱 하나는 봉 하나의 비용, 히스토리 전체가 아닙니다

틱이 마지막 봉을 바꾸면 각 지표는 시리즈 전체를 다시 계산하지 않고 그 봉(과 공식이 되돌아보는 몇 개의 봉)부터 이어서 계산합니다. 기억하는 봉이 더 이상 맞지 않으면 전체 재계산으로 돌아가므로 데이터 수정을 놓치지 않습니다.

약 20개 지표는 자체 증분 업데이트를 갖고, 31개는 점 단위로 이어서 계산하는 기반 위에 만들어졌습니다. 다른 지표의 선을 읽는 지표도 이어서 계산합니다.

0 / 64 지표 4개, 100,000개 봉에서 측정: 틱당 약 0.001 ms
04

축소하면 픽셀당 한 열

봉이 1픽셀보다 좁아지면 하나씩 그리는 것은 눈에 보이지 않는 낭비입니다. 캔들, OHLC 바, 하이로, 거래량은 픽셀당 한 열로 그리며, 그 아래 봉들의 첫 시가, 최고가, 최저가, 마지막 종가를 씁니다. 선과 영역은 픽셀당 약 두 점(LTTB)을 유지하고, 지표 선과 밴드는 픽셀 열마다 한 구간을 그립니다.

화면 속 봉
313
픽셀당 봉
0.39
그린 도형
313

봉은 CSS 픽셀의 4분의 1보다 좁아지지 않습니다. 그래서 100만 개 봉을 가진 차트도 끝까지 축소하면 픽셀당 최대 약 4개, 1,600 px 차트에서 약 6,400개를 보여 줍니다. 100만 봉 벤치마크는 나머지를 이동하는 성능을 잽니다.

05

캔버스 두 개, 그래서 호버는 거의 비용이 없습니다

장면(그리드, 시리즈, 지표, 드로잉, 주문)은 한 캔버스에, 크로스헤어, 범례, 축 라벨, 카운트다운은 그 위의 얇은 캔버스에 있습니다. 마우스를 움직이면 위 캔버스만 다시 그립니다. 데이터가 바뀌면 다음 프레임에 장면을 한 번 그립니다.

전체 크기 캔버스마다 브라우저는 프레임당 한 번 합성을 더 합니다. 그래서 엔진은 캔버스를 적게 유지하고, 2D 전용 그리기용 보조 캔버스는 필요할 때만 둡니다.

06

다시 쓰지 않고 기록해서 WebGL로

차트는 Canvas 2D 호출로 그립니다. WebGL을 켜면 그 호출이 레코더로 가서 경로, 채우기, 선을 디바이스 픽셀 기준 GPU 명령으로 바꿉니다. GPU가 똑같이 그릴 수 없는 것은 순서를 지키며 Canvas 2D에 남으므로, 어느 쪽이든 그림은 같습니다.

  • renderer: 'auto'는 큰 성능 경고가 없는 컨텍스트를 요청하므로, WebGL을 소프트웨어로 돌리는 기기는 Canvas 2D를 유지합니다.
  • 한 페이지에서 최대 8개 차트가 WebGL 컨텍스트를 가집니다(setMaxWebGLCharts). 나머지는 대기열에서 Canvas 2D로 그립니다.
  • GPU가 컨텍스트를 잃으면 차트는 Canvas 2D로 계속 그리고, 컨텍스트가 돌아오면 WebGL로 다시 구성합니다.
  • 캔들은 셰이더와 CPU에서 같은 규칙으로 디바이스 픽셀에 맞춰지므로, 두 렌더러가 같은 픽셀에 그립니다.
07

저사양 기기를 위해

  • 기본은 Canvas 2DWebGL은 더 많은 기기에서 측정될 때까지 선택 사항입니다. 높은 픽셀 비율의 내장 그래픽에서는 그리기가 아니라 브라우저의 합성이 병목입니다.
  • 프레임당 한 번의 리사이즈리사이즈 이벤트는 프레임당 한 번으로 묶이고, 크기가 그대로면 아무것도 하지 않습니다.
  • 페이지 단위 히스토리과거로 스크롤하면 이전 봉을 500개씩, 한 번에 한 요청만, 실패 후에는 간격을 늘리며 불러옵니다.
  • 로딩 깜박임 없음로딩 화면은 200 ms를 기다리므로 빠른 종목 전환에서는 깜박이지 않습니다.
  • 작은 다운로드헤드리스 차트는 gzip 기준 176 KB, 전체 위젯은 315 KB, WebGL 렌더러는 쓸 때 18 KB가 더해집니다.
  • 제자리에서 갱신되는 시세판bo-grid 시세판은 틱을 프레임당 한 번의 패치로 합치고 바뀐 셀만 다시 그립니다.
08

아직 개선 중인 것

엔진은 완성되지 않았습니다. 소스에서 확인한 비용이며, 다음에 작업할 항목입니다.

  1. 지표는 프레임당 한 번이 아니라 봉이 갱신될 때마다 업데이트됩니다. 프레임마다 틱이 많이 오는 피드는 그리는 횟수보다 더 자주 계산합니다.
  2. 봉은 객체 배열로 저장됩니다. 타입 배열로 된 열 기반 저장이면 매우 긴 히스토리에서 메모리를 덜 쓰고 더 빨리 순회합니다.
  3. 모든 것이 메인 스레드에서 실행됩니다. 지표 계산용 워커가 코드에 있지만 아직 차트에 연결되지 않았습니다.
  4. 호가 히트맵은 스냅샷을 일반 배열에 두고 프레임마다 모든 호가를 훑어 최대값을 찾으며, 볼륨 프로파일은 그릴 때마다 버킷을 새로 할당합니다.
  5. 일부 객체(경로, 작은 배열)는 여전히 프레임마다 할당되어 가비지 컬렉터에 일을 줍니다.
  6. 내장 그래픽에서 2560×1400, 픽셀 비율 2일 때 합성만 약 23 ms가 걸려 WebGL 프레임은 평균 29 ms입니다.
내 기기에서 측정하기 →감사 페이지는 이 장면들을 여러분의 브라우저에서 실행합니다: Canvas 2D와 WebGL의 100만 봉, 10만 봉 위의 실시간 틱, 1,000종목 시세판.