Huge amount of repaint/reflow (animationFrameRequest)


#1

Case: View with nested use of custom elements
Seems that each dom element added causing animationFramerequest (RAF)

NB! not blaming framework :slight_smile: (I’m a beginner)
It’s a principal question:
Ho to reduce number of repaint / reflow in case where loading a view causing
big change in number of element in DOM

Problem is from an existing application in production state
Hopefully solvable without big rewrite
(can not use virtual repeat, row/cell hight not deterministic)


#2

If you are running into perf issue, you can check if you are doing expensive work before or after a view is attached to the DOM. If possible, move logic related to manipulate elements from attached() to bind(), if you don’t have to calculate their real dimension. Would be easier to help if you can give some code examples. Beside that, some number would be nice: how big is the array, etc…


#3

No manipulation in attached(), bind() used. Mayby it the css causing Aurelia putting
all elemets in requestAnimationFrame queue… ?

Modified aurelia-binding.js with a conso.log of queue.length
image

Loading real data, 91672 goes into queue.length (Aurelia queue request for animationFrame)
console when loading view in browser. get 117 message of
aurelia-binding.js:155 [Violation] ‘requestAnimationFrame’ handler took 77ms (variation 50ms - 150ms)


#4

This is the connect-queue placing the observeProperty calls inside RAF. 91672 property descriptors (or a multiple thereof) are created to observe all your view models.
You can verify this by adding & oneTime to your interpolations and/or .one-time in your binding expressions.
What does your view look like?

Note: the problem isn’t really the RAF or connect-queue itself, but rather that there are a whopping 91k things to observe. This is going to be slow no matter what you do. If acceptable, using oneTIme bindings would be your way to go. That would work if the data properties don’t change after loading.


#5

May I see how your view code looks like, and how you are assigning new array to the repeat?


#6

If all observProperty goes into RAF, it’s a new
knowledge / understanding for me (how the RAF got into play)

Try to progress further with oneTime binding where it can bee done.

The view elements and repeets, in general a matrix:
nested structure of view and element’s
i.e. 500 person one week
ending in 500 x 7 -> 3500 operationalDuty

operationalPlan.html

<template view-cache="*">	
...
	<grid-row				
		repeat.for="employee of employees.items & oneWay"
		employee.one-time="employee"
		plan.one-time="$parent">
	</grid-row>
..
</template>

gridRow.html

<template view-cache="*">	
..
	<grid-cell-operational			    
		repeat.for="day of days & oneWay"
		employee.to-view="employee"
		day.to-view="day"
		plan.one-time="plan"
		group-view.one-time="groupView"
		containerless>
	</grid-cell-operational>
..
</template>

gridCellOperational.html

<template view-cache="*">
..
	<operational-duty
			repeat.for="duty of duties & oneWay & signal:'filter-updated':'duty-updated'"
			duty.to-view="duty"
			day.to-view="day"
			plan.one-time="plan"
			group-view.one-time="groupView"
			containerless>
	</operational-duty>
..
</template>

operationalDuty.html

<template view-cache="*">
...
	<!-- Information on a duty in the scheduler -->
...
</template>

#7

Not all observeProperty goes into RAF.

How connect-queue works

The first 100 connect calls will execute immediately. Only when the number of bindings that need to be connected is higher than 100 will the items past the first 100 be queued with RAF.

Then there is also a limitation on how many are executed within a RAF. If the queue has been processing for 15ms, the remainder will go with the next RAF (this algorithm is a bit naive as the real budget of performing work is typically less than 7ms, this is a point for improvement).

Effectively what this does, is it ensures that your page will always load after no more than initialization time + 15ms + time to attach everything. It puts an upper bound on by how much the observation can slow down page loads.
It also means that for a very large number of bindings, your app will continue processing the initialized observations after the page appears to be done loading. This is likely what’s causing the several clogged-up RAF’s you see occurring after loading a view with lots of data in it.

To put it in other words: it’s the “price” you’re paying for the large number of items. The alternative would be to have to look at a completely not loaded page for the time that it currently takes for the RAFs to be done.

Ways to optimize

virtual-repeat
You already know about the virtual repeater. Technically this could be built upon to make it adapt to dynamic item height - this is something I’ll be experimenting with in vNext at least.

You might be able to “hack” around the non-deterministic height problem, as long as you know the minimum height of an element, to simply give it the lowest possible height.
It will render a bunch of off-screen items but still likely far fewer than the normal repeater does now.

when-visible
jdanyow has created a binding behavior for this sort of problem, which causes your bindings to only be updated if they’re visible on screen. I don’t know precisely how it works (still need to look into it myself) but in any case, it might be of use to you: https://github.com/jdanyow/aurelia-when-visible

use signals instead of observation
I’m not a fan of this idea because it kind of goes against the spirit of Aurelia. But if the other solutions aren’t viable and this is a critical thing to your app, you could just make everything one-time / & oneTime and use signals to update everything.

In vNext we’ll be addressing this with a more generalized switch for observation methods, in vCurrent it’s not possible to do this very cleanly unfortunately.

I also noticed you use & oneWay in your repeaters. This is redundant, just FYI. Collection bindings (or really any bindings that are not targeting form elements) are one-way / to-view (same thing) by default.


#8

Some recraft of UX to get the virtual repeater to work
is kind of safe path, and should work. It is used several places in the application.

Thanks a lot, appreciate the comments !
Posting back later, solution selected to get around the issue