When developing GWT (Google Web Toolkit) applications, the GWT hosted mode is an invaluable tool, as it allows for short development cycles. Whenever you change something in the GWT client code and hit the “Refresh” button it will compile the change, reload the application and show the result of my change almost immediately. In theory, that is…
As soon as the GWT client code grows in size, it takes longer and longer to refresh, to the point that it becomes unacceptable.
In this blog I’ll explore this issue, discuss its causes and what you can do about it.
Here’s a true story from a project I’m working on. From the outset we anticipated that next to the IDE the HM is where we were going to spend most of your time while working on GWT client code, since it allows for short development cycles. So we set up our development environments with all server-side code deployed on a Tomcat server, and used the HM to test and debug the GWT client code. Once the hosted mode was started, a refresh would typically take only a few seconds. For a while…
As the application grew, we kept adding new panels, widgets and remote services. We noticed the time it took the app to refresh was going up all the time. When it had grown to almost a minute, waiting for the app to refresh had become almost unbearable. Something needed to be done about it, and we started to investigate why it was so slow.
What’s taking so long: a few surprises
We inserted logging statements in the code to see what was taking so long. The first logging statement was at the very beginning of the onModuleLoad()
method in the entry-point class.
I somehow expected that most of the time would be spent loading the application, but instead the first logging statement was reached almost immediately. So all that time it took to refresh the app was actually spent inside the app itself, it was the time it took the app to startup!
Next we inserted more logging statements to see how much time it actually spent on specific parts. The first thing that surprised us was that for each remote service it took 5 seconds to create its client proxy using GWT.create()
. Since we had 5 remote services at that time, all initialized during startup, this accounted for 25 seconds of total startup time.
The remaining half was accounted for by the app’s remaining initialization code, mostly creating widgets and wiring them together with event-listeners. We were not surprised it spent half of the startup time here, although we were surprised that this actually took as long as 30+ seconds. Compared to the web mode, were the app starts in a split second, hosted mode seems to be an order of magnitude of 100 times slower. The actual difference may vary depending on the nature of the code, but the consequences remain the same: an app may start up in less than a second in web mode and at the same time take intolerably long to start up in the hosted mode.
Once we understood why a refresh took so long, we took measures to do something about it. We were able to reduce refresh time to less than 15 seconds, and we feel confident it stays like that no matter how big the application may eventually become.
Solutions
Since the application is ultimately deployed in web mode, why bother at all about its hosted mode performance?
Firstly, if its startup-time is allowed to grow continuously, it will eventually get to a point that its not acceptable in web mode either. Optimizing for a fast startup in hosted mode just means dealing with this problem early.
Secondly, the ability that the HM provides to develop GWT client code in short code-refresh-review iterations is one of GWT’s best features. It makes development more productive and more fun. Keeping the HM startup-time at an acceptable level makes the difference between a pleasant experience and a frustrating one for a developer. This should be enough to justify the extra effort!
Here’s what you can do:
#1 Reduce the number of remote services
Since each remote service adds 5 seconds to startup time, it’s an easy optimization, when you have multiple remote services, to replace them all by one. If you want to adhere to the conceptual partitioning in multiple services, you can maintain this partitioning by creating a facade service that just delegates to the original services. Its remote and async interfaces are derived from the original interfaces like this:
// Facade interface for services A and B public interface UberService extends ServiceA, ServiceB {}
// Facade async interface for services A and B public interface UberServiceAsync extends ServiceAAsync, ServiceBAsync {}
Once a UberServiceAsync instance is created in the clientcode, it can be used instead of each of the original async interfaces. This way the modification can be applied with minimal impact on the existing code.
#2 Initialize panels lazily
An app typically (but not necessarily) uses something like a DeckPanel or TabPanel to manage a set of panels that you can navigate through. This approach results in all these panels being created and initialized at startup, although initially only one (or none) of them is visible. Initialization of each panel includes all panels they may include (possibly DeckPanels too), event-listeners etcetera, all the way down the panel layout hierarchy. So without additional measures all the widgets in the application will be initialized at startup, regardless of when they are actually displayed.
In order to reduce startup time, lazy initialization of panels must be introduced, i.e. initialization of the panels must be postponed until just before the first time they are displayed. This will spread the overhead of initialization of widgets more evenly over the duration of a user session, instead of having it all at startup, and save you from the initialization overhead for all the panels that are not displayed. Implementing lazy initialization of widgets is straightforward, using an interface like this for a lazy widget:
public interface LazyWidget { public Widget createWidget(); }
Next, create a lazy version of DeckPanel (or TabPanel) that holds references to LazyWidget instead of Widget instances, and calls their createWidget()
method to initialize the corresponding widget the first time it is displayed. If you take the source of the original DeckPanel as a starting point, this is fairly straighforward, so I won’t go into details here.
Use anonymous inner classes to provide LazyWidget implementations, where the method createWidget()
contains the initialization code for the widget. So suppose a snippet of the original code looks like this:
DeckPanel p = new DeckPanel(); Widget w = new SomeWidget(); p.add(w); // Add widget to the panel
After the introduction of lazy initialization it will look like this:
LazyDeckPanel p = new LazyDeckPanel(); // Deckpanel based on lazy widgets. LazyWidget w = new LazyWidget() { public Widget createWidget() { return new SomeWidget(); } }; p.add(w); // Add lazy widget to the panel
In general there’s no need to make every panel lazily initialized. To get the biggest bang for the buck, consider what the panel hierarchy looks like, and focus your efforts on the panels which are the highest up in the hierarchy or have the most widgets beneath them.
#3 Use hosted mode detection to detect development mode
Hosted mode is development mode, meaning that when the app is in hosted mode, it is safe to assume it is running in a development environment instead of production. A call to GWT.isScript()
actually allows you to detect this, it returns false for HM. You can use this feature to switch off functionality that is useful in production, but less so during development.
The code below, for example, navigates to the “Dashboard” only when in web mode, making the “Dashboard” the app’s starting point. In hosted mode this behaviour is disabled, saving us from the overhead of initializing the components used by the dashboard at startup. This is useful, since in development we usually want to go straight to the panel we’re working on instead of the dashboard.
// When in web mode, open dashboard as the first page if (GWT.isScript()) { History.newItem("Dashboard"); }
#4 Use faster hardware
Often, though not always, cutting edge technology requires cutting edge hardware. This certainly applies to GWT development. If you follow my suggestions above, keeping hosted mode refresh-time below 15 seconds is feasable, but only when using state of the art hardware. You need to decide on what to spend money: on new hardware or on waiting.
Excellent introduction of problems that can be met and also solutions that you find to correct the shot. I will used them intensively.
I will also follow your next posts.
We are in the same boat and were about to start trying to identify what we could do speed things up. I think u just helped us big time.
thnx
Nice writeup! Thanks for taking the time to share your findings.
Just wanted to mention that we (the GWT team) are aware of the importance of making hosted mode refreshes (and, to a lesser extent, initial HM startup) as fast as possible. The current HM implementation doesn’t seem to be anywhere close to the potential lower bound on speed yet. So, we’re pretty confident that within the next few versions of GWT, hosted mode will become much faster without you necessarily having to take steps explicitly in your code to make it faster.
With that said, since your suggestions would produce better web mode performance anyway, they are good advice independently of hosted mode speed as well.
[…] Optimizing startup time for GWT hosted mode Some tips of optimizing startup time for GWT application (tags: GWT tips web2.0 ajax javascript) […]
Please help me making my project faster.Tell me from beginning how to start and how to finish.
hello it is test. WinRAR provides the full RAR and ZIP file support, can decompress CAB, GZIP, ACE and other archive formats.
roenerilqfalkugkyzbajozoupyhvzfifmfhello
Excellent advice. Thanks for the write-up.
Hi! I just came across your facade suggestion for RPC calls (#1 above) and I wondered if you were still using it today, and what results you get. I’m working with 1.7, and giving 2.0 a first try, and was curious about the possible speedup.
#4 Use faster hardware ???
Come on!!!
optimization and enhancement are two different things. what you’re explaining here can enhance the gwt start up time but it does not perform any “optimization”.
Do you have any idea why each remote service adds 5 seconds to startup time?
My web page is slow on startup in production mode. The image tabs are lazy loaded but the text tabs are not. I will try make the all tabs lazy loading and to see it the problem goes away. There is no remote service call yet.
Sorry, my web page is disneyvh.com