Software Performance [Web]

Konstantin Dinev / Tuesday, July 2, 2019

Performance is one of the most important, if not the most important, vectors that should be considered when producing software products. The adoption of your software by new users, as well as the retention of those users relies heavily on the performance that your solution has. Hence the very famous anecdote with the Google search engine, that meaningful search results must be returned to the user within three seconds of initiating the search. Going past the three second mark makes the user hit the refresh button of the browser, thinking that something went wrong. Performance is not important just for search engines, however. Users expect any software, that they use daily, to load fast and to respond fluidly to their interactions.


Software Performance Dimensions

Software performance is not a single feature that we can implement, or measurement that we can take. It’s the general behavior of the software. All features of the software we produce need to take into account how well they perform individually, and in combination. There are three main dimensions that we can measure software performance on, and to respectively try to improve it on — load time performance, run-time performance, and soft performance. When looking to improve the performance of our software, all three dimensions need to be considered, because going after no single one of them would not improve the general satisfaction of our users.

Load Time Performance

This is probably the easiest of the three dimensions for us to observe and measure. Load time performance is simply how fast our software application loads. For a desktop application, the time is measured from the user interaction triggering the software executable application, to the time the user can start interacting with the software. We call this measurement “time to interactive”. Load time performance is very important for on-boarding your new users.

Run-time Performance

Run-time performance is defined by how responsive our software is to user interactions. Run-time performance would be different for each navigation within the application, as well as each interaction with a component within it. Measuring run-time performance is done on multiple levels — individual component responsiveness, general view responsiveness, navigation between views, memory consumption. Run-time performance is important for user retention.

Soft Performance

Soft performance is the trickiest of dimensions, as it’s defined by the general usability of your software. It’s measured by observing users and evaluating how easy and intuitive it is for them to perform the general tasks your software is intended for. Soft performance on its own has plenty of dimensions, from how easy it is for your user to find and navigate to the feature they want to use, through how appealing the look and feel of your application is, to how good the error messages your software produces are, in order for your end user to understand what they did wrong.


Performance in Web

Performance in web is a complicated subject, as it does not simply depend on how the software was written and what hardware the client machine has. It’s also heavily dependent on the type of browser being used, and on the way assets and resources have been optimized. Over the past 10 years things have gotten a lot better with the end of support for Windows XP and respectively for Internet Explorer 6, 7, 8 (also mostly 9 and 10). However, Internet Explorer 11 still comes with the latest version of Windows, and some legacy applications, especially the ones that still include ActiveX modules require IE11 to run. If you think that this scenario is edge-case, you should know that there are a lot of such enterprise applications that still exist! With IE11 in mind, performance in web applications becomes even trickier.

Let’s look at the different performance aspect that we should consider for web applications, and how should we tackle them to provide our users with the most optimal performance possible. Based on the previous overview, we will look at load time performance, run-time performance, and soft performance.


Load Time in Web

Load time performance is the time it takes for your application to load, for the user request in the browser, to the time the application is interactive. There are various tools that you can use in order to measure load time performance of your web application, and in general they will give you suggestions on what you should be doing in order to improve each performance aspect the tool measures. Currently I would suggest that you use the Lighthouse Google Chrome extension to measure your application’s lighthouse score.

lighthouse performance report

The values that we should be first looking at are: First Contentful Paint, First Meaningful Paint, and Time to Interactive.

First Contentful Paint

The first contentful paint is when the first text or image is rendered on the page. The first contentful paint depends entirely on the time to download the resources required by your app and on the work the browser needs to do before it starts rendering DOM.

To decrease the time to your app’s first contentful paint, you need to minimize the size and number of resources downloaded by your app initially, and you also need to minimize the number of render-blocking external resources, like external stylesheets.

After you’ve done that, make sure you also perform:

  1. Resource caching for returning users.
  2. Minification of you text-based assets, like JavaScript files and CSS Stylesheets. Uglify.js is your friend!
  3. 1. Compression of your text-based assets. Every browser nowadays supports Brotli and/or gzip compression!
  4. Remove unused code from text-based assets. Modern JavaScript frameworks, like Angular, employ tree shaking and code splitting mechanisms to minimize the size of the payload that is being delivered to the client machine. With older code base, make sure that you remove links to old stylesheets that are no longer used, and to JavaScript files that are also obsolete.

First Meaningful Paint

The first meaningful paint is when the primary content of your page is visible. The page is not yet fully interactive, but the end user sees content rendered.

To decrease the time to your app’s first meaningful paint, you need to look at optimizing the Critical Rendering Path of your application. The Critical Rendering Path is the set operations that need to be performed between receiving the HTML, CSS and JavaScript resources of your app and the rendering of pixels on the user screen that represent the final result these resources are intended to produce.

To optimize the time your app takes until the first meaningful paint is produced, work on optimizing your render-blocking resources, especially CSS. CSS is a render-blocking resource, because without your stylesheets, your app has a completely different look and feel. You can optimize CSS by providing appropriate media rules to your CSS link references. If, for example, a CSS is annotated with a media query for specific device orientation, or specific screen sizes, then it won’t be considered render blocking, and the rendering wouldn’t be delayed by it. This means that splitting your media-specific CSS out of the main CSS file, and loading them separately, will provide a performance boost to your application.

Refer to this article on in-depth information and suggestions about optimizing the Critical Rendering Path performance.

Time to Interactive

