[Feedback] Perspective view load-ahead optimization

An important new optimization for Perspective view loading has just been merged and will be available in the 8.1.5 nightly starting tomorrow, 4/7. This optimization nearly eliminates the performance penalty of composing pages/views using embedded sub-views by loading them ahead of time with their parent, reducing or eliminating browser layout thrash and providing a more pleasant navigation experience.

The load-ahead logic is compatible with Embedded View, Flex Repeater, and Tab Container components that are statically configured. This means that the properties like path and in the case of the repeater, instances are configured in a fixed manner and not driven by a binding or a script. In the case of dynamic configuration, everything will simply function as it did before. The reason it works like this is that we can’t do an optimized loading-ahead for configuration values that aren’t present until some unspecified time later when a binding or script provides a value.

We have observed dramatic speedups in navigation time in our in-house test projects, however, it is hard for us to know just how common the kinds of ideal view scenarios are that benefit most from this kind of optimization. We are curious to hear feedback to see how much benefit is observed in your real-world projects. If you want to explore a side by side comparison after updating, you may disable the optimization in your browser’s dev console using __client.flags.load_ahead=false (this setting is not sticky and will clear on reload or refresh)

Lastly, we currently have this optimization enabled all the time. We haven’t seen any downsides, but if any are discovered it may make sense for us to add a property to allow you to opt out of this optimization on a per-view basis.

10 Likes

Hi Carl,

Do you load all static embedded views when loading resources in the perspective startup screen?

For me, I create most of my objects dynamically by script in flex repeater, so we also need another optimization for this kind of senario. Do you have any plan for that too?

Do you load all static embedded views when loading resources in the perspective startup screen?

No, not in the startup screen. What's happening here is that whenever we load any view, we look in that view and see if we can find any other views that we know will be mounted as a consequence of the view that is loading. In this way, we can load all the views together, rather than: loading the parent view, mounting it, seeing that sub-views mounted, loading them, mounting them, seeing than views beneath them mounted, etc etc.

...most of my objects dynamically by script in flex repeater, so we also need another optimization for this kind of scenario...

Your views that are dynamically added to a flex repeater still may benefit from this optimization. For example, if the views that you're putting into the flex repeater themselves have embedded views inside them, they (each direct child view of the repeater) will load faster because of this optimization.

That said, yes, we are exploring some different kinds of optimizations for the repeaters that don't rely on static configuration of the repeater.

3 Likes

Should be interesting to see. I’m on 8.1.1 still. But, I’ve notcied that embedded views for cells in a table with hundereds of rows will take a long time to load when you scroll down the table

This isn't too surprising, and probably won't be affected by this optimization, unless those views inside those cells themselves embed more views.

Hi Carl, sorry to be the bearer of bad news, but i’ve just tested this and it’s actually made our load times worse and increased them by more than 2x.

