9

Introduction

For the same logic, the performance of the code implemented by different people will vary by orders of magnitude; for the same code, you may fine-tune a few characters or the order of a certain line of code, and the performance will be improved several times; for the same code, it may also be There are also several times the performance difference running on different processors; ten times programmers are not just legends, but probably all around us. Ten times is reflected in the programmer's method, and the code performance is the most intuitive aspect.
This article is the third in the "How to Write High-Performance Code" series. This article will tell you how to write better GC code to improve code performance.

Optimize memory reclamation

Garbage collection GC (Garbage Collection) is the main means of memory recovery in advanced programming languages, and it is also an essential feature of advanced languages. For example, the well-known Java, python, and go all have their own GC, and even C++ has begun to With the shadow of GC. GC can automatically clean up those unused garbage objects and release memory space. This feature is extremely friendly to novice programmers. On the other hand, in languages without GC mechanism, such as C++, programmers need to manage and release memory by themselves, which is prone to memory leaks. bug, which is one of the reasons why C++ is much more difficult to use than many languages.
The emergence of GC has reduced the difficulty of getting started with programming languages, but over-reliance on GC can also affect the performance of your program. Here I have to mention a notorious word - STW (stop the world), which means that the application process suspends all work and gives time to the GC thread to clean up garbage. Don't underestimate this STW, if the time is too long, it will obviously affect the user experience. Like the advertising business I was in before, studies have shown that the longer the response time of the advertising system, the lower the number of clicks on the ad, which means the less money you make.
GC also has a key performance indicator - throughput (Throughput), which is defined as the proportion of the time running user code to the total CPU running time. For example, suppose the throughput is 60%, which means that 60% of the CPU time is running user code, and the remaining 40% of the CPU time is occupied by the GC. From its definition, of course, the higher the throughput, the better, so how to improve the GC throughput of the application? Here I summarize three.

reduce the number of objects

This is easy to understand. The fewer garbage objects are generated, the fewer GC times are required. So how can we reduce the number of objects? This has to review the two features we mentioned in the previous lecture on using data features ) - reusability and non-essentiality. Students who forget can click on the link above to review. Here's an overview of how these two features reduce object generation.

reusability

Reusability here means that most objects can be reused, and these reusable objects do not need to be newly created every time, wasting memory space. Here is another example that has been used in Java, and this one has to start with a strange piece of code.

 Integer i1 = Integer.valueOf(111);
Integer i2 = Integer.valueOf(111);
System.out.println(i1 == i2);

Integer i3 = Integer.valueOf(222);
Integer i4 = Integer.valueOf(222);
System.out.println(i3 == i4);


What will the output of the above code be? You thought it was true+true, but it was actually true+false. what? ? In Java, 222 is not equal to 222. Is there a bug? In fact, this is a common mistake made by novices when comparing the size of values . The equality judgment between packaging types should use equals instead of '==', '==' will only judge this Whether the two objects are the same object, not whether the concrete values of the packages in the objects are equal.
在这里插入图片描述
A batch of numbers such as 1, 2, 3, 4, etc. are very commonly used in any scenario. It is wasteful to create a new object every time it is used. Java developers have also taken this into account, so in Jdk Extract and cache a batch of integer objects (-128 to 127), these numbers can be used directly each time, instead of creating a new object. And numbers outside the range of -128 to 127 will be new objects every time. The following is the source code and comments of Integer.valueOf():

 /**
     * Returns an {@code Integer} instance representing the specified
     * {@code int} value.  If a new {@code Integer} instance is not
     * required, this method should generally be used in preference to
     * the constructor {@link #Integer(int)}, as this method is likely
     * to yield significantly better space and time performance by
     * caching frequently requested values.
     *
     * This method will always cache values in the range -128 to 127,
     * inclusive, and may cache other values outside of this range.
     * 
     * @param  i an {@code int} value.
     * @return an {@code Integer} instance representing {@code i}.
     * @since  1.5
     */
    public static Integer valueOf(int i) {
        if (i >= IntegerCache.low && i <= IntegerCache.high)
            return IntegerCache.cache[i + (-IntegerCache.low)];
        return new Integer(i);
    }

I saw several objects i1-i4 through Debug in Idea. In fact, the two objects of 111 are indeed the same, and the two objects of 222 are indeed different, which explains the strange phenomenon in the above code.

unnecessary

Non-essential means that some objects may not need to be generated. Here I give an example, which may be similar to the following code, which is very common in business systems.

 private List<UserInfo> getUserInfos(List<String> ids) {
        List<UserInfo> res = new ArrayList<>(ids.size());
        if (ids == null || res.size() == 0) {
            return new new ArrayList<>();
        }
        List<UserInfo> validUsers = ids.stream()
                .filter(id -> isValid(id))
                .map(id -> getUserInfos(id))
                .filter(Objects::nonNull)
                .collect(Collectors.toList());
        res.addAll(validUsers);
        return res;
    }

