Book Image

TypeScript High Performance

By : Ajinkya Kher
Book Image

TypeScript High Performance

By: Ajinkya Kher

Overview of this book

<p>In a world where a tiny decrease in frames per second impacts customer engagement greatly, writing highly scalable code is more of a necessity than a luxury. Using TypeScript you get type checking during development. This gives you the power to write optimized code quickly. This book is also a solid tool to those who’re curious to understand the impact of performance in production, and it is of the greatest aid to the proactive developers who like to be cognizant of and avoid the classic pitfalls while coding.</p> <p>The book will starts with explaining the efficient implementation of basic data Structures, data types, and flow control. You will then learn efficient use of advanced language constructs and asynchronous programming. Further, you'll learn different configurations available with TSLint to improve code quality and performance. Next, we'll introduce you to the concepts of profiling and then we deep dive into profiling JS with several tools such as firebug, chrome, fiddler. Finally, you'll learn techniques to build and deploy real world large scale TypeScript applications.</p>
Table of Contents (17 chapters)
Title Page
Credits
Foreword
About the Author
Acknowlegement
About the Reviewer
www.PacktPub.com
Customer Feedback
Preface
7
Profile Deployed JS with Developer Tools and Fiddler

Strings


Let's start by taking a look at strings. In TypeScript, you can create a string in one of the following ways:

    var test1: string = 'test string';

    var test2: String = new String('test string');

For all practical purposes, you would almost always use the first way to work with strings. The only difference between the two is that string is a literal type and a preferred way to declare strings in TypeScript. String is an object type, which is essentially a wrapper object around the string. From a performance standpoint, literal types tend to perform better than object types. We can confirm this by running a simple code snippet as mentioned in the following code snippet:

    function createString_1(): string {
     return `Lorem Ipsum is simply dummy text of the printing and  
     typesetting industry. Lorem Ipsum has been the industry''s    
     standard dummy text ever since the 1500s, when an unknown    
     printer took a galley of type and scrambled it to make a type   
     specimen book. It has survived not only five centuries, but also      
     the leap into electronic typesetting, remaining essentially  
     unchanged.It was popularised in the 1960s with the release of   
     Letraset sheets containing Lorem Ipsum passages, and more  
     recently with desktop publishing software like Aldus PageMaker   
     including versions of Lorem Ipsum.`;
    }

    function createString_2(): String {
     return new String(`Lorem Ipsum is simply dummy text of the   
     printing and typesetting industry. Lorem Ipsum has been the 
     industry''s standard dummy text ever since the 1500s, when an   
     unknown printer took a galley of type and scrambled it to make a  
     type specimen book. It has survived not only five centuries, but 
     also the leap into electronic typesetting, remaining essentially  
     unchanged. It was popularised in the 1960s with the release of  
     Letraset sheets containing Lorem Ipsum passages, and more 
     recently with desktop publishing software like Aldus PageMaker 
     including versions of Lorem Ipsum.`);
    }

    // calculate time taken to create 50, 000 'strings'
    let time1: number = Date.now();
    for (let i = 0; i < 50000; i++) {
      createString_1();
    }

    let time2: number = Date.now();
    console.log('Time difference (createString_1): ', time2 - time1);

    // calculate time taken to create 50, 000 'Strings'
    time1 = Date.now();
    for (let i = 0; i < 50000; i++) {
      createString_2();
    }

    time2 = Date.now();
    console.log('Time difference (createString_2): ', time2 - time1);

The preceding code snippet creates a long string (the famous filler text mostly used as a placeholder on visual web elements) using the preceding described two ways.

Note

