Reordering of the JVM in JAVA is described in detail

  • 2020-04-01 03:18:13
  • OfStack

In a concurrent program, the programmer will pay special attention to the data synchronization between different process or thread, especially when multiple threads at the same time to modify the same variable, reliable synchronization or other measures should be taken to guarantee the data is properly modified, here's an important principle is: don't assume that the order of the instruction execution, you can't predict the instruction will be executed in what order between different threads.

But in single-threaded programs, it's often easy to assume that the instructions are executed sequentially, or you can imagine what can happen to the program. The ideal model is one in which instructions are executed in a unique and ordered order, the order in which they are written in code, independent of the processor or other factors. This model is known as the sequential consistency model and is also based on the von neumann system. Of course, this assumption is reasonable in itself and rarely unusual in practice, but in fact, no modern multiprocessor architecture would adopt this model because it is simply too inefficient. In both compilation optimization and CPU pipelining, instruction reordering is almost always involved.

Compile-time resort

The typical method of reordering at compile time is to reduce the number of times of reading and storing registers and fully reuse the stored values of registers by adjusting the instruction order without changing the program semantics.

Suppose the first instruction calculates A value assignment to variable A and puts it in A register, the second instruction has nothing to do with A but needs to occupy A register (assuming it will occupy the same register as A), and the third instruction USES the value of A and has nothing to do with the second instruction. So if, according to the sequential consistency model, A is put into A register after the first instruction is executed, A no longer exists at the second instruction, and A is read back into the register at the third instruction, and the value of A does not change during this process. Usually the compiler will swap the locations of the second and third instructions so that when the first instruction ends, A is in the register and can then be read directly from the register, reducing the overhead of repeated reads.

What reordering means for pipelining

Nearly all modern CPU using pipelining mechanism to speed up the order processing speed, in general, an instruction to a number of processing CPU clock cycle, and executed in parallel by the assembly lines, can be in the same clock cycle, the execution of some particular way, in a nutshell is the instructions are divided into different execution cycle, such as reading, addressing, parsing, implementation steps, such as and on the different components in the processing, at the same time in execution units in the EU, functional units are divided into different components, such as adding element, multiplication components, load components, storage devices, etc., can further realize the calculation of different parallel execution.

The pipeline architecture dictates that instructions should be executed in parallel, not as thought in the sequential model. Reordering is beneficial to make full use of pipelining and achieve superscalar effect.

Ensure sequentiality

Although instructions do not always execute in the order we wrote them, it is clear that in a single-threaded environment, the end result of instruction execution should be the same as the result of sequential execution, otherwise the optimization will be meaningless.

The above principle is usually satisfied for instruction reordering at compile time or run time.

Resort in the Java storage model

Reordering is an important section in the Java Memory Model (JMM), especially in concurrent programming. The JMM guarantees sequential execution semantics through the happens-before rule. If you want the thread executing operation B to observe the result of the thread executing operation A, then A and B must satisfy the happens-before principle. Otherwise, the JVM can sort them arbitrarily to improve program performance.

The volatile keyword ensures the visibility of variables, because the operations on volatile are in Main Memory, which is Shared by all threads, at the expense of performance. Registers or caches cannot be used, because they are not global, visibility is not guaranteed, and dirty reads may occur.

Another benefit of volatile is that it locally prevents reordering. Instructions on volatile variables are not reordered because reordering can cause visibility problems.

In terms of visibility, locks (including explicit locks, object locks) and reads and writes to atomic variables ensure visibility. But this is implemented in a slightly different way. For example, a synchronized lock guarantees that data will be read back from memory to flush the cache when the lock is acquired, and data will be written back to memory to keep the data visible when the lock is released, whereas volatile variables are simply reads and writes to memory.


Related articles: