Zero-copy apresentação

prefácio

Entendida a partir do significado literal é copiar de volta os dados e para trás não é necessário, aumentar consideravelmente o desempenho do sistema; a palavra muitas vezes ouvimos em java nio, netty, kafka, RocketMQ e outras estruturas, muitas vezes como um dos destaques da sua melhoria de desempenho; abaixo diversos conceitos de I / O começar, o zero copiar uma análise mais aprofundada.

conceito I / O

1. Tampão

O tampão é a base de toda a de I / O, I / O é nada mais do que falar para dentro ou para fora da memória tampão de dados; executa processo de operações de I / O, um pedido é enviado para o sistema operacional, é, quer os dados do buffer drenado (escrever), ou para preencher o buffer (leitura); veja abaixo de processos iniciados java uma leitura de dados pedido para carregar um fluxograma aproximadamente:

depois de iniciar o processo após a solicitação de leitura, a solicitação de leitura é recebido pelo núcleo, ele irá primeiro verificar se o processo já existe no espaço do kernel os dados necessários, se ele já existir, em seguida, copiar os dados diretamente para o processo de tampão; se não houver kernel então emitir comandos para o controlador de disco, requer a leitura de dados a partir do disco, o controlador de disco para ler os dados diretamente para o buffer do kernel este passo a conclusão de DMA, o próximo passo é copiar os dados do kernel tampão processo;
se os pedidos de escrita do processo iniciado, os utilizadores também necessário copiar os dados para um tampão no interior do tampão no interior tomada de kernel, e, em seguida, através dos dados de DMA copiar a placa de rede e envia-los para fora;
você pode pensar que isto é bastante um desperdício de espaço, cada dado tempo necessário para copiar o espaço de kernel espaço do usuário, de modo que o de cópia zero aparece para resolver este problema;
no zero cópia fornece dois modos são: Modo de mmap + write, forma sendfile;

2. Memória Virtual

Todos os sistemas operacionais modernos usam memória virtual, endereço virtual para uso para substituir o endereço físico, os benefícios de fazê-lo são:
mais de 1. Um endereço virtual pode apontar para o mesmo endereço de memória física,
2. espaço de memória virtual pode ser maior que o endereço físico real disponível ;
usando a primeira característica pode ser mapeado espaço de endereços virtuais do espaço de núcleo e de usuário endereço para o mesmo endereço físico, de modo que possa ser enchido com o kernel DMA e processos do espaço do utilizador visíveis simultaneamente para um tampão substancialmente como mostrado abaixo:

omitido as relações copiar o kernel e no espaço do usuário, java também aproveitar este recurso do sistema operacional para melhorar o desempenho, olhar para o seguinte foco java em que tipo de apoio tem cópia zero.

3.mmap + modo de escrita

Use mmap + modo de escrita em vez do modo de leitura + gravação original, mmap é um método de arquivos de memória mapeada, um arquivo ou outros objectos provenientes mapeados para o endereço espaço de endereço processo, arquivo e disco no espaço de endereço virtual do processo para um endereço virtual onze pares de relação enantiomérica, de modo excepto o núcleo original pode copiar dados de memória intermédia de leitura para a memória intermédia do utilizador, mas ainda requer que o núcleo para ler copiar dados de tampão para o tampão de tomada de kernel, substancialmente como mostrado abaixo:

maneira 4.sendfile

chamada de sistema sendfile versão do kernel 2.1 é introduzido, o objetivo é simplificar o processo de transferência de dados realizada por meio de uma rede entre os dois canais. introdução de chamada do sistema sendfile, não só reduz a replicação de dados, também reduz a mudança de contexto, substancialmente como mostrado abaixo:

a transferência de dados ocorre apenas no espaço de núcleo, um comutador de contexto é reduzido, no entanto, ainda existe uma cópia, Can esta cópia tempo também pode ser omitido, Linux2.4 kernel foi melhorada, o kernel tampão informações de descrição de dados correspondente (endereço de memória, offset) correspondente à registrada entre o buffer de soquete, de modo que mesmo uma CPU espaço do kernel também salvou uma cópia;

Java zero cópia

1.MappedByteBuffer

FileChannel java nio oferta fornece um método map (), que pode criar um mapeamento de memória virtual entre um arquivo aberto e MappedByteBuffer, MappedByteBuffer herdada de ByteBuffer, semelhante a um tampão a base de memória, mas os dados do objeto elementos armazenados em um arquivo no disco, chamada para obter () método obtém os dados do disco, os dados refletem o conteúdo atual do arquivo, chamar a put () método atualiza o arquivo no disco, e modificações de arquivo feitas para outra leitor também é visível; veja o exemplo a seguir uma leitura simples, e então analisados ​​para MappedByteBuffer:

