Announcement

Collapse
No announcement yet.
X
  • Filter
  • Time
Clear All
new posts

    js parsing performance

    SNAPSHOT_v13.1d_2023-11-01/Enterprise Deployment

    Chrome on MacOS

    Hello, when loading my largest (as number of screens) application, the browser takes a bit too much time in parsing the JavaScript code.
    Before I start adopting a different loading strategy from the current one (where I have a single assembly), I was trying to understand where most of the time is spent.
    I should note that I'm not an expert in this field.
    However, it seems that screens composed of SplitPanes are among those that take the most time. In particular, it appears that the initWidget of my SplitPane takes an average of 60ms.

    By running a test with the following code in the showcase:
    Code:
    isc.defineClass("MySplitPane", "SplitPane").addProperties({
        autoDraw: false,
        initWidget: function () {
            var startTime = window.performance.now();
            this.Super("initWidget", arguments);
            var duration = window.performance.now() - startTime;
            totalDuration += duration;
            isc.logEcho("SplitPane initWidget execution time: " + duration + " - total: " + totalDuration)
        }
    })
    
    var numIter = 30;
    var timeout = 100;
    for (var index = 1; index <= numIter; index++) {
        isc.Timer.setTimeout("isc.MySplitPane.create()", timeout * index * 3)
    }
    I notice a significant variance in execution times. Is this normal?
    The same code in my application is significantly slower, although not as slow as the actual code being parsed during loading.

    DynamicForms also seem to take a considerable amount of time to load.
    With the following code:
    Code:
    var totalDuration = 0;
    isc.defineClass("MyDynamicForm", "DynamicForm").addProperties({
        autoDraw: false,
        numItems: 1,
        initWidget: function () {
            var items = [];
            for (var index = this.numItems - 1; index >= 0; index--) {
                items.add({name: "field" + index, type: "float"})
            }
            var startTime = window.performance.now();
            this.Super("initWidget", arguments);
            this.setItems(items);
            var duration = window.performance.now() - startTime;
            totalDuration += duration;
            isc.logEcho(this.numItems + " items - DynamicForm initWidget execution time: " + duration + " - total: " + totalDuration)
        }
    })
    
    var numIter = 30;
    var timeout = 100;
    for (var index = 1; index <= numIter; index++) {
        isc.Timer.setTimeout("isc.MyDynamicForm.create({numItems: " + index + "})", timeout * index * 3)
    }
    I've observed the same significant variance as I see for SplitPane, and I don't see a correlation with the number of fields.
    This code also performs more slowly in my application compared to the showcase.
    I don't know if this could mean that there's some problem in my code.

    While I'm still trying to clarify my thoughts, do you have any advice?
    Am I perhaps wasting my time, and would it be better to split the JavaScript loading so that it's loaded only when needed?

    Edit: I just noticed that if I run those snippets in one of my smallest applications, they are even faster than in the showcase. Is it possible that the larger number of screens have this effect on performance?
    Last edited by claudiobosticco; 7 Nov 2023, 09:05.

    #2
    We're seeing far lower numbers, which may be because debug tools are enabled. Still, some small optimizations are probably still possible.

    However, it sounds like you have a very simple & straightforward major optimization you can do - it sounds like you are create()ing, and perhaps even drawing, all of your screens when the application first loads. There's no need to do that - you can just create/draw the screens when they are accessed. More detail here:

    https://smartclient.com/smartclient-...rtArchitecture

    Note that the JS parser per se is extremely fast. The core SmartClient runtime consists of megabytes of JavaScript source code, and the entire thing is parsed, with our classes and everything set up, in about 100ms. That's on an older laptop.

    Comment


      #3
      Thanks for your reply. Actually I create (almost) all screens (but not drawing).
      Before I didn't notice a big delay, but now this bigger application is actually a merge of other applications.
      I thought only of possibly segmenting (using the FileLoader) it, but you're right that I can also avoid the creation upfront. Thanks for pointing that out!

      Comment


        #4
        No problem.

        Note that we actually strongly recommend against "segmenting" in the sense of loading the JavaScript source code for screens only when they are accessed. This isn't worth doing because you can deliver dozens of screens in a single, compressed, small, cacheable JavaScript file. So long as you do not create() or draw() the components until they are needed, the parsing is nearly instantaneous, and the memory usage is very light (all that is created is a few JavaScript Function objects).

        When people start trying to load the JavaScript code for individual screens on demand, they end up with a bunch of complexity (synchronous operations become asynchronous, inter-screen dependencies like helper methods and helper classes need to be more intensively managed, etc) and very often end up actually degrading performance (there are more network requests for smaller files, reducing the effectiveness of compression, and caching / CDN / edge network delivery is often not used, plus end users are waiting for a network turnaround in a place where it could be handled instantaneously if the code were loaded in advance).

        So, definitely defer the create() calls and of course the draw() calls, but don't start down the path of trying to load the actual JavaScript code on demand. There's nothing good in that direction.

        Comment


          #5
          Hello, thanks for the clarification. I can confirm a huge improvement with just defering the create() calls. Also in memory consumption of the Chrome tab.

          Do you think it's worth postponing defineClasses too or not?

          Comment


            #6
            Probably not, unless you have hundreds of classes, or if it's trivially easy to defer the definitions.

            If you attempt to defer class definitions, watch out for things like other code calling class methods, or calling isc.isA.SomeClass().

            Comment


              #7
              After achieving great improvement with the deferred creation of objects, I am now wondering if delaying the loading of the data sources, which currently seem to have the most significant impact on performance, would be something worth trying or you think it isn't.

              Currently I use the pattern described in the doc for jsp environments, so I have (more than one) jsps to include them:

              Code:
              <%@taglib uri="/WEB-INF/iscTaglib.xml" prefix="isomorphic" %>
              <%@taglib uri="/WEB-INF/fmt.tld" prefix="fmt" %>
              
              <script type="text/javascript" charset="UTF-8">
              <isomorphic:loadDS ID="myDS"/>
              ...
              </script>

              Comment


                #8
                Probably not worth it, but with some caveats:

                1) if you have a very clear boundary in your app, such as a bunch of DataSources that are only needed for an application module that most users never visit (an admin area, for example), then it could be worth deferring that

                2) if you are not using Declarative Security at all, then DataSource definitions are actually always the same for every user, so you can just capture them as a plain .js file and load that.
                This removes the server-side processing part but has no impact on the client-side part.

                However, note also that if you looking at the server-side processing costs, make sure you load the app at least 3 times and look at the load time on the 3rd and later loads. There is a lot of caching/priming /JIT stuff that happens on the first request, and in some environments, things are as much as 10x faster on subsequent loads than on the first load.

                Note also that even if you are using Declarative Security, such that DataSource definitions differ for different users with different roles, you can of course pre-generate and cache all combinations of roles as JS files and load those. However, these kinds of techniques are for 100k+ concurrent user loads, or trying to eke out the last few percent of performance from dated hardware.

                3) if you do pre-generation as in #2 above, you can edit the DataSources file (which is now just a plain JS file) so that you surround certain isc.DataSources.create() calls in functions, which you then call when you need them. This allows you to defer creation of the DataSources while still loading all the DataSources in a single file up front. This results in better compression, and can be less complicated because the call to load DataSources is now synchronous rather than an asynchronous network call.

                4) finally, we're assuming you're using the approach suggested in the QuickStart Guide, of having a plain HTML login page (no SmartClient) which loads SmartClient & application resources in the background while the user is going through the login process. There is a further technique, which is to also go through all the component instantiation and even rendering while hidden. Then when login completes you just call show() and the UI instantaneously appears.

                This is so fast it's almost disorienting - the end-user experience is that they click the login button and the app is just suddenly there. However, it can only be applied if the first screen shown doesn't involve resources that require authentication or are user-role-specific, and only if you don't mind some information leakage (that is, the browser is loading the code for the application before the user is logged in).

                Comment

                Working...
                X