Как он остаётся быстрым на обычном ноутбуке.
Торговые экраны тяжелы: длинная история, тики без остановки, много графиков на одной странице. TradeCanvas делает ровно столько работы, сколько нужно каждому кадру, в основном потоке, без стороннего кода. Эта страница показывает, как, и где работа ещё идёт.
Работа продолжается: движок достаточно быстр для продакшена, но ещё не так быстр, как будет. В последнем разделе перечислено, что осталось.
Маленькие пакеты, одно направление
Каждый пакет зависит только от тех, что ниже него. На вашей странице не работает ничего, кроме TradeCanvas: компоненты для фреймворков берут React, Vue или Svelte как peer-зависимость, а рендерер WebGL — отдельный чанк, который загружается, только когда его запрашивает график.
0 сторонних runtime-зависимостей
Путь тика
Цена приходит из вашего потока и попадает на экран за один кадр. Каждый этап делает только ту часть, которая изменилась.
- 1Ваш поток
DataAdapterWebSocket, REST, брокер: всё, у чего есть история и поток. - 2Менеджер потока
StreamManagerСначала история, потом живые данные. Ответы на устаревший запрос отбрасываются; замолчавший поток замечается в пределах 30 с. - 3Агрегатор тиков
TickAggregatorТики попадают в кольцевой буфер на 1000 и сворачиваются в бар своего таймфрейма. - 4Индикаторы
IndicatorEngineПересчитываются с последнего бара, а не с начала серии. - 5Область просмотра
ViewportОбходятся только бары на экране; ценовая шкала строится только по ним. - 6Цикл отрисовки
RenderLoopСколько бы тиков ни пришло, сцена рисуется не чаще одного раза за кадр анимации. - 7Canvas 2D или WebGL
RenderEngineДва canvas, а под ними GPU-canvas, когда включён WebGL.
Тик стоит одного бара, а не всей истории
Когда тик меняет последний бар, каждый индикатор продолжает с этого бара (и с нескольких предыдущих, на которые оглядывается его формула), а не пересчитывает серию. Если запомненные им бары больше не совпадают, он переходит к полному пересчёту, так что ни одна поправка не теряется.
Около 20 индикаторов имеют собственное инкрементальное обновление, а ещё 31 построен на основе, которая продолжает расчёт точка за точкой. Индикаторы, читающие линию другого индикатора, тоже продолжают расчёт.
При отдалении: один столбец на пиксель
Когда бары становятся уже пикселя, рисовать каждый из них — пустая работа, которую глаз не увидит. Свечи, бары, максимум-минимум и объём рисуются одним столбцом на пиксель: с первым открытием, наибольшим максимумом, наименьшим минимумом и последним закрытием баров под ним. Линии и области сохраняют около двух точек на пиксель (LTTB); линии и полосы индикаторов рисуют один отрезок на столбец пикселей.
- баров в поле зрения
- 313
- баров на пиксель
- 0,39
- нарисовано фигур
- 313
Бар никогда не бывает уже четверти CSS-пикселя, поэтому график с миллионом баров при максимальном отдалении показывает не больше четырёх баров на пиксель: около 6400 на графике шириной 1600 px. Прокрутку по остальным и измеряет бенчмарк на миллион баров.
Два canvas, чтобы наведение почти ничего не стоило
Сцена (сетка, серия, индикаторы, рисунки, ордера) живёт на одном canvas; перекрестие, легенда, подписи осей и обратный отсчёт — на тонком canvas над ним. Движение мыши перерисовывает только верхний. Обновление данных перерисовывает сцену один раз, в следующем кадре.
Каждый полноразмерный canvas стоит браузеру одного смешивания на кадр, поэтому движок держит их число небольшим: задний canvas для чисто 2D-рисования существует, только пока он кому-то нужен.
WebGL через запись, а не через переписывание
График рисует вызовами Canvas 2D. С включённым WebGL эти вызовы попадают в рекордер, который превращает пути, заливки и обводки в команды GPU в физических пикселях. Всё, что GPU не может нарисовать так же, остаётся на Canvas 2D, по порядку, так что картинка одинакова в обоих случаях.
- пути, заливки и обводки
- свечи, объём и сетка как инстансные вызовы отрисовки
- серия в одном Float32Array, шесть чисел на бар; тик переписывает только хвост
- текст
- изображения и узоры
- тени, фильтры, необычные обрезки
- всё после первого шага, который GPU сделать не может
renderer: 'auto'просит у браузера контекст без серьёзного ограничения производительности, поэтому машина, где WebGL работал бы программно, остаётся на Canvas 2D.- Контекст WebGL держат не более 8 графиков на странице (
setMaxWebGLCharts); остальные ждут в очереди и рисуются через Canvas 2D. - Если GPU теряет контекст, график продолжает работать на Canvas 2D и перестраивается на WebGL, когда контекст возвращается.
- Свечи округляются до целых физических пикселей по одному правилу в шейдерах и на CPU, поэтому оба рендерера ставят их на одни и те же пиксели.
Создан для слабых машин
- Canvas 2D по умолчаниюWebGL включается по желанию, пока его не измерили на большем числе устройств. На встроенной графике с высокой плотностью пикселей узкое место — композиция в самом браузере, а не отрисовка.
- Одно изменение размера на кадрСобытия resize объединяются в одно на кадр, а неизменившийся размер ничего не запускает.
- История страницамиСтарые бары подгружаются по 500 при прокрутке назад, по одному запросу за раз, с паузой после сбоя.
- Без мигания при загрузкеВуаль загрузки ждёт 200 мс, поэтому быстрая смена символа никогда не мигает.
- Небольшой размер загрузкиHeadless-график весит 176 KB после gzip, полный виджет — 315 KB, рендерер WebGL при использовании — ещё 18 KB.
- Табло, обновляемые на местеТабло котировок bo-grid объединяет тики в одно обновление на кадр и перерисовывает только изменившиеся ячейки.