How we fought for bytes on the frontend. Pacifying the gluttonous drawing widget and helping the iPad digest it

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 .





  1. . ~256 Mb.





  2. (). ~316 Mb.





, ~20% — .





, , .





, , , . : , , , , .





" "

, / . , i ?





, - , . , . , , .





" ", :





1000 . — (), — /.





  1. . ~160 Mb.





  2. . ~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



, :





  1. 20



    — (shallow size);





  2. 88



    — , GC (retained size).





— 64- , typed arrays



.





Typed arrays



— , . , , typed arrays



int8



float64



.





, typed arrays



, . , 2 :





  1. buffer



    — ;





  2. 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
}

      
      



:





  1. Points



    — ;





  2. 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%.





:





  1. ;





  2. .





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.








All Articles