The above code is very simple. It is to obtain complete user information through a batch of user IDs. Before obtaining, the input parameters must be verified, and then the validity of the IDs will be verified. The problem with the above code is that the res object is initialized too early. If a UserInfo is not found, the res object is initialized in vain. In addition, it is enough to return validUsers directly in the end, there is no need to install it in res, here res is unnecessary.
The above situation may be seen everywhere in many business systems (but not necessarily so intuitive). Initializing some objects that are useless in advance will not only waste memory and CPU, but also increase the burden on GC.

Reduce object size

It is also easy to understand how to reduce the size of the object. If the number of objects generated by the object per unit time is fixed, but after the volume is reduced, the memory of the same size can load more objects, and the GC will be triggered later, and the frequency of the GC will be reduced. , the lower the frequency, the smaller the impact on performance will naturally be.
Regarding reducing the size of objects, here I recommend a jar package - eclipse-collections, which provides many collections of primitive types, such as IntMap, LongMap... Use primitive types (int, long, double...) instead of encapsulated types (Integer, Long, Double...), it is very advantageous in some businesses with too many values. The following figure is a comparison of the memory usage of HashSet<Integer> and IntSet in eclipse-collections under different data amounts. IntSet's The memory footprint is only a quarter of that of HashSet<Integer>.
在这里插入图片描述
In addition, when we write business code, do not add unnecessary fields when writing some DO, BO, and DTO. When checking the database, do not check out the unused fields. I have seen a lot of business codes before. When I checked the database, I checked out the entire line. For example, I wanted to check the age of a user, and the result was his name, address, birthday, phone number... All these information were found out. It needs to be stored in Java objects one by one. If some fields are not used, they are firstly taken for nothing. In fact, it is a waste of memory space to store them.

Shorten object lifetime

Why does reducing object lifetime improve GC performance? The total garbage objects have not decreased! Yes, that's right, simply shortening the lifetime of an object will not reduce the number of garbage objects, but will reduce the number of GCs. To understand this, you must first know the GC triggering mechanism. For example, in Java, when the heap space usage exceeds a certain threshold, the GC will be triggered. If the time of the object can be shortened, then each GC can release more space. The next GC will come later, and the overall number of GCs will be reduced.
Here I will give a real case of my own experience. We had an interface in the previous system. Just by adjusting the order of two lines of code, the performance of this interface was improved by 40%, and the CPU usage of the entire service was reduced by 10%+ , and the change of the order of these two lines shortens the life cycle of most objects, so it leads to performance improvement.

 private List<Object> filterTest() {
        List<Object> list = getSomeList();
        List<Object> res = list
                .stream()
                .filter(x -> filter1(x))  // filter1需要调用外部接口做过滤判断,性能低且过滤比例很少
                .filter(x -> filter2(x))  
                .filter(x -> filter3(x))  // filter3 本地数值校验,不依赖外部,效率高且过滤比例高
                .collect(Collectors.toList());
    }

In the above code, the performance of filter1 is very low but the filtering ratio is low, and filter3 is just the opposite. Often, those that are not filtered by filter1 will be filtered by filter3, which does a lot of useless work. Here, you only need to exchange the positions of filter1 and filter3. In addition to reducing useless work, the life cycle of most objects in the List will also be shortened.
In fact, having a better programming habit can also reduce the survival time of objects. In fact, I also probably mentioned in the first part of this series, that is, reducing the scope of variables. If you can use local variables, you can use local variables, and if you can put them in if or for, you can put them inside, because the implementation of programming language scope is the stack used. The smaller the scope, the faster the stack is popped, and the faster the objects used are judged for dead objects.


In addition to the above three ways to optimize GC, there is actually a kind of saucy operation, but I don't recommend using it myself, that is - off-heap memory

off-heap memory

In Java, only on-heap memory is managed by the GC collector, so the most direct way to prevent performance from being affected by GC is to use off-heap memory. Java also provides an API for off-heap memory usage. However, off-heap memory is also a double-edged sword. If you want to use it, you must take good management measures. Otherwise, memory leaks will cause OOM to become GG, so it is not recommended to use it directly. However, there are always some excellent open source codes, such as the caching framework ehcache ), which can allow you to safely enjoy the benefits of off-heap memory. For the specific usage, you can refer to the official website, which will not be repeated here.


Well, today's sharing is here. After reading it, you may find that today's content and the previous lecture (2) Skillful use of data features have some repetitive content. Yes, I understand that the bottom layer of performance optimization is the same set of methodology , many new methods are just derived from different perspectives in different fields. Finally, thank you for your support, and I hope you will gain something from reading the article. If you are interested, you can also pay attention to the first two articles in this series.

How to write high-performance code series of articles


xindoo
717 声望3k 粉丝