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, ** “zero copy” refers to 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 to the network directly from 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. ** The sendFile () method in Linux and the Filechannel.transferto () method 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.

ChannelBuffer Interface Netty provides a unified ChannelBuffer interface for data to be transferred. The main design ideas of the interface are as follows:

1. Use getByte(int index) to implement random access

2. Use double Pointers to implement sequential access

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.

Write in the last

Welcome to pay attention to my public number [calm as code], massive Java related articles, learning materials will be updated in it, sorting out the data will be placed in it.

If you think it’s written well, click a “like” and add a follow! Point attention, do not get lost, continue to update!!