Launch Time Optimization
This topic presents a case study of improving an application to reduce its launch time. It describes the application launch process, techniques for reducing application loading time, and best practices for writing more efficient JavaScript.
Applications that offer a good user experience (UX) attract more users. Users who have positive impressions of an application return to it and are more likely to recommend it to others. If you monetize the application, this can result in greater profit.
Application launch time is the total time from the moment when the user clicks the application icon until the application is ready to use.
Improving the application launch time improves the user experience. Because application loading is the first experience the user gets of your application, a long loading time can leave them with a negative impression.
Therefore, it is worthwhile to prioritize reducing application launch time.
User satisfaction drastically decreases as the application launch time increases, as shown in the following figure based on a user survey. Using this information, a target launch time of around 5-7 seconds satisfies most users.
Application Launch Process
The application launch process can be divided into 5 stages. Some stages are controlled by the system, but most stages are dependent on application implementation, providing opportunities for performance improvement.
This case study is based on an application running on a 2015 Samsung Smart TV. Video was captured while launching the application on the TV, enabling the duration of each application launch stage to be measured with an accuracy of one video frame.
The following figure illustrates the application launch stages, and the time spent at each stage for the case study application.
The following table describes each stage in detail.
Stage | Action | Screen | Time | Description |
---|---|---|---|---|
1 | Application icon click | 1.086s | Time between clicking the application icon to when the Smart Hub "Apps" panel disappears. | |
2 | Initial loading screen | 4.979s | Time between loading the home page ("index.html") and calling the window.onload() method.
|
|
3 | Black screen | 0.837s | Time a transition screen is shown before calling the window.onload() method (for Samsung Legacy Platform applications only).
|
|
4 | Splash image | 0.570s | Time until the splash image appears. The splash image is the first screen shown in the application after calling the window.onload() method. It is usually shown during authentication or while content is loading.
|
|
5 | Home page | 0.348s | Time taken to render the home page, fill it with content, and enable user input. |
Table 1. Application launch stages
Optimizing Launch Logic
This section assumes that the case study application utilizes a framework. The framework contains modules responsible for activities, such as sending AJAX requests to the backend server, application authorization in the backend side, and modules responsible for the UI and logic.
The case study application loading process is illustrated in the following figure.
- The application logic is dependent on the framework setup. In stages 2 and 3, load and initialize the framework.
- After the framework setup is complete, start authorization at the backend server with the application logic.
- If authorization succeeds, send AJAX requests to the backend server to fetch application data, such as a list of recommended movies to display on the application home page.
AJAX requests are dependent on the authorization step, because they must contain an authorization token. - Wait for the data needed to render the home page, and render it.
This model is not optimized because the entire framework must be loaded and initialized before the application can start the authorization and content loading processes.
A possible approach to speeding up the launch process is by rebuilding the application in the following way.
- Divide the framework setup into 2 phases:
- In the first phase, load the modules responsible for application authorization.
- In the second phase, initialize the rest of the framework.
- Once the first phase is loaded, perform authorization immediately. It is only dependent on the XHR object created in Stage 1 during Web runtime setup. Multiple AJAX requests are merged into 1 request to avoid unnecessary delays. The AJAX request is dependent on the authorization step for the authorization token.
- When the framework is fully initialized, begin performing the basic home page setup without waiting for the AJAX request response.
- Stage 3 is eliminated completely by migrating the application from Samsung Legacy Platform to Tizen.
- Padding time is needed to synchronize the application logic and AJAX requests. The application logic requires AJAX request data to render the home page, but as the requests are asynchronous, implement mechanisms to coordinate the modules, such as events, callbacks, and promises.
- Resume the application logic after receiving the response from the server.
- Load other parts of application logic, such as required scripts and data, on demand. In this way, they do not delay the launch process.
This flow is more optimized, as many tasks are performed in parallel. Initially, the application loads only the modules which are necessary for authorization and starts authorization immediately along with other operations, such as loading the rest of the framework, the UI, resources, and content. Any tasks not required for displaying the home page are postponed to start after the launch process. Consequently, application launch time is much shorter than before.
Improving Launch Time
The following table lists optimization strategies that can improve application launch time, and the application launch stages they affect.
Optimization | Stage | ||||
---|---|---|---|---|---|
1 | 2 | 3 | 4 | 5 | |
Migrating application to Tizen | + | + | |||
Enabling prelaunch | + | + | + | + | |
Minimizing home page code | + | + | |||
Loading JS files asynchronously | + | + | |||
Sending AJAX requests promptly | + | + | |||
Parallelizing AJAX requests | |||||
Concatenating AJAX requests | + | + | + | ||
Delaying platform and Tizen API calls | + | + | |||
Caching API output | + | + | + | ||
Using jQuery 2.x or higher | + | + | + | ||
Loading static resources locally | + | + | |||
Minimizing and concatenating code | + | + | |||
Removing obsolete code | + | + | |||
Using sprites | + | + | + | ||
Using pure CSS | + | + | + | ||
Enforcing GPU acceleration | + |
Table 2. Launch stages affected by optimizations
Migrating Application to Tizen
- Stages affected: 1 and 3
Tizen is more efficient than Samsung Legacy Platform. Migrating the application from Samsung Legacy Platform to Tizen can improve its performance significantly.
Enabling Prelaunch
- Stages affected: 1, 2, 4, and 5
TV Web applications in Tizen support prelaunching. Applications can load in the background when the Samsung TV is switched on. When the user launches the application, it loads more quickly because it has been prelaunched.
For more information, see Prelaunching Applications.
Minimizing Home Page Code
- Stages affected: 2 and 5
Application launch time can be improved by loading and parsing less code at launch. When the application launches, load only the HTML, JS, and CSS code necessary to show the home page. Other code can be loaded on demand using a library, such as require.js
, to avoid building and parsing the DOM before it is needed.
Loading JavaScript Files Asynchronously
- Stages affected: 2 and 4
When loading external JavaScript files normally, the HTML parser stops while the JavaScript files are downloaded and executed.
To avoid the pause in HTML parsing during download, you can use the defer
and async
attributes:
<script src='js/main.js' defer></script>
<script src='js/scripts.js' async></script>
Using the defer
attribute downloads the JavaScript file in parallel while parsing HTML. The JavaScript code executes only when all HTML parsing is complete. Scripts using the defer
attribute are guaranteed to execute in the same order that they appear in the document.
Using the async
attribute downloads the JavaScript file in parallel while parsing HTML, but when the download completes, the HTML parser pauses to execute the script.
The defer
and async
attributes can be used only for scripts implemented with the script
element and src
attribute.
For more information, see the HTML <script> element.
Handling AJAX Requests
You can improve launch time by optimizing AJAX request usage:
- Sending AJAX requests promptly
- Stages affected: 2 and 4
Most applications load their content from remote servers and utilize a framework that helps manage the requests. However, loading and executing the framework code can take a lot of time. To speed up application launch, send the AJAX requests needed to display the home page as soon as possible.
You can send a simple XHR request at the beginning of the "index.html" file, before loading the framework. XHR requests are asynchronous and do not block any other threads. Cache the response in a global object, so the application framework can use the data immediately to display the home page without sending the request again.
- Stages affected: 2 and 4
- Parallelizing AJAX requests
Send AJAX requests in parallel if they are not dependent on responses from prior requests:- Before:
Each request is executed after receiving a response from the previous one:sendRequest1 onSuccess: sendRequest2 onSuccess: sendRequest3 onSuccess: sendRequest4 onSuccess: sendRequest5
- After:
The first request authorizes the application. After receiving the response, the other requests are executed asynchronously:sendRequest1 onSuccess: sendRequest2 sendRequest3 sendRequest4 sendRequest5
- Before:
- Concatenating AJAX requests
-
Stages affected: 2, 4, and 5
Limit the number of AJAX requests by merging several requests, if possible. The fewer requests there are, the less time it takes to process all of them.For example, consider an application that sends 3 requests to display the home page, such as for application authorization, a list of recommended movies, and a list of the latest movies. Verify whether it is possible to modify the requests and backend in such a way that the response for the authorization request contains the recommended and latest movies as well.
You can also use this strategy throughout the application to reduce the loading time of other scenes.
-
Delaying Platform and Tizen API Calls
- Stages affected: 2 and 4
Many applications make early calls to Tizen and platform APIs for static information, such as the DUID or model code. Because API initialization is "lazy" (the API is initialized only when it is first needed), the first call to each API module takes time. Postpone API calls until the application is fully started and ready to use, if possible.
Caching API Output
- Stages affected: 2, 4, and 5
Instead of querying the API each time, cache the static output from Product and Tizen APIs in JavaScript variables.
Consider the following scenarios:
- The total duration of video content is constant during video playback. You can retrieve it once and store it in a JavaScript variable instead of querying the API each time it is needed.
- TV specification parameters, such as the DUID or model code, never change. You can retrieve the parameters using the API the first time the application is launched and save it in
localStorage
. Retrieving data fromlocalStorage
is quicker than querying the API.
Using jQuery 2.x or Higher
- Stages affected: 2, 4, and 5
Newer versions of jQuery are smaller and faster. You can also consider using a custom jQuery build.
Loading Static Resources Locally
- Stages affected: 2 and 4
Load static application resources directly from the local file system, by including all application source code, external libraries, and static UI elements, such as CSS and images, within the Tizen package:
- Before:
<link rel='stylesheet' href='https://maxcdn.bootstrapcdn.com/bootstrap/3.3.5/css/bootstrap.min.css'> <link rel='stylesheet' href='https://example.com/myapp/css/styles.min.css'> <script src='http://code.jquery.com/jquery-2.0.0.min.js'></script> <script src='https://maxcdn.bootstrapcdn.com/bootstrap/3.3.5/js/bootstrap.min.js'></script>
- After:
<link rel='stylesheet' href='css/bootstrap.min.css'> <link rel='stylesheet' href='css/styles.min.css'> <script src='js/jquery-2.0.0.min.js'></script> <script src='js/bootstrap.min.js'></script>
This not only decreases loading time, but can also improve application security and eliminate some application errors caused by network issues.
Minimizing and Concatenating Code
- Stages affected: 2 and 4
The application loads faster when it makes fewer requests to the file system. If you do not want to use a lazy loading mechanism to load parts of the application on demand, concatenate and minimize your source code, especially JavaScript and CSS files:
- Before:
<!DOCTYPE HTML> <html lang='en'> <head> <meta name='viewport' content='width=1280, user-scalable=no'> <meta http-equiv='Content-Type' content='text/html; charset=utf-8'> <title>My application</title> <link rel='stylesheet' type='text/css' href='styles/main.css'> <link rel='stylesheet' type='text/css' href='styles/list.css'> <link rel='stylesheet' type='text/css' href='styles/player.css'> <link rel='stylesheet' type='text/css' href='styles/login.css'> <link rel='stylesheet' type='text/css' href='styles/account.css'> <script src='js/main.js'></script> <script src='js/list.js'></script> <script src='js/player.js'></script> <script src='js/login.js'></script> <script src='js/account.js'></script> </head> <body> <div id='Menu'></div> <div id='mainContainer'> <div id='helpbar'></div> </body> </html>
- After
<!DOCTYPE HTML> <html lang='en'> <head> <meta name='viewport' content='width=1280, user-scalable=no'> <meta http-equiv='Content-Type' content='text/html; charset=utf-8'> <title>My application</title> <link rel='stylesheet' type='text/css' href='styles/all_styles.css'/> <script src='js/all_scripts.js' defer></script> </head> <body> <div id='Menu'></div> <div id='mainContainer'> <div id='helpbar'></div> </body> </html>
In some situations, it can be better to concatenate your code into 2 files instead of 1:
- If the code must be executed as soon as possible, put it into a file that is loaded with the
async
attribute. - If the code requires the DOM to be ready before execution, put it in another file that is loaded with the
defer
attribute.
Make sure that the code loads in the correct order to satisfy its dependencies.
Removing Obsolete Code
- Stages affected: 2 and 4
If the application loads any unused source code, whether it is JavaScript, CSS, or HTML, the browser takes time to parse it, build the DOM, and search the HTML code for matching CSS rules. Keep your code clean and up to date to avoid unnecessary performance issues caused by obsolete or redundant code.
For example, when the window.onload()
method is called, calling the unregisterKey()
method of the TVInputDevice API is unnecessary because no keys are registered other than the default "Left", "Up", "Right", "Down", "Enter", and "Back" keys.
Using Sprites
- Stages affected: 2, 4, and 5
If your application loads many small graphics, such as icons, button elements, and backgrounds, consider using CSS sprites. CSS sprites help images load faster by making fewer requests to the file system, which improves overall application performance.
You can merge all of the icons or background images into 1 large image and use CSS to set the positions for each element. Consider the following mouse hover implementation:
- Before:
This code is not optimal, as the browser must make 2 requests to the file system instead of 1. Additionally, the first time the user hovers on the button, its background will flicker, as some time is needed to request the new image and show it on the screen.#myButton { width: 300px; height: 100px; background: url('img/button-normal.png') 0px 0px no-repeat; } #myButton:hover { background: url('img/button-hover.png') 0px 0px no-repeat; }
- After:
This code is more optimal because it loads only 1 image (which takes less time to load, even though its size is bigger). Mouse hover changes only the background image position, so there is no flickering when switching between the states.#myButton { width: 300px; height: 100px; background: url('img/button-sprite.png') 0px 0px no-repeat; } #myButton:hover { background: url('img/button-sprite.png') 0px -100px no-repeat; }
Using Pure CSS
- Stages affected: 2, 4, and 5
Tizen supports CSS3, which allows you to create many graphic effects without using image files. Pure CSS renders faster and takes less time to calculate its properties, which is especially valuable when animating CSS elements.
The following code creates a button using only pure CSS:
button {
width: 180px;
height: 62px;
line-height: 62px;
font-size: 24px;
color: #fff;
text-align: center;
text-shadow: 1px 0 1px rgba(0, 0, 0, .75);
background: -webkit-linear-gradient(
top,
#1e5799 0%,
#2989d8 50%,
#207cca 51%,
#7db9e8 100%
);
border: solid 2px #fff;
border-radius: 12px;
box-shadow: 0px 0px 15px 5px rgba(255, 255, 190, .75);
}
Enforcing GPU Acceleration
- Stage affected: 5
Normally, the browser applies GPU acceleration when there is some indication that a DOM element can benefit from it.
CSS3 allows you to force the browser to render elements with GPU acceleration. One of the simplest methods is setting a style indicating 3D transform, even when no real 3D transformation is applied:
.acceleratedElem {
-webkit-transform: translateZ(0);
}
In general, 3D transformations in CSS force the browser to use the GPU.
The following declarations can reduce flickering during CSS transforms or animations:
.acceleratedElem {
-webkit-backface-visibility: hidden;
-webkit-perspective: 1000;
}
For more information, see An Introduction to CSS 3-D Transforms and Increase Site Performance With Hardware-Accelerated CSS.
Optimizing JavaScript Code
Follow a reliable and consistent coding standard and style for JavaScript, as it makes the code easier to write, easier to read, improves its maintainability, and makes it less error-prone.
The following section presents JavaScript best practices that are frequently used in the Web community.
Caching DOM Element References
Searching the DOM tree is a resource-hungry operation. If there are DOM elements on which you perform operations frequently, store them in JavaScript variables to avoid repetitive DOM traversal:
-
Prefer:
var elem1 = document.getElementById('elem1'); elem1.innerHTML = 'Lorem ipsum dolor'; elem1.classList.remove('class1'); elem1.classList.add('class2'); var $elem2 = $('#elem2'); $elem2.html('Lorem ipsum dolor sit amet'); $elem2.addClass('class3');
-
Avoid:
document.getElementById('elem1').innerHTML = 'Lorem ipsum dolor'; document.getElementById('elem1').classList.remove('class1'); document.getElementById('elem1').classList.add('class2'); $('#elem2').html('Lorem ipsum dolor sit amet'); $('#elem2').addClass('class3');
It can be helpful to store references to frequently-used DOM elements in a global object. For example:
var domElementsCache = {
elDocument: window.document,
elHtml: window.document.documentElement,
elBody: window.document.body,
elHead: window.document.head,
elTitle: window.document.getElementById('title')
};
domElementsCache.elTitle.innerHtml('My title');
Avoiding Slow jQuery Selectors
jQuery selectors work slower than native ones. If application performance is critical, consider replacing jQuery selectors, for example:
- ID selector:
//jQuery var elem = $('#myElem'); //native JS equivalent var elem = document.querySelector('#myElem');
- Class selector:
//jQuery var elems = $('.class1'); //Native JS equivalent var elems = document.querySelectorAll('.class1');
- TagName selector:
//jQuery var elems = $('span'); //Native JS equivalent var elems = document.querySelectorAll('span');
$.find()
://jQuery var elems = $el.find('.target1, .target2, .target3'); //Native JS equivalent var elems = el.querySelectorAll('.target1, .target2, .target3');
Using Native Methods
Native JavaScript methods are faster than wrappers from different libraries. In some cases, it can be helpful to use them instead of library methods:
var myArray = [1, 2, 3];
//Underscore
_.each(myArray, function(val) {
console.log(val);
});
//jQuery
$.each(myArray, function(val) {
console.log(val);
});
//Native JS equivalent
for (var i = 0; i < myArray.length; i++) {
console.log(myArray[i]);
}
Removing Console Logging
Extensive logging decreases application performance, because each console.log()
method call blocks the JavaScript thread in the browser. Other logging methods, such as the console.warn()
, console.info()
, console.error()
methods, behave similarly. To improve application performance, remove log methods in the final build.
You can use a logger utility to easily switch logging on and off. Open source libraries, such as Woodman, are available, or you can write your own logger, such as the following example:
// Global object for storing application configuration
var applicationConfig = {
debugMode: true //enables logs in application
};
// Logger object factory
var Logger = function() {
var logger = {};
if (applicationConfig.debugMode === true) {
logger = {
log: function() {
var args = Array.prototype.slice.call(arguments, 0);
console.log(args);
},
info: function() {
var args = Array.prototype.slice.call(arguments, 0);
console.info(args);
},
warn: function() {
var args = Array.prototype.slice.call(arguments, 0);
console.warn(args);
},
error: function() {
var args = Array.prototype.slice.call(arguments, 0);
console.error(args);
}
};
} else {
logger = {
log: function() {},
info: function() {},
warn: function() {},
error: function() {}
};
}
return logger;
};
// USAGE:
// For development and debugging
var applicationConfig = {
debugMode: true
};
var myLog = new Logger(); // Initializes logger
myLog.log('test'); // Outputs 'test' to console
//For production
var applicationConfig = {
debugMode: false
};
var myLog = new Logger(); // Initializes logger
myLog.log('test'); // Does nothing
console.log()
or any other native function.Overriding native objects is a bad practice in JavaScript, as it can lead to unexpected behavior and introduce defects that are very hard to debug.
Optimization Results
The principles described in this guide were applied to several Samsung TV applications, as listed in the following table.
Optimization | Application | |||||
---|---|---|---|---|---|---|
A | B | C | D | E | F | |
Migrating application to Tizen | + | + | + | + | + | + |
Enabling prelaunch | + | + | + | + | + | + |
Minimizing home page code | + | + | + | + | + | + |
Loading JS files asynchronously | + | + | + | + | + | + |
Sending AJAX requests promptly | + | + | + | + | ||
Parallelizing AJAX requests | + | + | + | |||
Concatenating AJAX requests | + | + | ||||
Delaying platform and Tizen API calls | + | + | + | + | + | |
Caching API output | + | + | + | + | ||
Using jQuery 2.x or higher | + | + | + | + | ||
Loading static resources locally | ||||||
Minimizing and concatenating code | + | + | + | + | + | + |
Removing obsolete code | + | + | + | |||
Using sprites | + | + | + | + | + | + |
Using pure CSS | + | |||||
Enforcing GPU acceleration | + | |||||
Caching DOM element references | + | + | + | + | + | + |
Avoiding slow jQuery selectors | + | + | + | |||
Using native methods | + | + | ||||
Removing console logging | + | + | + | + | + |
Table 3. Optimizations applied to Samsung TV applications
The following table lists the launch time optimization results.
Application | Before Optimization |
After Optimization |
Reduction | Reduction (%) |
---|---|---|---|---|
A | 7.234 s | 4.422 s | 2.812 s | 38.87% |
B | 6.972 s | 4.642 s | 2.330 s | 33.42% |
C | 4.160 s | 3.140 s | 1.020 s | 24.52% |
D | 16.512 s | 4.011 s | 12.501 s | 75.71% |
E | 4.090 s | 3.153 s | 0.937 s | 22.91% |
F | 17.853 s | 7.462 s | 10.391 s | 58.20% |
Average Reduction: | 4.999 s | 42.27% |
Table 4. Results of application launch time optimization
The average reduction in application launch time was almost 5 seconds, a 42% improvement. This result suggests that it is worthwhile to optimize applications, wherever possible.