The multiline string is represented in TypeScript by enclosing the string within two back-tick/back-quote (`) characters. It's that easy!

Now, let's take a look at the results of executing the preceding code snippet:

Chrome (v56)

IE (v11)

Edge (v38)

createString_1() : string

3 ms

17 ms

13 ms

createString_2(): String

5 ms

42 ms

31 ms

Note

Every set of time-based results mentioned in this book is an average of five runs. For example, the average runtime of createString_1() on Chrome (v56) across five runs is 3 milliseconds. We cover the runtime across three browsers - Google Chrome version 56, Internet Explorer Version 11, and Microsoft Edge Version 38.

As you can see from the results, the literal type string does behave slightly better. The creation of the string, however, is not the most impacting operation that would affect your application. Let's take a look at some classic string manipulation operations.

String concatenation

Let's start with string concatenation. Take a look at the following code snippet:

    function createString(): string {
     return `Lorem Ipsum is simply dummy text of the printing and 
     typesetting industry. Lorem Ipsum has been the industry''s 
     standard dummy text ever since the 1500s, when an unknown   
     printer took a galley of type and scrambled it to make a type     
     specimen book. It has survived not only five centuries, but also 
     the leap into electronic typesetting, remaining essentially  
     unchanged. It was popularised in the 1960s with the release of 
     Letraset sheets containing Lorem Ipsum passages, and more 
     recently with desktop publishing software like Aldus PageMaker   
     including versions of Lorem Ipsum.`;
    }

    let time1: number = Date.now();
    let s1: string = '';

    for (let i = 0; i < 40000; i++) {
     s1 = s1.concat(createString());
    }

    let time2: number = Date.now();
    console.log('Time difference: ', time2 - time1);

    let s2: string = '';
    time1 = Date.now();

    for (let i = 0; i < 400000; i++) {
     s2 += createString();
    }

    time2 = Date.now();
    console.log('Time difference: ', time2 - time1);

The preceding snippet contains a simple function createString, which returns a long string, precisely 617 characters long. We append this string to itself 40,000 times. In the first loop, s1 does so by calling s1.concact(createString()) as many times, and in the second loop, s2 does so by calling s2+= createString() as many times. This is just a shorthand for s2 = s2 + createString().

When you analyze these loops, how long do you think they took to complete execution? Did one substantially outperform the other? Or, do you think they're just two different ways to append a string to itself, and has no impact on performance? Well, here are the results:

Chrome (v56)

IE (v11)

Edge (v38)

Loop 1

7 ms

50 ms

36 ms

Loop 2

60 ms

183 ms

163 ms

The preceding results are an average of five runs. As you can clearly see, Loop 1 outperforms Loop 2 massively.

Just to understand how massive the impact on the end result would be, consider the following scenario. Consider a social web application, which when the user starts on his/her end loads the entire friend/contact list and the entire conversation history with each contact. Hypothetically, assume that the user has 100 contacts, and with each contact he/she has 10 conversations. Let's further assume each conversation to be essentially the preceding string in our example appended to itself 40,000 times. Using our preceding results (considering Chrome as the web browser), with an inefficient implementation of string concatenation the page load time would be 100 * 10 * 60 milliseconds = 60,000 milliseconds = 60 seconds or 1 minute. Now, that's a really long time the user has to wait before the application becomes functional. Such sluggishness can greatly impact the application's usability and user engagement.

As opposed to this, if the user was to load the same application with the efficient string concatenation technique, the user wait time would be 100 * 10 * 7 = 7,000 milliseconds = 7 seconds, which is not quite that bad.

If you're starting to appreciate the powers of efficient implementation, we're just getting started. The time performance isn't even the critical impact factor. The resource that is greatly impacted is memory!

When you do s1.concact(createString()), you're essentially appending the string on the same object, as a result of this the total memory consumption is increased only by the memory needed by the additionally appended string.

However, when you do s2+= createString(), you're essentially creating a new object each time. If s2 was pointing to a memory block which occupies x bytes, now after one call to s2+= createString(), it is pointing to a new memory block which occupies x + x = 2x bytes. Thus, at each step, the total memory consumption is greatly increased.

After 40,000 such invocations, the first loop will result in total memory consumption of 40,000 * x bytes, whereas the second loop will result in total memory consumption of x + 2x + 3x + ... + 40,000 * x bytes = 800, 020 * x bytes. As you can see, way greater memory is consumed by the second loop.

Running some tests, it is observed that on Chrome, the shallow size occupied by the concatenated string during the execution of the first loop is 802, 160 bytes, or ~0.8 MB. The shallow size occupied by the concatenated string during the execution of the second loop is 8, 002, 160 bytes, or ~8 MB.

The implications of this are dastardly, and the user can experience application crashes and freezes due to memory overload, which would lead to poor user experience and if not immediately corrected, very soon zero usability and lost business.

Now, let's take a look at string replacement.

String replacement

Take a look at the following code snippet:

    function createString(): string {
      return `Lorem Ipsum is simply dummy text of the printing and     
      typesetting industry. Lorem Ipsum has been the industry''s  
      standard dummy text ever since the 1500s, when an unknown   
      printer took a galley of type and scrambled it to make a type  
      specimen book. It has survived not only five centuries, but 
      also the leap into electronic typesetting, remaining   
      essentially unchanged. It was popularised in the 1960s with the   
      release of Letraset sheets containing Lorem Ipsum passages, and   
      more recently with desktop publishing software like Aldus  
      PageMaker including versions of Lorem Ipsum.`;
    }
    let baseString: string = createString();
    const replacementCharacter: string = '|';
    let time1: number = Date.now();
    for (let i = 0; i < 50000; i++) {
        baseString.split(' ').join(replacementCharacter);
      }
    let time2: number = Date.now();
    console.log('Time difference (SplitJoin): ', time2 - time1);
    time1 = Date.now();
    for (let i = 0; i < 50000; i++) {
         baseString.replace(/ /g , replacementCharacter);
        }
    time2 = Date.now();
    console.log('Time difference (Replace_w_RegExp): ', time2 - 
    time1);

In the preceding code snippet, we start with the same base string as in previous examples. Here, we have the objective of replacing every whitespace with the pipe character ('|'). For example, if the base string were "High Performance TypeScript", after applying our replace logic, the resultant string would be "High|Performance|TypeScript".

Now, this string has a total of 127 whitespaces. We loop over the base string and perform this replacement a total of 50,000 times. Let's understand the following two different replacement options we are leveraging:

  • SplitJoin: Let's take a look at the following replacement option code snippet:
        baseString.split(' ').join(replacementCharacter);

Let's split this into two pieces to better understand how this works, such as split and join:

  • split: This method splits the string into an array of strings by separating the string into substrings, as specified by an optional splitter character. Here, we are using the whitespace (' ') as the splitter character. So, after we apply baseString.split(' '), baseString is split into a string array of length 128.
  • join: This method joins or combines all elements of a string array into a string, as specified by an optional combiner character. Here, we specify the pipe ('|') as the combiner character. Thus, the result of baseString.split(' ').join('|') is a resultant string of the same length as the base string (617 characters), with all the whitespaces replaced by the pipe character.
  • Replace with RegEx: Let's take a look at the following replacement option code snippet:
        baseString.replace(/ /g, replacementCharacter);

The replace method returns a new string with some or all matches of a pattern replaced by a replacement character. We again choose the pipe character as the replacement character, and the pattern we choose is a regular expression '/ /g'. We specify a RegEx by enclosing it within the forward slash character ('/'). Here, we specify a white space as the RegEx.

Note

We include the g flag to indicate a global search and replace. If we are to not include it, then only the first occurrence of a whitespace would be replaced. There is also an "i" flag that can be used, which would mean ignore case. In this case, it does not apply as we are not bothered about the casing with the whitespace character.

If you were to analyze the two methods, you would note that in the SplitJoin method you perform two passes around the input string -- first to break it down into smaller chunks, and then to combine those chunks back into a whole string. This would involve as many string concatenations as the number of chunks it got split into in the first place. The Replace with RegEx options seems to take one pass around the input string, creating a new string copy, character by character, looking for a pattern match along the way and replacing it with the replacement character. An educated guess would be that the Replace with RegEx option has a better performance.

Let's take a look at the results to find out! Note that these are a result of 127 replacements on a 617 character long string, done 50,000 times:

Chrome (v56)

IE (v11)

Edge (v38)

SplitJoin

195 ms

405 ms

452 ms

Replace with RegEx

139 ms

144 ms

139 ms

 

As we predicted, the Replace with the RegEx method outperforms the SplitJoin method. The difference between the two methods is more significant on IE and Edge. In terms of real-world usage, string replacement is even more widely used than concatenation. Any web/mobile application with a frontend framework that works with multiple backends, or even a single backend, can run into the string replacement scenarios where data interpretation across different systems varies and there's a need to replace some strings to convert it into a format that the current system can understand. If done inefficiently, the results could once again lead to a slower/sluggish UI rendering, higher network latencies, incapability in using the application's full feature set due to the slower performance making the application unusable and gives you a poor user experience overall.

As you can see, efficient implementation of basic data type manipulations have a far reaching impact on the performance and usability of an application. Let's look at some other data structures and constructs.