Reducing the launch time of the applications has always been a challenge on a low CPU and memory devices. Where low CPU doesn’t allow rich applications to launch faster, low memory restricts the developers to keep those applications always running in the background. When we try to run high-end applications on low CPU and memory devices, tuning the performance of such a system at framework level is essential to give an enriched user experience.
It was 2008 time frame when I joined a new organization which was developing a Linux-based mobile operating system using the GTK as the application framework. GTK, being designed for desktop-based Linux, was not a very good fit for the mobile devices from performance point of view, but to expedite the development process and quick reach to the market, technical architects of the organization took the decision (even before I joined the organization) to go with GTK as a development solution until they move things to OpenGL based framework. Even though hardware industry was going through a major change that time and new chipsets with high processing power and memory were making their debut to the market, the devices which were available with us (and client) were still running at just 250 MHz with RAM as low as 500 MB itself.
I joined the organization as a performance lead with the responsibility to improve launch time performance of applications up to the industry standard. The existing framework without our solution used to take approximately 30-40 seconds to launch an application (depending on the application type e.g. Phonebook, Media Player, Browser etc. and UI scheme used) and the industry standard was approximately 3-4 seconds. Hence, I was supposed to improve the application launch performance by approximately 10 folds.
After performing the regular performance benchmarking of the platform, by using time-based logging techniques, although I could suggest few of the good coding techniques to developers which helped in improving performance up to some extent, we were nowhere close to the number of 3-4 seconds. This was the time when I came up with a thought that most of the time during application launch actually goes in making the memory object by parsing layout files and initializing process memory, and if we look at it closely, we realize that code follows the exact same path (unless any configuration changes) every time user launches an application. Once we had this realization, we thought about reusing the work done by application code path during the first launch for all the subsequent launches which mean that we decided to launch an application and hibernate it (save the process context in a file) and reuse the hibernated context when user launches it subsequently, and avoid all the initial processing time which used to be taken by application during launch time.
Although we were able to draw an analogy of the process hibernation with the hibernation technique used for the complete device hibernation (e.g. laptop, desktop etc.), there were few differences between the process hibernation and full device hibernation. As full device hibernation just requires you to save the state of complete RAM (all processes together) in the non-volatile memory, process hibernation just needs to save the state of one single process individually. This brings in the challenge that, as per modular architecture of the platform, there are dependencies between the processes running on the device e.g. an application can be dependent on other application(s) to fetch some data and settings, and also it needs to interact with Window Manager and x-server kind of process to render itself on the phone screen. Once you hibernate a particular process and save it to the memory and try to relaunch it the next time, it becomes every important that you also restore the connection/interaction of the process with other dependent processes as well (other processes were not stored and they were running as per their design).
There were the couple of challenges and solutions which were supposed to be identified before we could successfully implement the solution at platform level:
- Need to stop the application at the point of hibernation to save the context
- To save the context of the application during the first launch, we needed to stop the process for couple of seconds at the point of hibernation so that we can take the memory dump of that process and store it in a file on non-volatile memory, but this can introduce a significant delay in the hibernation path (while saving the context). To overcome the issue, we forked a new process at the point of hibernation and saved the context of that new process; letting the original process continue to launch as usual for better user experience. We were making the process wait at a “while loop” at the point of hibernation.
- Need to restore the saved context in the minimum time possible so that we can take maximum advantage of hibernation mechanism.
- During the subsequent launch of the application, after restoring the context, we needed to bring the saved context out of the wait condition (“while loop” mentioned above) in the minimum time possible. To overcome the issue, we modified the saved context offline after the hibernation process in such a way that when we restore the process, it is already out of the wait condition i.e. condition on which “while loop” was waiting needed to be changed offline.
- At the point of hibernation, there should not be any open socket/open file handle/ open shared memory/ fork etc.
- We issued the guidelines to the application and framework developers to restructure their code in such a way that - at the point of hibernation there should be no open connection with the other processes i.e. all the open connections were either supposed to be closed or moved to a later stage to take the benefit of the technique.
Other than the above design challenges, we were faced with other issues like PID of the process getting occupied by some other process at the time of restoring a hibernated process, changes in the application configuration by the user e.g. changing the layout from grid view to list view and vice versa, changing/upgrade of any shared library etc. All these challenges were addressed either by issuing guidelines or taking care of the things at run-time like discarding the old hibernated file and re-hibernating the application context at the point of any change detection.
Once the solution was ready, it was indeed able to deliver us the performance which we were looking for. Even though the solution looks complicated and did require some restrictions on the coding techniques used by application developers, it was well suited for the low CPU device at that time to give us some time to develop the other solution which was based on OpenGL and fulfil the organization as well as client needs.
The solution was primarily used as an interim solution until we could develop our new OpenGL based framework and received affordable high-speed CPU (in GHz) and big size RAMs (in GBs). As the solution was able to give us the perception of how the final product will look like, it was able to solve the client and organization need to show it to the end customer during initial demos and conferences.