Performance considerations of syncing FX Nodes with NG Nodes

John Hendrikx john.hendrikx at gmail.com
Fri Feb 3 11:44:24 UTC 2023


On 03/02/2023 01:27, Kevin Rushforth wrote:
> A longer answer will take more time than I have at the moment, but 
> here is a quick reply.
>
> 1. We don't have a lot of scene graph performance tests, so before 
> making any changes here we would need to do some targeted testing.
I completely agree here, if there are no tests that can be re-used then 
some will need to be created, that's okay.
>
> 2. FX scene graph node --> NGNode peer is intended to be be one-way
Makes sense.
>
> 3. There are certainly use cases for reusing nodes (attach / detach). 
> VirtualFlow will do this for ListView, TableView, etc., with many 
> rows. I would expect some apps to make use of it as well, since it's 
> cheaper than recreating the FX nodes. There are also cases where nodes 
> are moved from one parent to another and might transiently be 
> "removed" from the graph.
Moving nodes is one of the cases that may degrade in performance. 
Keeping (unused) cell nodes as part of the children list but 
invisible/unmanaged should not be affected (I'm not 100% sure what 
VirtualFlow is doing at the moment, I only know that's how I do it in my 
own ListViewSkin -- I don't think it had a performance reason, more that 
it could result in visual artifacts in 1 rendered frame before correct 
colors were applied -- perhaps a bug in itself).
>
> 4. Setting the node's peer to null when detached will cause it to be 
> recreated when / if the node is added back into the scene graph, which 
> you touch on later in your email. We wouldn't want the case of a 
> scrolling ListView / TableVIew to have to continually recreate peers 
> as the cells are cycled through.
Yeah, not sure if that is what would happen, it depends on how 
VirtualFlow is handling its cells.
>
> Anything done here would need to be done very carefully to ensure both 
> correctness and performance. Your idea of splitting the peer data from 
> the graph connectivity is interesting, but it also sounds pretty 
> intrusive.
>
> Before going down that route I'd want to understand the problem better 
> and see if there was a simpler solution to clear the parent / child 
> relationship in the render graph.

There are multiple problems it seems in this area. At first I thought it 
was only the `removed` list in Parent that is a problem, as it can 
contain old Nodes that don't get cleaned up when not part of a sync 
cycle.  But any node that is not part of a sync cycle may refer to a 
peer, and that peer can refer to parent/children in turn, keeping an 
entire NGNode graph in memory.

For the `removed` list in Parent, there is a simple solution I think.  
It already has a mechanism to mark itself as "fully" dirty when more 
than 20 children are removed. This mechanism could be re-used to also 
mark it as fully dirty when the Parent is no longer part of an active 
sync cycle. If ever re-attached, it would just fully render instead of 
trying to calculate a smaller dirty region based on removed children.

There is also an odd piece of code in Node, in invalidateScenes. In this 
code it will call `peer.release()` which does nothing for any of the 
current NG implementations (even though there are comments that they 
should do "something").  But because it doesn't set `peer` to `null` it 
will later use the "released" peer again if re-attached to a scene.  If 
this is not a mistake, it deserves an explanatory comment I think.

--John


>
> -- Kevin
>
>
> On 2/2/2023 9:17 AM, John Hendrikx wrote:
>> I have a few questions that maybe some Prism/SceneGraph experts could 
>> help me with.
>>
>> 1) Are there any performance tests related to syncing the NGNode 
>> peers? Specifically, I'm interested if there are tests that compare 
>> the performance of "fresh" FX graphs that have never been displayed 
>> before, versus ones that have their peers already created.
>>
>> 2) Does prism code ever refer back to FX Nodes?  I've noticed that 
>> NGGroup imports javafx.scene.Node for one of its method signatures, 
>> but that seems to be a mistake; it can easily be changed to not 
>> require javafx.scene.Node.  Aside from several enums, constants and 
>> data classes (like Color) being re-used from the javafx side, it 
>> seems the NG Prism nodes are well encapsulated and that references 
>> are one way only (FX Nodes refer to NG Nodes via `peer`, but never 
>> the other way around).
>>
>> 3) How common is it to re-use FX Nodes that are no longer part of an 
>> active scene?  I've found myself that it is unwise to detach/recreate 
>> children in high performance controls that reuse cells -- it's often 
>> better to just hide cells that are not currently needed instead of 
>> removing them from the children list.
>>
>> 4) Are there any (major) performance implications to setting the NG 
>> peer to `null` when FX nodes are not part of an active sync cycle 
>> (ie, they have no Scene, or the Scene is not currently visible)?
>>
>> My observations on the sync cycle (syncPeer/doUpdatePeer) is that it 
>> is highly optimized, and tries to avoid new allocations as much as 
>> possible.  However, this seems to come at a price: it leaks memory 
>> when not part of a sync cycle. Given a FX Node graph, that has been 
>> displayed at least once, a mirrored graph is created consisting of 
>> NGNodes. When such a FX Node graph is no longer displayed, any 
>> changes to it are no longer synced to the mirror.
>>
>> This means that when I detach a small portion of the FX Node graph 
>> (let's say a single Node), and keep a reference to only that Node, 
>> that the FX graph can be GC'd. The corresponding NG Node graph 
>> however (which still hasn't been updated) can't be GC'd. The single 
>> detached FX Node has a peer, and this peer has a parent (and 
>> children), keeping not only the detached Node's peer in memory, but 
>> also all the other peers in the mirrored graph.
>>
>> Notifying the NG mirrored graph of changes is not easy, as it must be 
>> done as part of the sync (locking is involved). Even a detached FX 
>> Node can't assume its peer can be modified without locking as it may 
>> still be used in an active rendering pass.
>>
>> This leads me to believe that to move to a situation where peers are 
>> not being leaked would involve clearing peers as soon as FX nodes 
>> becomes detached from the sync cycle.
>>
>> This has no doubt has performance implications, as peers would need 
>> to be recreated if nodes are re-used (see Q3).  However, not all data 
>> that is part of the peers is a problem, and code that simply clears 
>> its peer when detached could be optimized again to be more peformant 
>> (optimizing from a correctly working situation, instead of fixing 
>> problems working from an optimized situation that has memory leaks).
>>
>> One way to optimize this may be to split the data that is tracked by 
>> peers in parts that are just direct copies of FX Node data, and parts 
>> that refer to parent/children.  The data that is just copies could be 
>> kept ("peerData") while the peer itself is nulled.  When the peer 
>> requires recreation, it is constructed as "new NGNode(peerData)".  
>> Because the parent/child linkages are not part of `peerData`, it is 
>> no longer possible for large NG node trees to be kept in memory.  
>> Recreation of NGNode would still require work, but these NGNodes are 
>> much smaller.
>>
>> --John
>>
>


More information about the openjfx-dev mailing list