1 
2 
3 
4 
5 
6 
7 
8 
9 
10 
11 
12 
13 
14 
15 
16 
17 
18
classe pública MappedByteBufferTest { 
 
    public static void main (String [] args) throws Exception { 
        arquivo file = new File ( "D: //db.txt"); 
        longo len = file.length (); 
        byte [] ds = new byte [(int) len]; 
        MappedByteBuffer MappedByteBuffer = new FileInputStream (arquivo) .getChannel () Mapa (FileChannel.MapMode.READ_ONLY, 0,. 
                LEN); 
        para (int offset = 0; deslocamento <len; compensar ++) { 
            byte b = mappedByteBuffer.get (); 
            ds [offset] = b; 
        } 
        Scanner scan = Scanner novos (nova ByteArrayInputStream (DS)) useDelimiter (". "); 
        while (scan.hasNext ()) { 
            System.out.print (scan.next () + " ");
        } 
    } 
}

Mapa FileChannel fornecido principalmente por () para conseguir mapa, mapa () é como segue:

1 
2 
3
    public abstract MappedByteBuffer mapa (modo MapMode, 
                                         posição longa, tamanho de comprimento) 
        throws IOException;

Três parâmetros são fornecidos, MapMode, posição e tamanho; respectivamente representam:
mapmode: modos de mapeamento, opções incluem: READ_ONLY, READ_WRITE, PARTICULAR;
o Posição: posição inicial mapeado a partir do qual o número da posição de bytes;
Tamanho: a partir da posição começar a voltar quantos bytes;

Foco olhar MapMode, indique dois somente leitura e de leitura e escrita, e, claro, o modo de mapeamento solicitado por restrições de acesso FileChannel objeto, se habilitado READ_ONLY em um arquivo não tem permissão de leitura, vai jogar NonReadableChannelException; modo privado Representa copy-on-write meios de mapeamento através put () método irá resultar em quaisquer alterações feitas para produzir uma cópia privada dos dados e os dados do único copiar exemplo MappedByteBuffer pode ser visto, este processo não faz quaisquer alterações no arquivo subjacente , e uma vez que o tampão é submetido à acção de recolha de lixo (lixo recolhido), estas alterações serão perdidos; um rápido olhar para o mapa de origem (método):

1 
2 
3 
4 
5 
6 
7 
8 
9 
10 
11 
12 
13 
14 
15 
16 
17 
18 
19 
20 
21 
22 
23 
24 
25 
26 
27 
28 
29 
30 
31 
32 
33 
34 
35 
36 
37 
38 
39 
40 
41 
42 
43 
44 
45 
46 
47 
48 
49 
50 
51 
52 
53
    pública MappedByteBuffer mapa (modo MapMode, posição longa, tamanho de comprimento) 
        throws IOException 
    { 
            ...省略... 
            int pagePosition = (int) (posição% allocationGranularity); 
            longo mapPosition = posição - pagePosition; 
            longo mapSize = + tamanho pagePosition; 
            try { 
                // Se nenhuma exceção foi lançada a partir map0, o endereço é válido 
                addr = map0 (imode, mapPosition, mapSize); 
            } Catch (OutOfMemoryError x) { 
                // Um OutOfMemoryError pode indicar que já esgotou a memória 
                // modo vigor gc e re-tentativa mapa 
                System.gc ();
                try { 
                    Thread.sleep (100); 
                } Catch (InterruptedException y) { 
                    Thread.currentThread () interrupção ().; 
                } 
                Try { 
                    addr = map0 (imode, mapPosition, mapSize); 
                } Catch (OutOfMemoryError y) { 
                    // Depois de um segundo OOME, falha 
                    lançar nova IOException ( "Mapa falhou", y); 
                } 
            } 
 
            // No Windows, e potencialmente outras plataformas, precisamos de uma aberto 
            descritor // arquivo para algumas operações de mapeamento. 
            FileDescriptor MFD; 
            experimentar {
                mfd = nd.duplicateForMapping (fd); 
            } Catch (IOE IOException) { 
                unmap0 (addr, mapSize); 
                jogue OIE; 
            } 
 
            Afirmam (IOStatus.checkAll (addr)); 
            Assert (addr% allocationGranularity == 0); 
            int iSize = (int) de tamanho; 
            Unmapper hum = novo Unmapper (addr, mapSize, iSize, MFD); 
            if ((gravável) || (imode == MAP_RO)!) { 
                return Util.newMappedByteBufferR (iSize, 
                                                 addr + pagePosition, 
                                                 MFD, 
                                                 um);
            } Else { 
                retornar Util.newMappedByteBuffer (iSize, 
                                                addr + pagePosition, 
                                                MFD, 
                                                um); 
            } 
     }

