Understanding zero copy
Zero copy is one of Netty’s most important features, but what exactly is zero copy? The WIKI defines it as follows:
“Zero-copy” describes computer operations in which the CPU does not perform the task of copying data from one memory area to another.
From the WIKI definition, we can see that “zero copy” is a computer operation in which the CPU does not need to consume resources for copying data between memory. It usually refers to the way in which computers send files directly to the network in the Kernel Space without copying the contents of the files to the User Space.
Non-zero Copy mode:
Zero Copy mode:
As you can clearly see from the figure above, Zero Copy improves overall system performance by avoiding data copying between user space and memory space. Sendfile () in Linux and filechannel.transferto () in Java NIO both implement zero-copy functionality, Zero-copy is also implemented in Netty by wrapping NIO’s filechannel.transferto () method in FileRegion.
Another form of zero-copy in Netty is that Netty allows us to merge multiple pieces of data into a single piece of virtual data for users to use without copying the data, which is what we’ll be talking about today. We all know that during stream-based transport (such as TCP/IP), packets may be reencapsulated in different packets, for example, when you send the following data:
It is possible to actually receive the following data:
Therefore, in practical application, it is likely that a complete message is divided into multiple packets for network transmission, and a single packet is meaningless to you. Only when these packets form a complete message can you make correct processing. Netty can combine these packets into a complete message for you to use with zero copy. At this point, the scope of zero copy is only in user space.
The implementation mechanism of Virtual Buffer ##Netty3 zero copy is described in Netty 3.8.0.Final source code.
ChannelBuffer interface
Netty provides a unified ChannelBuffer interface for data to be transferred. The main design ideas of the interface are as follows:
Random access is achieved using the getByte(int index) method
Sequential access using double Pointers
Each Buffer has a readIndex and a writeIndex.
Read pointer moves back when reading data and write pointer moves back when writing data
With a unified interface defined, it’s time to do the various implementations.
Netty mainly implements HeapChannelBuffer ByteBufferBackedChannelBuffer, etc., let’s talk about is directly related to the Zero Copy CompositeChannelBuffer classes. The CompositeChannelBuffer class CompositeChannelBuffer is used to compose a virtual ChannelBuffer for operation.
This is virtual because the CompositeChannelBuffer does not actually combine the multiple channelbuffers, but only holds their references, thus avoiding copying data and achieving Zero Copy. Let’s look at the code implementation, starting with member variables
private int readerIndex;
private int writerIndex;
private ChannelBuffer[] components;
private int[] indices;
private int lastAccessedComponentId;Copy the code
A few of the more important member variables are listed here. Both the readerIndex and writerIndex Pointers are inherited from AbstractChannelBuffer.
The indices is an int array that contains the indices of each Buffer. The indices is an int array that contains the indices of each Buffer.
The last lastAccessedComponentId is an int value that records the child Buffer ID when it was last accessed. The CompositeChannelBuffer is a series of buffers stored in an array that implements the ChannelBuffer interface. Working with these buffers is like working with a single Buffer.
create
Next, we look at the CompositeChannelBuffer. SetComponents method, it will be invoked when initializing CompositeChannelBuffer.
/** * Setup this ChannelBuffer from the list */ private void setComponents(List<ChannelBuffer> newComponents) { assert ! newComponents.isEmpty(); // Clear the cache. lastAccessedComponentId = 0; // Build the component array. components = new ChannelBuffer[newComponents.size()]; for (int i = 0; i < components.length; i ++) { ChannelBuffer c = newComponents.get(i); if (c.order() ! = order()) { throw new IllegalArgumentException( "All buffers must have the same endianness."); } assert c.readerIndex() == 0; assert c.writerIndex() == c.capacity(); components[i] = c; } // Build the component lookup table. indices = new int[components.length + 1]; indices[0] = 0; for (int i = 1; i <= components.length; i ++) { indices[i] = indices[i - 1] + components[i - 1].capacity(); } // Reset the indexes. setIndex(0, capacity()); }Copy the code
As you can see from the code, this method combines a List of channelbuffers. It first puts the elements from the List into the Components array, then creates indices for data lookup, and finally uses setIndex to reset the pointer.
Note that setIndex(0, capacity()) sets the read pointer to 0 and the write pointer to the current Buffer length, Assert C. readerIndex() == 0 and assert C. iterIndex() == C.capacity (). So Netty recommended we use ChannelBuffers. WrappedBuffer approach to Buffer merge, In this method, Netty uses the slice() method to ensure that the CompositeChannelBuffer is constructed and that all the child buffers passed in are valid.
The data access
CompositeChannelBuffer. GetByte (int index) implementation is as follows:
public byte getByte(int index) {
int componentId = componentId(index);
return components[componentId].getByte(index - indices[componentId]);
}Copy the code
Indices [componentId] : componentId: componentId: componentId: componentId: componentId: componentId: componentId: componentId: componentId: componentId: componentId: componentId: componentId: componentId: componentId: componentId: componentId: componentId: componentId: componentId: componentId: componentId: componentId: componentId: componentId: componentId: The result is then returned.
ComponentId (int index) componentId(int index)
private int componentId(int index) {
int lastComponentId = lastAccessedComponentId;
if (index >= indices[lastComponentId]) {
if (index < indices[lastComponentId + 1]) {
return lastComponentId;
}
// Search right
for (int i = lastComponentId + 1; i < components.length; i ++) {
if (index < indices[i + 1]) {
lastAccessedComponentId = i;
return i;
}
}
} else {
// Search left
for (int i = lastComponentId - 1; i >= 0; i --) {
if (index >= indices[i]) {
lastAccessedComponentId = i;
return i;
}
}
}
throw new IndexOutOfBoundsException("Invalid index: " + index + ", maximum: " + indices.length);
}Copy the code
We can see from the code that Netty searches left and right, centered on lastComponentId, the last accessed subbuffer number, so that when the two random character sequences are close (which is the case most of the time), The fastest search to the target index componentId.
The original link: my.oschina.net/plucury/blo… Wenyuan network, only for the use of learning, such as infringement, contact deletion.
I’ve compiled the interview questions and answers in PDF files, as well as a set of learning materials covering, but not limited to, the Java Virtual Machine, the Spring framework, Java threads, data structures, design patterns and more.
Follow the public account “Java Circle” for information, as well as quality articles delivered daily.