Lazy Loading for Forms?

I haven’t implemented this yet, so I have no clone link, but my question is more general.

Does anvil support lazy loading of forms? This way, I would be able to make my site less slow, since it’s pretty large. I just want anvil to only load a form when its called.

Anvil already only loads the module a form is in when that module is referenced. But if you import a Form that module has to get loaded. And those imports tend to cascade throughout all the forms, so that all the modules end up getting loaded when the app starts.

To not load a form until it’s called, you’d need to move all your imports into the functions where they were used, similar to what’s done on the server side for expensive imports.

None of that works if you’re using the routing module from Anvil Extras, since all the forms must be loaded to process the route information.

6 Likes

I drastically reduced my startup time with the routing module from Anvil Extras this way:

  • split each form into two forms, one lightweight that contains only the routing info and one heavyweight that does all the work.
  • Importing the lightweight form in the @routing.main_router module is then fast
  • the lightweight form lazily imports the heavyweigh form

Example:

@routing.route(ACCOUNTS_URL_PATH, title='Accounts')
class Accounts(AccountsTemplate):
  def __init__(self, **properties):
    # lazy import of the heavyweight form
    from ..AccountsComponent import AccountsComponent
    self.add_component(AccountsComponent())
7 Likes

Love that! I’ll have to work my way through my large main app and do that.

1 Like

It depends on what you mean by loading a form.

When the app starts, all the code is loaded, forms are code, so all the forms are loaded when the app is loaded. If you have 100 forms and 5000 lines of code, when the app starts you load 100 forms and 5000 lines of code. You can’t do anything about this, that’s how Anvil works. This is the overhead of starting up the app.

Forms are also imported, and you may call that import loading a form. The client side only allows a few packages, and they are all fast to load. So avoiding the import of a form would save you very little time.

Each form does something when it starts up, you may consider that loading a form. This can be very slow, for example if you call dozens of server functions, or even one single server function that runs something very slow on the server before returning the control to the form. You have full control over this, and you can do it as lazy as you like, but it’s up to you, it’s not something that Anvil “supports”.

Lazily loading a form may mean loading the data required by each component only when that component needs it. This could be beneficial in some cases, but in most cases this will make your form slower, much slower. It’s often faster to load everything you need in one shot, even if it means loading more than you actually need, than postponing the loading and triggering many delayed server calls.

Each form is an instance of its form class. The routing of Anvil Extras caches (optionally) the form instances, which means that if you go again to the same url, the module will show the form as it was the last time it was shown, without executing or loading or initializing anything. Well, it will execute the form_show event, where you can decide whether to do nothing, reload everything, or do select checks for stale data and update only what you need to update. That’s how you make your forms lazy.

To use the routing module you need to change how forms are loaded. The routing module will load the forms for you and pass the url arguments to the form. Then the form decides what to do with those arguments, usually in the form_show event.

All my apps use the routing module, and all my forms make zero or one server call in the form_show event, so they are as fast as that server call.

2 Likes

I was not aware that it was possible to speed up client-side loading in this way. My working model was more along the lines of:

Do you agree with @stefano.menci’s characterization, @jshaffstall and @stein, or do you think he and I are missing something about how to speed up the initial load time of an Anvil app?

Out of the three types of loading:

Loading modules by transferring them from the server to the client: I don’t have any way of measuring this, so don’t know how Anvil treats code modules specifically. Assets are lazily loaded, but I’ve never heard Anvil staff comment on modules. It’d be nice to get a definitive statement from Anvil on that.

Loading modules by importing them: this absolutely happens only when you import them. So if you do it inside a function, it doesn’t happen on app load. If you do it at the module level, then it happens at app load (if that module is also imported by something that gets loaded at app load).

Loading forms by instantiating them, thus running all the code in their init functions: this happens typically when you show them. e.g. even with Anvil Extras routing it only happens when you navigate to the form.

So it’s the second form of loading (importing modules) that I’ve been talking about. The more forms you have that are imported, the more time it takes.

That’s a simplification that I haven’t found to be true. Forms import other forms and non-form modules. Across a large app (hundreds of forms), that adds time to the app startup.

I haven’t experimented with splitting those forms like @stein has, so can’t say how much of a speedup doing so might get. But I do know that my app startup has increased a noticeable amount over the years as it’s gotten larger and larger.

1 Like

All I can say is that it works.

I timed each import in the @routing_main_router to identify the slow ones (typically forms that import other libraries like plotly). Then I applied this metod, and observed that the import time became much quicker.

The time it took to run the imports in the @routing_main_router was reduced from >10 seconds to <2 seconds.

This was the bottleneck for the slow startup time for the app, and the total startup time seems to have been reduced with the same number of seconds.

Importing forms has never bothered me, I guess because I have many small apps instead of a large one. The largest has 20ish forms.

For me the slowest part at app startup is the sign in and permission check.

Thank you both for the elaboration. I don’t use plotly or any similarly large client-side imports, so I doubt there’s much to gain for my app from playing with this, but I may still experiment with the imports I do have (like datetime).

I don’t either, I just make up for it with quantity. It’s possible that Stein’s approach won’t benefit me because of that.

1 Like

It could be worth it to time the imports to see if there are any opportunities. Like @jshaffstall, I experienced that the startup time became longer as my app grew. I have around 100 forms registered with the routing module.

In case useful for someone, here is how I timed the imports:

import time
def _time_it(from_t, msg):
  now_t = time.time()
  dt = now_t-from_t
  if dt >= 0.04: # don't bother with imports that are already fast
    print(f'{msg}  = {dt}')
  return now_t
start_t = time.time()
t=time.time()

from .Form1 import Form1
t = _time_it(t, 'Form1')
from .Form2 import Form2
from .Form3 import Form3
t = _time_it(t, 'Bulk1')
...

_time_it(start_t, 'Total imports time',)
1 Like