Deep thinking of performance optimization caused by try catch

Time:2021-3-6

Deep thinking of performance optimization caused by try catch

The key code is disassembled as shown in the figure below (the irrelevant part has been omitted)

Deep thinking of performance optimization caused by try catch

At first I thought it might be thisgetRowDataItemNumberFormatSome of the methods in the function are too slow to executeformatData.replacereachunescape(abandoned, officially recommended)decodeURIOrdecodeURIComponentInstead, I suspect that none of these methods is responsible for the slow running of the function. In order to find out the reason, I’ll give it to youstyle.formatDataDifferent values are passed in, and it is found that the efficiency of this function is different. I started to wonder whystyle.formatDataThe value of causes such a big difference in the efficiency of this function.

Further final positioning found that ifstyle.formatDataWhen it is undefined, the efficiency drops sharply, ifstyle.formatDataWhen it is a legal string, the efficiency is normal. I began to realize the cause of the problem and turned my eyes to ittry catchCode block, this is a very suspicious place. I heard about unreasonable code block a long time agotry catchIt will affect the performance, but it has never been encountered before. Combined with some data, I found that there are few cases to explore the performance of this kind of code fragment. I decided to write code to verify:

window.a = 'a';
window.c = undefined;
function getRowDataItemNumberFormatTryCatch() {
    console.time('getRowDataItemNumberFormatTryCatch');
    for (let i = 0; i < 3000; i++) {
        try {
            a.replace(/%022/g, '"');
        }
        catch (error) {
        }
    }
    console.timeEnd('getRowDataItemNumberFormatTryCatch');
}

I try to puttry catchPut in oneforIn the loop, let it run 3000 times to see how much time it takes. The time for my computer to execute the code is about 0.2 ms, which is a relatively fast value, but here it isa.replaceIt’s running normally, that isaIs a string that works properlyreplaceMethod, so the time consumption here is normal. I made a little change to him as follows:

function getRowDataItemNumberFormatTryCatch2() {
    console.time('getRowDataItemNumberFormatTryCatch');
    for (let i = 0; i < 3000; i++) {
        try {
            c.replace(/%022/g, '"');
        }
        catch (error) {
        }
    }
    console.timeEnd('getRowDataItemNumberFormatTryCatch');
}

The only difference between this code and the above code is,c.replaceAt this time, there should be an error, becausecyesundefinedThis error will be correctedtry catchCatch, and the above code time consumption has changed dramatically, up to 40 ms, nearly 200 times! And the above code and the first figure’s “getrowdataitemnumberformat” function code appearMinor GCPay attention to thisMinor GCIt’s also time-consuming.

Deep thinking of performance optimization caused by try catch

This can explain part of the reason. The code we are running above is a key part of performance and should not be usedtry catchStructure, because the structure is quite unique. Unlike other constructs, its runtime creates a new variable in the current scope. every timecatchThis happens when the clause is executed, assigning the captured exception object to a variable.

Even within the same scope, this variable does not exist in other parts of the script. It’s incatchClause is created at the beginning of the clause and then destroyed at the end of the clause. Because this variable is created and destroyed at run time (which takes extra time!) And this isJavaScriptLanguage is a special case, so some browsers can not deal with it very effectively, and in the case of catching exceptions, putting the capture handler in the performance critical loop may cause performance problems, which is why we have the above problemsMinor GCAnd there will be serious time-consuming reasons.

If possible, exception handling should be done at a higher level in the code, in which case it may not occur so frequently, or it can be avoided by checking first to see if the required operation is allowed. abovegetRowDataItemNumberFormatTryCatch2If the required property does not exist in the loop shown in the function example, the loop may cause multiple exceptions. For this reason, a better way to write the loop is as follows:

function getRowDataItemNumberFormatIf() {
    console.time('getRowDataItemNumberFormatIf');
    for (let i = 0; i < 3000; i++) {
        if (c) {
            c.replace(/%022/g, '"');
        }
    }
    console.timeEnd('getRowDataItemNumberFormatIf')
}

The above code is semantically similar totry catchIn fact, it is similar, but the operation efficiency drops rapidly to 0.04ms, sotry catchThis construct should be completely avoided by checking properties or using other appropriate unit tests, as they can greatly affect performance and should be minimized.

If a function is called repeatedly or a loop is evaluated repeatedly, it is best to avoid including these constructs. They are most suitable for code that is executed only once or several times and is not executed within performance critical code. Isolate them from other code as much as possible to avoid affecting their performance.

For example, you can put them in top-level functions, or run them once and store the results so that you can use them again later without having to rerun the code.

Deep thinking of performance optimization caused by try catch

getRowDataItemNumberFormatAfter the transformation of the above ideas, the operation efficiency has been qualitatively improved. The time reduced in the measured 300 cycles is shown in the figure below, which fully optimizes the time of nearly 2 seconds. If it is 3000 cycles, the optimization ratio will be higher

Deep thinking of performance optimization caused by try catch
Deep thinking of performance optimization caused by try catch