Cerca de significado adquirido pelo endereço método nativo de memória mapeada, e se isso falhar, a re-mapeamento manualmente gc; o último endereço de memória mapeado por instanciar um MappedByteBuffer, em si MappedByteBuffer é uma classe abstrata, de fato, não é um exemplo real de palavras fora de DirectByteBuffer;

2.DirectByteBuffer

DirectByteBuffer herdou MappedByteBuffer, você pode adivinhar o nome abre alguma memória direta, não ocupa espaço de memória JVM; aquele traçado por FileChannel MappedByteBuffer real DirectByteBuffer também, é claro, além de Desta forma, você pode abrir manualmente algum espaço:

1
ByteBuffer directByteBuffer = ByteBuffer.allocateDirect (100);

Como espaço de memória direta aberta de 100 bytes;

transporte 3.Channel-to-Channel

Muitas vezes precisa transferir arquivos de um local para outro local, desde FileChannel transferTo () método é usado para aumentar a eficiência da transmissão, primeiro olhar para um exemplo simples:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public class ChannelTransfer {
    public static void main(String[] argv) throws Exception {
        String files[]=new String[1];
        files[0]="D://db.txt";
        catFiles(Channels.newChannel(System.out), files);
    }
 
    private static void catFiles(WritableByteChannel target, String[] files)
            throws Exception {
        for (int i = 0; i < files.length; i++) {
            FileInputStream fis = new FileInputStream(files[i]);
            FileChannel channel = fis.getChannel();
            channel.transferTo(0, channel.size(), target);
            channel.close();
            fis.close();
        }
    }
}

通过FileChannel的transferTo()方法将文件数据传输到System.out通道,接口定义如下:

1
2
3
    public abstract long transferTo(long position, long count,
                                    WritableByteChannel target)
        throws IOException;

几个参数也比较好理解,分别是开始传输的位置,传输的字节数,以及目标通道;transferTo()允许将一个通道交叉连接到另一个通道,而不需要一个中间缓冲区来传递数据;
注:这里不需要中间缓冲区有两层意思:第一层不需要用户空间缓冲区来拷贝内核缓冲区,另外一层两个通道都有自己的内核缓冲区,两个内核缓冲区也可以做到无需拷贝数据;

Netty零拷贝

netty提供了零拷贝的buffer,在传输数据时,最终处理的数据会需要对单个传输的报文,进行组合和拆分,Nio原生的ByteBuffer无法做到,netty通过提供的Composite(组合)和Slice(拆分)两种buffer来实现零拷贝;看下面一张图会比较清晰:

TCP层HTTP报文被分成了两个ChannelBuffer,这两个Buffer对我们上层的逻辑(HTTP处理)是没有意义的。 但是两个ChannelBuffer被组合起来,就成为了一个有意义的HTTP报文,这个报文对应的ChannelBuffer,才是能称之为”Message”的东西,这里用到了一个词”Virtual Buffer”。
可以看一下netty提供的CompositeChannelBuffer源码:

1
2
3
4
5
6
7
8
9
10
11
12
13
public class CompositeChannelBuffer extends AbstractChannelBuffer {
 
    private final ByteOrder order;
    private ChannelBuffer[] components;
    private int[] indices;
    private int lastAccessedComponentId;
    private final boolean gathering;
 
    public byte getByte(int index) {
        int componentId = componentId(index);
        return components[componentId].getByte(index - indices[componentId]);
    }
    ...省略...

components用来保存的就是所有接收到的buffer,indices记录每个buffer的起始位置,lastAccessedComponentId记录上一次访问的ComponentId;CompositeChannelBuffer并不会开辟新的内存并直接复制所有ChannelBuffer内容,而是直接保存了所有ChannelBuffer的引用,并在子ChannelBuffer里进行读写,实现了零拷贝。

其他零拷贝

RocketMQ的消息采用顺序写到commitlog文件,然后利用consume queue文件作为索引;RocketMQ采用零拷贝mmap+write的方式来回应Consumer的请求;
同样kafka中存在大量的网络数据持久化到磁盘和磁盘文件通过网络发送的过程,kafka使用了sendfile零拷贝方式;

总结

零拷贝如果简单用java里面对象的概率来理解的话,其实就是使用的都是对象的引用,每个引用对象的地方对其改变就都能改变此对象,永远只存在一份对象。高质量编程视频shangyepingtai.xin

发布了122 篇原创文章 · 获赞 47 · 访问量 3万+

Acho que você gosta

Origin blog.csdn.net/fengzongfu/article/details/105341878
Recomendado
Clasificación