Time to interactive is the time it takes for your page to become fully interactive. This means that not only the content is fully visible, but also all page event handlers have already been registered for the visible content, and the page responds to user interaction within 50 milliseconds.

Some applications optimize the first contentful and first meaningful paint at the expense of interactivity. This could turn users away, as it creates suboptimal experience for them. It’s rather confusing to have a fully visible page, and the page to not respond to user interactions because event handlers have not yet been attached.

To optimize the time the interactive, defer or remove all unnecessary JavaScript executions that are being ran during page load time. The most critical measure for time to interactive is the JavaScript boot-up time, and the JavaScript payload size.


Run-time Performance in Web

Run-time performance is defined by how responsive your application is to user interaction. The best way to identify performance bottlenecks of your application is to use the browser developer tools. My personal preference leans towards the Chrome developer tools.

 Chrome performance tab

It’s also good to use the memory profiler provided by the dev tools, in order to identify memory leaks in the application, because those cause the performance of the application to degrade over time. This is especially important when using a SPA (single page application) framework like Angular or React, as your entire application runs as a single web page, and not freeing up resources properly will lead to your application not only having worse performance, but also crashing after it has been used for a certain period of time. A good introduction to memory profiling and detecting leaks can be found here.

Each individual component in your web application, as well as the interactivity between all of them, define the run-time performance of your application. To ensure good run-time performance of your web application, you should consider these guidelines:

  1. Minimize the number of DOM nodes and the depth of the DOM node tree — try to have as few DOM nodes as possible. The recommendation is to have less than 1500 DOM nodes in total, and a DOM tree depth less than 32.
  2. Minimize the number of event handlers you attach to DOM elements — try to have as few event handlers as possible. If you’re repeating event handlers on the same type of element in a list, for example, then attach a single delegated event handler on the list element, instead of individual event handlers directly to the elements they target inside the list.
  3. Properly size images in your application — make sure the images are not larger than the containers they’re placed inside. Large images make your load time and runtime performance a lot slower.
  4. Defer offscreen image loading — make sure that images that should not be immediately visible have a deferred load policy.

Soft Performance in Web

Soft performance is defined by the general UX (user experience) of your application. Soft performance cannot be measured, but what you can do is perform usability studies for core components of your application to discover bottlenecks for your application’s usability. Such studies are created and carried out by UX experts with physical observations or by software, which records user interaction of the subjects participating in the usability study. A good tool for usability studies is Indigo.Design, which lets you create mock prototypes of your application, and then allows you to define and carry out a usability study on top of it. Here’s a sample of the report this tool produces.

In general, to improve the soft performance of your application, you need to:

  1. Ensure that core features are immediately accessible — if you want large percentage of your users to access a certain feature, then either directly present your users with it, or create a navigation element that leads to it. Core features should be accessible within two user interactions with your page.
  2. Core navigation elements are easily distinguishable on the page — if you have a navigation element that is your “call to action” or navigates to a core feature, then make sure it doesn’t blend with the rest of the page, or does not go off-screen when the page is loaded. Use larger font and contrasting color for it.
  3. The information flow is consistent — make sure that related information is not scattered on the page. Usually users go through information diagonally starting at the top left corner.
  4. Make your content accessible  a11y is a must in web. Make sure you run accessibility tests for your application. The Lighthouse Chrome extension also produces accessibility reports.

Example is how we’ve approached the usability of data filtering within our Ignite UI for Angular grid component. There are two different filtering UIs that can be used with the component, but the general interaction with either one is such that within two interactions with the component, the user gets filtering results. In the first mode, there’s an always visible filter chip in a static row right below the grid header elements. Clicking the filter chip transitions the filter row to a filtering input for the corresponding column, the input is on focus and a condition to filter (“contains” for string columns) by has already been pre-selected. The UI is ready for input. As the user starts typing, the column is being filtered according to what they are typing:

As you can see, the filtered results are available to the user within two user interactions with the component. What is not apparent by the screenshot, is that this component is also fully accessible. The filter chip elements are can also be accessed with only the keyboard, and the filtering can be applied using only the keyboard also. The elements are also screen readable. I’m giving this component as an example, because it’s complex enough to be compared to an application.

Other Considerations

You should be aware that all three performance dimensions should be considered together. Focusing on one can potentially hurt another. An example would be too much focus on run-time performance, which can lead to a degradation in the soft performance aspect, and vice-versa. I will illustrate this example, again, with a grid component on a web page. Grid components, in general, are intended to visualize large quantities of tabular data. In order to satisfy the requirements of a web application for load time and run-time performance, they need to virtualize the DOM elements they render, and to either swap or reuse DOM elements when the user performs vertical and horizontal scrolling on the component’s container. Let’s see how this looks in different existing grid components:

 ag-grid scrolling

 igx-grid scrolling

Notice that both grids have good run-time scrolling performance. The first one, however, has a noticeable visual tear which is caused by the debounced scroll handling put in place to improve run-time performance. This means that there’s a delay between the scrolling events and the rendering. The result is reduced soft performance in favor of run-time performance.


Conclusion

Software performance is a very broad topic, and what I have done in this article is to scrape the surface of it, by looking as the general performance concepts. My colleague, Brian Lagunas, has created a great article with performance tips for WPF that you can checkout.

Next I will dive deeper into performance of Angular web application, where I will look at more framework and technology specific aspects of performance.

This article is a re-post. The original article is on Medium.