Since the above code is modified from the project, it may not be intuitive enough, so I rewrite another similar example. The code is as follows, and the logic in it is the same as that in the abovegetRowDataItemNumberFormatFunction reasoning is consistent, but I let it go when it goes wrongcatchThe logic performs the task.

in factplus1andplus2Function code logic is consistent, only code semantics is different, one is to return 1, the other is to throw an error 1, a summation method in thetryThe fragment is completed, and another summation method is used againcatchAfter that, we can paste this code and remove different comments in the browser to observe the results.

We found thattryThe code in the fragment ran for about 0.1 ms, while thecatchIt takes about 6 ms to complete the same summation logic, which is in line with the expectation of our code observation above. If we continue to increase the calculation range, the gap will be more obvious. If we calculate 300000 times, the gap will be expanded from 60 times to 500 times, that is to say, we are implementing the same summation logiccatchThe less the number of times, the less the discount efficiencycatchThe more times, the more efficiency will be lost.

So use it when you have totry catchCode block, but also to ensure that as little as possible into thecatchIn the control flow branch.

const plus1 = () => 1;
const plus2 = () => { throw 1 };
console.time('sum');
let sum = 0;
for (let i = 0; i < 3000; i++) {
    try {
        //Sum + = plus1(); // the correct time is about 0.1ms
        Sum + = plus2(); // the error time is about 6ms
    } catch (error) {
        sum += error;
    }
}
console.timeEnd('sum');

The above performance further aroused my thinking about the performance of the project. I searched at least 800 of our projectstry catchUnfortunately, we can’t guarantee all of themtry catchIt does not damage the performance of the code and is meaningful. Many of the above classes will be hidden in ittry catchCode block.

From a performance point of view, currentlyV8The engine is really going throughtry catchTo optimize this kind of code fragment, in previous browser versions, the whole loop above even occurred intry catchWithin the code block, it also slows down because previous browser versions disabled it by defaulttry catchInternal code optimization to facilitate our debugging exception.

andtry catchYou need to traverse some structure to find itcatchHandle code, and usually assign exceptions in some way (e.g., need to check the stack, view heap information, execute branches, and recycle the stack). Although most browsers have been optimized, we should try our best to avoid writing similar codes, such as the following:

try {
    container.innerHTML = "I'm alloyteam";
}
catch (error) {
    // todo
}

If you actually throw and catch an exception, it may slow down. However, because the above code has no exception in most cases, the overall result will be faster than the exception.

This is because there is no branch in the code control flow, which will reduce the running speed. In other words, when there is no error in the code execution, you do not waste your code execution time in the catch. We should not write too much codetry catchThis will increase unnecessary costs when we maintain and inspect the code, which may distract and waste our attention.

When we have a hunch that the code fragment may go wrong, we should pay more attention to itsuccessanderrorInstead of usingtry catchTo protect our code, more oftentry catchOn the contrary, it will make us ignore the fatal problems existing in the code.

if (container) container.innerHTML = "I'm alloyteam";
else // todo

It should be reduced or not used in simple codetry catchWe can give priority to itif elseInstead, it should be reduced in some complex and unmeasurable codetry catch(asynchronous code, for example), we’ve seen a lotasyncandawaitThe sample code is a combination oftry catchYes, in many performance scenarios, I don’t think it’s reasonable. I think the following writing should be more clean, tidy and efficient.

becauseJavaScriptIt is event driven. Although an error does not stop the whole script, if any error occurs, it will make an error. There is almost no benefit in capturing and handling the error. The main part of the code istry catchA code block is unable to catch an error that occurs in an event callback.

Generally, it is more reasonable to pass the error message through the first parameter in the callback method, or consider using thePromiseOfreject()For processing, you can also refer tonodeThe common expressions in are as follows:

;(async () => {
    const [err, data] = await readFile();
    if (err) {
        // todo
    };
})()

fs.readFile('<directory>', (err, data) => {
    if (err) {
        // todo
    }
});

Combined with the above analysis, I make some simple conclusions

    1. If we perfect some tests and try our best to ensure that there are no exceptions, we don’t need to try to use themtry catchTo catch exceptions.
    1. Non exception paths do not require additionaltry catchTo ensure that the exception path is given priority when performance needs to be consideredif elseRegardless of the performance, please feel free, while asynchronous can consider the return of callback functionerrorThe processing or use of informationPromse.reject()
    1. It should be reduced appropriatelytry catchUse it, and don’t use it to protect our code. Its readability and maintainability are not high. When you expect the code to be abnormal, you can consider using it when it doesn’t meet the above 1,2 scenarios.

Finally, I hope this article can give you and me some direction and inspiration, if there are omissions, please give me advice!

With links to notes, you can read more high-quality articles in the past. I hope it can help you a little. Your praise is my biggest encouragement

Recommended Today

Rust and python: why rust can replace Python

In this guide, we compare the rust and python programming languages. We will discuss the applicable use cases in each case, review the advantages and disadvantages of using rust and python, and explain why rust might replace python. I will introduce the following: What is rust? What is Python? When to use rust When to […]