I took 2 GIF screencaptures at 30fps (nominal, the app [https://www.screentogif.com/] I use I believe does some smarts to work out if frames have changed or not to discard duplicates) when loading the same screen with the load-ahead enabled and disabled. I cut these animations down to only the frames where the page was loading from start to end, and the results are below.
Edit: I just added an additional two tests performed right on the server itself in Chrome.

What I found was that when the load-ahead option is enabled, the loading icon overlay (image ) actually displayed for approximately the time it took for the disabled option to fully load the page (for one run, the loading icon overlay displayed for 2220ms, then the tag bindings took another 2740ms to load once the loading overlay disappeared).

Total frame time: (35, 4720)  <-- 35 frames, 4720ms in total load time
File: Perspective 8.1.5 speed increase comparison - New TF FS - Active - TF Detail Only.gif

Total frame time: (35, 4960)
File: Perspective 8.1.5 speed increase comparison - New TF FS - Active - TF Detail Only 2.gif

Total frame time: (47, 6140)
File: Perspective 8.1.5 speed increase comparison - New TF FS - Server Client - Active - TF Detail Only 1.gif

Total frame time: (51, 6060)
File: Perspective 8.1.5 speed increase comparison - New TF FS - Server Client - Active - TF Detail Only 2.gif



Total frame time: (31, 2230)
File: Perspective 8.1.5 speed increase comparison - New TF FS - InActive - TF Detail Only.gif

Total frame time: (35, 2340)
File: Perspective 8.1.5 speed increase comparison - New TF FS - InActive - TF Detail Only 2.gif

Total frame time: (35, 2900)
File: Perspective 8.1.5 speed increase comparison - New TF FS - Server Client - InActive - TF Detail Only 1.gif

Total frame time: (39, 2750)
File: Perspective 8.1.5 speed increase comparison - New TF FS - Server Client - InActive - TF Detail Only 2.gif

This is the python code I used to calc the total frame times (stolen mostly from here [Get frames per second of a gif in python? - Stack Overflow]) For those wanting to actually use this, I had to install the ‘pillow’ library instead of the ‘PIL’ library, and I ran it in PyCharm with Python 3.9:

import os
from PIL import Image
import tkinter as tk
from tkinter import filedialog

def choose_file():
    root = tk.Tk()
    root.withdraw()
    file_path = filedialog.askopenfilename(filetypes=[('GIF', '*.gif')])
    return file_path

def get_frame_time(PIL_Image_object, startFrame=None, endFrame=None):
    """ Returns the total time of the of a PIL Image object """
    if startFrame is None:
        startFrame = 0
    if endFrame is None:
        endFrame = -1

    PIL_Image_object.seek(startFrame)
    nextFrame = 0
    frames = duration = 0
    while endFrame == -1 or nextFrame <= endFrame:
        try:
            frames += 1
            this_duration = PIL_Image_object.info['duration']
            duration += this_duration
            PIL_Image_object.seek(PIL_Image_object.tell() + 1)
            nextFrame = PIL_Image_object.tell()
        except EOFError:
            return frames, duration
    return frames, duration

def get_avg_fps(PIL_Image_object):
    """ Returns the average framerate of a PIL Image object """
    PIL_Image_object.seek(0)
    frames = duration = 0
    while True:
        try:
            frames += 1
            duration += PIL_Image_object.info['duration']
            PIL_Image_object.seek(PIL_Image_object.tell() + 1)
        except EOFError:
            return frames / duration * 1000
    return None

def main():
    filePath = choose_file()
    img_obj = Image.open(filePath)
    print(f"Total frame time: {get_frame_time(img_obj)}")
    print(f"File: {filePath.split('/')[-1]}")

if __name__ == '__main__':
    main()
1 Like

Testing with another page yields 2.76x the load time when this is turned on:

Total frame time: (64, 14240)
File: Perspective 8.1.5 speed increase comparison - L4FilterSkid- Server Client - Active 1.gif

Total frame time: (78, 5160)
File: Perspective 8.1.5 speed increase comparison - L4FilterSkid- Server Client - InActive 1.gif
1 Like

I have the same experience with the load ahead.

Without the load ahead, the page immediate shows the embedded views’ outline and progressively loads the detail, layout (we moved components inside based on config) and the values come in.

With the load ahead, we see the loading iconimage , for a few seconds. Once the icon disappears, the page loads very quickly to its final state.

While I didn’t take exact times, the old progressive loading actually feels faster as you can see stuff happening.

1 Like

The same happens for me. This method is good for nested embedded views and not the first-level embedded views.
May it is better to load all embedded as new resources in the startup of the project like components.

Well rats, that is disappointing.

It’s interesting to hear that some people like the “progressive” loading - I’ve been under the strong impression that folks dislike this, because until the loading is done the view isn’t really usable. In our tests, the browser layout thrash caused by the progressive loading slows down the overall process by a considerable margin.

Ok, here’s what I really need: I need your projects/views so that I can analyze what you’re observing. However, for things to be really useful, I need them in a somewhat self-contained / sanitized manner so that they’re not highly dependent on your environment. I don’t need everything - just one set of views that demonstrates faster loading with progressive loading is sufficient.

I’ve learned that when designing a view that is reasonably complex, is to compute as much state from arguments in a single thread in an atomic fashion along with delegating computed values to view properties as parameters to property bindings to utilize auto-refresh and auto-update functionalities where needed.

I’ve always wanted a good way to be able to set properties and component configuration before the view is loaded into the client, so that all the view properties for bindings and component configuration (where the value will not change after the value is assigned) could utilize this kind of pre-loading optimization as well as preventing aforementioned “layout thrashing” for most use cases, among other side-effects.

A fundamental constraint of perspective views is the nature of how components are dynamically configured in the same way that component display/input values are dynamically updated. The property binding model is wonderful for live values, but less so for updating view configuration in practice.

1 Like

This is slightly off topic, but an interesting idea. I'll have to think about how this might be done. The state of what views are mounted is driven by the frontend, not the backend. So, in order to do this, it would involve a round-trip for each view, as the view is started on the front-end but then is coordinating with / waiting for state to be initialized on the backend. It would inevitably be slower in some respects, but like you said, if the view were to appear with all of its configuration state in-tact from the first render, it would further eliminate thrash.

2 Likes

Right - because views are first loaded in the Perspective client, the client would have send a message out to the gateway to execute the script and wait on the response to update state before proceeding. I see how that would slow some things down, but for most cases that is happening anyway as property bindings are evaluated / onStartup scripts are being executed. If the implementation allowed for the server to hand over initial state of a view before mounting any components, that would be ideal.

1 Like

I’m sure this would be tricky to pull off, but it would be convenient for view designers to simply have a new checkbox in binding options to mark a binding as “required initial configuration”. Then, these bindings must produce a value before view loading may continue. Then, after these bindings have provided their initial value, there would be a forced property sync to the frontend before the view would actually mount. :thinking: this is an intriguing idea.

4 Likes

I am a big fan of this, and it would integrate very nicely with existing views.

Can you explain why embedded view can’t have same behavior like native perspective component?
They load very fast even for complex object like table or trend.
For native components, they also have binding which is calc in server side after component loaded in view. So why we can’t have same method for embedded view like component?

Hi Carl,

Please look at this please took at this pages. Both clients and I prefer the 8.1.4 loading delay compare to the new preloading method in 8.1.5. To me the overall loading page in 8.1.5 is much slower.
Both use the same system and client and server on the same PC so there is no network delay.

8.1.4:
8_1_4

8.1.5:

8_1_5

1 Like

Hi Carl,

I have attached a project demonstrating the issue. It is just a load of embedded views in a coordinate container, in a tab container so you can easily switch view to see the effect. No scripts, no tags, just a label binding to its own name.

This is way busier than a typical plant view, but it representative. Our mimic pages suffer from the loading delay. These pages are generally a background SVG with the vessels and piping, with the device symbols as embedded views placed in their correct location.

Hope this can help.

Regards,
Deon

TestLoad_2021-04-09_0823.zip (69.4 KB)

1 Like

Ok, so, first of all let's be clear that there is no difference between embedded views and any other views when it comes to loading and lifecycle. All views are just views, and must be loaded.

The comparison you're trying to make doesn't really make any sense. They aren't really comparable things, and they are not really separable. I'm not sure what you mean by saying that components "load very fast" as compared to views - loading the view is loading the components inside that view. A view "loading" is the process of taking the configuration of the view that is saved in the project, and turning it into actual react component instances that can be rendered, and trees of properties that are synchronizing with the back-end.