Two days ago, a friend in the group came to chat with me. During the interview, he was asked how to respond to performance optimization methods. Let’s talk today. This article mainly focuses on theoretical analysis. Let’s look at the rules that can be followed for Java performance optimization as a whole.
This article focuses on theory, Regarding practice, subsequent articles will use more cases to refine the knowledge points of this article, which is suitable for repeated Thinking and summarizing.
Performance optimization is divided into business optimization and technical optimization according to the optimization category. The effect of business optimization is also very large, but it belongs to the category of products and management. As programmers, in our daily work, the optimization methods we face are mainly through a series of technical means to achieve the established optimization goals. This series of technical means can be roughly summarized into the following seven categories:
As you can see, the optimization method focuses on the planning of computing resources and storage resources. There are many ways to exchange space for time in optimization methods, but it is not advisable to only take care of calculation speed without considering complexity and space issues. What we have to do is to achieve optimal resource utilization while taking care of performance.
Next, I will briefly introduce these 7 optimization directions. If you feel boring, that's okay. The purpose of this article is to give you a concept of the total score and an overall understanding of the theoretical basis.
When writing code, you will find that there are a lot of duplicate codes that can be extracted , make it a public method. In this way, you don't have to write it again the next time you use it.
This idea is reuse. The above description is an optimization of coding logic. For data access, there is the same reuse situation. Whether in life or coding, repeated things happen all the time. Without reuse, work and life will be more tiring.
In software systems, when it comes to data reuse, the first thing we think of is buffering and caching. Pay attention to the difference between these two words. Their meanings are completely different. Many students are easily confused. Here is a brief introduction.
Buffer (Buffer) is commonly used to temporarily store data and then transfer or write it in batches. The sequential method is mostly used to alleviate frequent and slow random writes between different devices. Buffering is mainly targeted at write operations.
Cache (Cache) is common in the reuse of read data. By caching them in a relatively high-speed area, caching is mainly targeted at read operations.
Similarly, it is the pooling operation of objects, such as database connection pool, thread pool, etc., which are used very frequently in Java. Since the creation and destruction costs of these objects are relatively high, we will also temporarily store these objects after use. The next time we use them, we don't have to go through the time-consuming initialization operation again.
Today’s CPUs are developing very fast, and most hardware is multi-core. If you want to speed up the execution of a certain task, the fastest and best solution is to let it execute in parallel. There are three modes of parallel execution:
The first mode is multi-machine, which uses load balancing to split traffic or large calculations into multiple parts and process them simultaneously. For example, Hadoop uses MapReduce to break up tasks and allow multiple machines to perform calculations at the same time.
The second mode is to use multiple processes. For example, Nginx uses the NIO programming model. The Master manages the Worker process in a unified manner, and then the Worker process performs the actual request proxying. This can also make good use of multiple CPUs of the hardware.
The third mode is to use multi-threading, which is what Java programmers are most exposed to. Netty, for example, uses the Reactor programming model and also uses NIO, but it is thread-based. The Boss thread is used to receive requests and then schedule them to the corresponding Worker threads for real business calculations.
Languages like Golang have a more lightweight coroutine. Coroutine is a more lightweight existence than threads, but it is not yet mature in Java, so it is not It has been introduced too much, but in essence, it is also for multi-core applications, allowing tasks to be executed in parallel.
Another optimization for calculation is to change synchronous to asynchronous, which usually involves programming models changes. In synchronous mode, the request will be blocked until a success or failure result is returned. Although its programming model is simple, it is particularly problematic when dealing with sudden and skewed traffic in time periods, and requests can easily fail.
Asynchronous operations can easily support horizontal expansion, relieve instantaneous pressure, and smooth requests. Synchronous requests are like fists hitting steel plates; asynchronous requests are like fists hitting sponges. You can imagine this process, the latter is definitely flexible and the experience is more user-friendly.
The last one is to use some common design patterns to optimize the business and improve the experience. For example, singleton mode, proxy mode, etc. For example, when drawing a Swing window, if you want to display more pictures, you can load a placeholder first, and then slowly load the required resources through a background thread, which can avoid the window from freezing.
Next, we will introduce the optimization of the result set. To give a more intuitive example, we all know that the representation of XML is very good, so why is there still JSON? In addition to being simpler to write, an important reason is that its size has become smaller, and its transmission efficiency and parsing efficiency have become higher. Like Google's Protobuf, its size is even smaller. Although the readability is reduced, in some high-concurrency scenarios (such as RPC), the efficiency can be significantly improved, which is a typical optimization of result sets.
This is because our current web services are all in C/S mode. When data is transmitted from the server to the client, multiple copies need to be distributed. The amount of data expands rapidly. Every time a small amount of storage is reduced, there will be a significant increase in transmission performance and cost.
Like Nginx, GZIP compression is usually turned on to keep the transmitted content compact. The client only requires a small amount of computing power to facilitate decompression. Since this operation is decentralized, the performance penalty is fixed.
Understanding this principle, we can see the general idea of optimizing the result set. You should try to keep the returned data as simple as possible. If there are some fields that are not needed by the client, then remove them in the code or directly in the SQL query.
For some businesses that do not have high requirements on timeliness, but have high requirements on processing capabilities. We must learn from the experience of buffers, minimize network connection interactions, and use batch processing to increase processing speed.
The result set is likely to be used twice, and you may add it to the cache, but it is still lacking in speed. At this time, it is necessary to optimize the processing of the data collection, using indexes or Bitmap bitmaps to speed up data access.
In our normal development, we will involve many shared resources. Some of these shared resources are stand-alone, such as a HashMap; some are external storage, such as a database row; some are single resources, such as Setnx of a certain Redis key; some are coordination of multiple resources, such as transactions, distributed transactions, etc.
In reality, there are many performance issues related to locks. Most of us think of database row locks, table locks, various locks in Java, etc. At the lower level, such as CPU command level locks, JVM instruction level locks, operating system internal locks, etc., it can be said that they are everywhere.
Only concurrency can cause resource conflicts. That is, at the same time, only one processing request can obtain the shared resources. The way to resolve resource conflicts is to lock. Another example is a transaction, which is essentially a kind of lock.
According to the lock level, locks can be divided into optimistic locks and pessimistic locks. Optimistic locks are definitely more efficient; according to lock types, locks are divided into fair locks and unfair locks. In terms of the task In terms of scheduling, there are some subtle differences.
Contention for resources will cause serious performance problems, so there will be some research on lock-free queues, and the performance improvement will be huge.
Algorithms can significantly improve the performance of complex businesses, but in actual businesses, they are often It's a variant. As storage becomes cheaper and cheaper, in some businesses where the CPU is very tight, space is often traded for time to speed up processing.
Algorithms belong to code tuning, which involves many coding skills and requires users to be very familiar with the API of the language used. Sometimes, the flexible use of algorithms and data structures is also an important part of code optimization. For example, commonly used ways to reduce time complexity include recursion, bisection, sorting, dynamic programming, etc.
An excellent implementation has a greater impact on the system than a poor implementation. For example, as List implementations, LinkedList and ArrayList are several orders of magnitude different in random access performance; for another example, CopyOnWriteList uses a copy-on-write method, which can significantly reduce lock conflicts in scenarios where there is more reading and less writing. When to use synchronization and when to be thread-safe, it also has higher requirements on our coding capabilities.
This part of knowledge requires us to pay attention to accumulation in our daily work. In the following classes, we will also select more important knowledge points to intersperse explanations.
In daily programming, try to use some components with good design concepts and superior performance. For example, with Netty, you no longer have to choose the older Mina components. When designing a system, considering performance factors, do not choose a time-consuming protocol like SOAP. For another example, a good syntax analyzer (such as using JavaCC) will be much more efficient than regular expressions.
In short, if the bottleneck point of the system is found through test analysis, the key components must be replaced with more efficient components. In this case, the adapter pattern is very important. This is why many companies like to add a layer of abstraction on top of existing components; and when the underlying components are switched, the upper-layer applications are not aware of it.
Because Java runs on the JVM virtual machine, many of its features are restricted by the JVM. Optimizing the JVM virtual machine can also improve the performance of JAVA programs to a certain extent. If parameters are configured improperly, it may even cause serious consequences such as OOM.
The currently widely used garbage collector is G1. With very few parameter configurations, memory can be recycled efficiently. The CMS garbage collector has been removed in Java 14. Since its GC time is uncontrollable, its use should be avoided if possible.
JVM performance tuning involves trade-offs in all aspects, which often affects the whole body, and the impact of all aspects needs to be considered comprehensively. Therefore, it is particularly important to understand some of the internal operating principles of the JVM. It will help us deepen our understanding of the code and help us write more efficient code.
The above are the 7 general directions of code optimization. Through a brief introduction, we will let you know Have a general understanding of the content of performance optimization. These seven major directions are the most important directions of code optimization. Of course, performance optimization also includes database optimization, operating system optimization, architecture optimization and other contents. These are not our focus. In the following articles, we will only give a brief summary. introduce.
The above is the detailed content of Interview: Do you know what methods are available for Java performance optimization?. For more information, please follow other related articles on the PHP Chinese website!