The issue of performance is increasingly being discussed in the front-end community as products, applications and sites become more complex - data, graphics, online interactions of a large number of users. This, as well as the sharp digitalization of all offline processes due to the lockdown, has led to the fact that current solutions require refinement and optimization for increased loads.
So it happened with our product: 2020 was the year of performance optimization for Miro. The more users interact on one board, the more content is generated, the more data each user's browser needs to receive, process and display in real time.
Intro
I work in the Canvas Widgets team, we are developing widgets on a canvas (a board with widgets). Once a product from a mobile team came to us with feedback from users. Its essence boiled down to one thing: on the iPad, with a large number of drawing widgets - as we call the pen tool - the interface elements began to "blink", disappear and the application could not be used.
What the statistics said:
130 tickets in the bug reporter from January to October 2020, that's about 13 tickets per month;
35% iPad — , ;
iPad — 95k ;
89% iPad — ;
iPad.
, , — iPad.
. , , — .
( Level of Detail, LoD) — , . , . — 2 .
— , . iPad .
: , . — FPS , .
" "
. , .
, , . , , .
" ", :
4000 .
. ~256 Mb.
(). ~316 Mb.
, ~20% — .
, , .
, , , . : , , , , .
" "
, / . , i ?
, - , . , . , , .
" ", :
1000 . — (), — /.
. ~160 Mb.
. ~105 Mb.
, ~35%.
, , ~50% — , ? — " ", , . — , . , UX:
? ?
?
— ?
: — ?
:
: 1 ?
: , ?
, UX , , .
" "
, . , PIXI.js
, — PIXI.Point
. 2 :
points: PIXI.Point[]
— ;
controlPoints: PIXI.Points[][]
— .
4000 , ~100 . :
|
|
() |
() |
PIXI.Point |
580482 |
12 |
30 |
Array |
307854 |
35 |
53 |
(heap): 165
.
, PIXI.Point
, :
20
— (shallow size);
88
— , GC (retained size).
— 64- , typed arrays
.
Typed arrays
— , . , ,typed arrays
int8
float64
.
buffer
— ;
view
— , —int8, uint8, int16, uint16, ...
.
, typed arrays
.
:
interface IPoint {
x: number
y: number
}
interface ICurveWidgetJson {
points: IPoint[]
}
— 2 float64
, Float64Array. , 16
.
, :
type ToArrayResult<PointType, SerializedType> = PointType extends [] ? SerializedType[][] : PointType[]
interface IOptimizedPoints<Data> {
length: number
toArray<OutPoint extends IPoint>(
pointConstructor?: new (x: number, y: number) => OutPoint
): ToArrayResult<Data, OutPoint>
forEach(callbackfn: (data: Data, index: number) => void): void
some(predicate: (value: Data, index: number) => boolean): boolean
}
:
Points
— ;
ControlPoints
— ( ).
:
pointsCount * POINT_ELEMENTS_COUNT * Float64Array.BYTES_PER_ELEMENT
pointsCount
— ;
POINT_ELEMENTS_COUNT
— () (- 2);
Float64Array.BYTES_PER_ELEMENT
— (8 ).
— ControlPoints
:
// 'in/out':
[[p1, p2] [p3], [p4, p5]]
// 'store as':
[p1.x, p1.y, p2.x, p2.y, p3.x, p3.y, p4.x, p4.y, p5.x, p5.y]
, .
heap'a :
|
|
() |
() |
PIXI.Point |
184048 |
4 |
8 |
Array |
255144 |
29 |
46 |
(heap): 130
.
, typed arrays
:
|
|
() |
() |
Points |
8024 |
0.11 |
8 |
ControlPoints |
4012 |
0.09 |
2 |
PIXI.Point
3 . typed arrays
, , , .
, ~20%.
:
;
.
Together, these optimizations have reduced memory consumption by ~ 40%.
A few months after the implementation of the optimizations, we have the following picture:
15 tickets in the bug reporter in 2 months, which is ~ 7 tickets per month, which is 2 times less than the starting point;
The number of alerts sent when the memory limit is reached dropped from 21k to 15k in a month.
Did the result of the work allow you to say with confidence that the problem will never arise again? No, but it will arise much later, allowing users who encountered it to continue working, and also create a springboard for the further development of the drawing widget.