Tuesday 5 September 2017

Ring buffer lmax forex


I8217m vai dar um rápido desaceleração sobre como colocamos mensagens no buffer do anel (a estrutura de dados do núcleo dentro do Disruptor) sem usar bloqueios. Antes de ir mais longe, it8217s valem uma leitura rápida da publicação Trish8217s. O que dá uma visão geral de alto nível do buffer de anel e como ele funciona. Os pontos salientes desta postagem são: o buffer do anel não é nada além de uma grande matriz. Todos os 8220pointers8221 no buffer do anel (também conhecido como seqüências ou cursores) são Java longs (números assinados de 64 bits) e contagem para sempre para sempre. (Don8217t pânico 8211, mesmo em 1.000.000 de mensagens por segundo, levaria a melhor parte de 300.000 anos para envolver os números de seqüência). Esses ponteiros são, em seguida, 8220mod8217ed8221 pelo tamanho do buffer do anel para descobrir qual índice de matriz contém a entrada fornecida. Para o desempenho, realmente forçamos o tamanho do buffer do anel a ser o próximo poder de dois maiores do que o tamanho que você pede, e então podemos usar uma máscara de bits simples para descobrir o índice da matriz. Estrutura básica de buffer de anel AVISO: em termos de organização do código. Muito do que I8217m a ponto de dizer é uma simplificação. Conceitualmente, penso que isso é mais fácil de entender a partir de como eu descrevo. O buffer de anel mantém dois ponteiros, 8220next8221 e 8220cursor8221: Na imagem acima, um buffer de anel de tamanho 7 (hey, você sabe como esses diagramas desenhados à mão funcionam às vezes) tem slots 0 a 2 preenchidos com dados. O próximo ponteiro refere-se ao primeiro slot livre. O cursor se refere ao último slot preenchido. Em um buffer de anel ocioso, eles serão adjacentes um ao outro como mostrado. Reivindicar um slot A API Disruptor tem uma sensação transacional sobre isso. Você 8220claim8221 um slot no buffer do anel, então você escreve seus dados no slot reivindicado, então você 8220commit8221 os dados. Let8217s assumem que aí um segmento que deseja colocar a letra 8220D8221 no buffer do anel. Ele reivindica um slot. A operação de reivindicação não é mais do que uma operação CAS 8220get-and-incre8221 no próximo ponteiro. Ou seja, esse segmento (let8217s chama isso de thread D) simplesmente faz um get-and-increment atômico que move o próximo ponteiro para 4 e retorna 3. O Thread D agora reivindicou o slot 3: Next, outro thread (thread E) reivindica Slot 4 da mesma maneira: Cometer as escritas Agora, os tópicos D e E podem, de forma segura e simultânea, escrever seus dados em seus respectivos slots. Mas let8217s dizem que o segmento E termina primeiro por algum motivo8230 Thread E tenta cometer sua gravação. A operação de confirmação consiste em uma operação CAS em um loop ocupado. Uma vez que o segmento E reivindicou o slot 4, ele faz um CAS esperando que o cursor chegue a 3 e depois a ajuste para 4. Mais uma vez, esta é uma operação atômica. Assim, como o buffer do anel está no momento, o segmento E vai girar porque o cursor está configurado para 2 e ele (thread E) está aguardando que o cursor esteja em 3. Agora, o thread D comete. Faz uma operação CAS e define o cursor como 3 (o slot que reivindicou) se o cursor estiver atualmente em 2. O cursor está atualmente em 2, então o CAS é bem-sucedido e o commit é bem-sucedido. Neste ponto, o cursor foi atualizado para 3 e todos os dados até esse número de seqüência estão disponíveis para leitura. esse é um ponto importante. Sabendo como 8220full8221 o buffer de anel é 8211, isto é, quanto tempo foi escrito, qual número de seqüência representa a escrita mais alta, etc. O 8211 é apenas uma função do cursor. O próximo ponteiro é usado apenas para o protocolo de gravação transacional. O passo final no quebra-cabeça é fazer a escrita do fio E8217 visível. Thread E ainda está girando tentando fazer uma atualização atômica do cursor de 3 a 4. Agora o cursor está em 3, sua próxima tentativa será bem-sucedida: a ordem que as gravações são visíveis é definida pela ordem em que os segmentos reivindicam slots em vez de Na ordem em que eles cometeram suas escritas, mas se você imaginar que esses tópicos estão puxando mensagens de uma camada de mensagens de rede, isso realmente não é diferente das mensagens que chegam em momentos ligeiramente diferentes, ou os dois segmentos de corrida para o slot reivindicam em uma ordem diferente. Então só temos isso. It8217s é um algoritmo muito simples e elegante. (OK, eu admito que eu estava fortemente envolvido em sua criação). Os escritos são atômicos, transacionais e sem bloqueio, mesmo com vários tópicos de escrita. (Obrigado a Trish pela inspiração para os diagramas desenhados à mão) Dissecando o Disruptor: O que é tão especial sobre um buffer de anel Recentemente, abrimos o Disruptor LMAX. A chave para o que torna nossa troca tão rápida. Por que abrimos a fonte. Bem, nós percebemos que a sabedoria convencional em torno da programação de alto desempenho é. Um pouco errado. Nós apresentamos uma maneira melhor e mais rápida de compartilhar dados entre threads e seria egoísta não compartilhá-lo com o mundo. Além disso, nos faz parecer morto inteligente. No site, você pode baixar um artigo técnico explicando o que é o Disruptor e por que é tão inteligente e rápido. Eu até consigo um crédito escrito, o que é gratificante quando tudo o que eu realmente fiz foi inserir vírgulas e frases de re-frase que eu não entendi. No entanto, acho que tudo é um pouco demais para digerir tudo de uma só vez, então vou explicar isso em pedaços menores, conforme o meu público da NADD. Primeiro - o buffer do anel. Inicialmente, tive a impressão de que o Disruptor era apenas o buffer do anel. Mas eu percebi que, enquanto esta estrutura de dados está no cerne do padrão, o bit inteligente sobre o Disruptor está controlando o acesso a ele. O que é um buffer de anel Bem, ele faz o que diz na lata - é um anel (é circular e envolvente), e você usa isso como um buffer para passar coisas de um contexto (um fio) para outro: (OK , Eu desenhei no Paint. Estou experimentando estilos de esboço e esperando que meu TOC não retroceda e exija círculos perfeitos e linhas retas em ângulos precisos). Então, basicamente, é uma matriz com um ponteiro para o próximo slot disponível. À medida que você continua a preencher o buffer (e a leitura presumível dele também), a sequência continua aumentando, envolvendo o anel: Para encontrar o slot na matriz que a seqüência atual aponta para você usar uma operação de modificação: array de matriz de matriz de sequência Índice Então, para o buffer de anel acima (usando a sintaxe de modificação Java): 12 10 2. Fácil. Na verdade, foi um acidente total que a imagem tinha dez slots. Os poderes de dois funcionam melhor porque os computadores pensam em binário. Então, se você olhar a entrada do Wikipedias em Buffers Circulares. Você verá uma grande diferença na maneira como implementamos o nosso - não temos um indicador para o final. Nós só temos o próximo número de seqüência disponível. Isso é deliberado - a razão original pela qual escolhemos um buffer de anel foi para que possamos suportar mensagens confiáveis. Nós precisávamos de uma loja das mensagens que o serviço enviara, então quando outro serviço enviou um nak para dizer que eles não receberam algumas mensagens, seria capaz de reenviá-las. O buffer de anel parece ideal para isso. Ele armazena a seqüência para mostrar onde o final do buffer é, e se conseguir um nak, ele pode reproduzir tudo, desde esse ponto até a seqüência atual: A diferença entre o buffer do anel como o implementamos e as filas que tínhamos tradicionalmente Usando, é que não consumimos os itens no buffer - eles ficam lá até que eles sejam escritos demais. É por isso que não precisamos do ponteiro final que você vê na versão Wikipedia. Decidir se o seu OK para embrulhar ou não é gerenciado fora da própria estrutura de dados (isso faz parte do comportamento do produtor e do consumidor - se você não pode esperar para que eu chegue a blogar sobre isso, confira o site Disruptor). E é tão bom porque. Então, usamos essa estrutura de dados porque nos dá um bom comportamento para mensagens confiáveis. Acontece que tem algumas outras características agradáveis. Em primeiro lugar, é mais rápido do que algo como uma lista vinculada porque é uma matriz e possui um padrão previsível de acesso. Isso é bom e compatível com o CPU - no nível de hardware, as entradas podem ser pré-carregadas, então a máquina não retorna constantemente à memória principal para carregar o próximo item no ringue. Em segundo lugar, é uma matriz e você pode pré-alocar a frente, tornando os objetos efetivamente imortais. Isso significa que o coletor de lixo não tem praticamente nada para fazer aqui. Novamente, ao contrário de uma lista vinculada que cria objetos para cada item adicionado à lista - estes, então, todos precisam ser limpos quando o item não estiver mais na lista. As peças que faltam eu não falei sobre como evitar o envolvimento do anel, ou detalhes sobre como escrever coisas e ler as coisas do buffer do anel. Você também notou que eu tenho comparado isso com uma estrutura de dados como uma lista vinculada, que eu não acho que ninguém acredite é a resposta para os problemas mundiais. A parte interessante vem quando você compara o Disruptor com uma implementação como uma fila. As filas normalmente cuidam de tudo como o início e o fim da fila, adicionando e consumindo itens, e assim por diante. Todas as coisas que eu realmente não toquei com o buffer do anel. Isso porque o buffer do anel não é responsável por essas coisas, mudamos essas preocupações fora da estrutura de dados. Para mais detalhes você vai ter que ler o artigo ou verificar o código. Ou assista Mike e Martin no QCon San Francisco no ano passado. Ou espere que eu tenha cinco minutos para recuperar a cabeça ao resto. Se você não consumir elementos do seu buffer de anel, então você mantê-los acessíveis e impedindo que eles sejam desalocados. Isso pode, obviamente, ter um efeito adverso sobre a produção e a latência do coletor de lixo. Escrever referências em diferentes locais em seu buffer de anel incorre na barreira de gravação, o que também pode afetar adversamente a taxa de transferência e a latência. Pergunto-me o que os trade-offs estão em relação a essas desvantagens e quando entram em jogo. No que diz respeito ao uso da memória, nenhum troco real é feito pelo Disruptor. Ao contrário de uma fila, você tem uma escolha sobre como usar a memória. Se a solução for um sistema macio em tempo real, reduzir as pausas do GC é primordial. Portanto, você pode reutilizar as entradas no buffer do anel, p. Ex. Copiando conjuntos de bytes de e para os buffers de IO de rede dentro e fora do buffer de anel (nosso padrão de uso mais comum). Como a quantidade de memória usada pelo sistema permanece estática, reduz a freqüência de coleta de lixo. Também é possível implementar uma Entrada que contém uma referência a um objeto imutável. No entanto, nessa situação, pode ser necessário que o consumidor anule o objeto da mensagem para reduzir a quantidade de memória que precisa ser promovida pelo Eden. Portanto, é necessário um pouco mais de esforço do programador para construir a solução mais adequada. Acreditamos que a flexibilidade proporcionada justifica esse pequeno esforço extra. Considerando a barreira de escrita, o objetivo principal do Disruptor é passar mensagens entre threads. Não fazemos compromissos quanto à ordenação ou consistência, portanto, é necessário usar barreiras de memória nos locais apropriados. Nós fizemos o nosso melhor para manter isso no mínimo. No entanto, somos muitas vezes mais rápidas do que as alternativas populares, pois a maioria delas usa bloqueios fornecem consistência. Como esta abordagem se compara à abordagem do Pool e outras abordagens usadas aqui: cacm. acm. orgmagazines20113105308-data-structures-in-the-multicore-agefulltext Por que não usar um Pool em vez de uma fila É o requisito LIFO essencial Infelizmente eu não posso ler Esse artigo porque eu não tenho uma conta nesse site. FIFO (não LIFO) é absolutamente essencial - nossa troca depende da ordenação previsível, e se você tocar os mesmos eventos nela, você sempre obterá o mesmo resultado. O Disruptor garante esta ordem sem tomar as penalidades de desempenho geralmente associadas às estruturas FIFO. Flying Frog Consultancy disse que, se você não consumir elementos do seu buffer de anel, você mantê-los acessíveis e impedindo que eles sejam desalocados. Isso pode, obviamente, ter um efeito adverso na taxa de transferência e latência do coletor de lixo. O ponto inteiro é não invocar o coletor de lixo. O padrão Disruptor permite que os dados sejam passados ​​entre CPU39s praticamente o máximo teórico do hardware - foi bem pensado I39m novo no padrão Disruptor. Eu tenho uma pergunta muito básica. Como eu adiciono mensagens ao Buffer de Anel de um Multi Threaded Producer. Se as chamadas de adicionar ao Buffer do Anel se sincronizarem Geralmente, o objetivo não é executar qualquer coisa multi-threaded. Produtores e consumidores devem ser monocatados. Mas você pode ter mais de um produtor: mechanitis. blogspot201107dissecting-disruptor-writing-to-ring. html - esta é uma publicação ligeiramente fora de data, as convenções de nomeação mudaram e a barreira do produtor agora é gerenciada pelo buffer do anel, mas Eu acho que este pode ser um bom lugar para começar a pensar sobre como resolver seu problema. Obrigado por interessar o artigo. Não tenho certeza se eu entendo, mas o conceito de manter a memória e reutilizar objetos já alocados para evitar pausas de GC não parece ser novo. Como o buffer do anel é diferente de um pool de objetos. Evitar o GC não é o objetivo principal do RingBuffer, embora ele ajude a velocidade do Disruptor. As características interessantes do RingBuffer são o FIFO do itann, e ele permite alguns lotes realmente bons quando você lê. O RingBuffer não é o molho secreto no desempenho do Disruptor, de fato, na versão atual do Disruptor, você não precisa disso. Vale a pena notar que não há nada de novo no Disruptor, de fato, muitas das idéias existem há anos. Mas eu não acho que haja outras estruturas em Java que combinem esses conceitos desta forma para dar o tipo de desempenho que vemos ao usar o Disruptor. Oi Trisha, de alguns dias eu descobri a arquitetura LMAX e disruptor também, não é tão claro para o meu exatamente como os consumidores extraem as mensagens do RingBuffer e como exatamente um consumidor, por exemplo, C1, sabe quais mensagens são para ele e não para Outro consumidor, C2. Graças Sorin. Na verdade, as mensagens são para ambos os consumidores. O comportamento padrão é que todos os consumidores (ou EventHandlers como estão agora) lêem todas as mensagens no RingBuffer. Se você tem diferentes tipos de eventos que são manipulados por consumidores diferentes, então é até o consumidor decidir se deve ignorar o evento ou não. Então, se o C1 lidar com todas as mensagens azuis e C2 manipula todos os vermelhos (por simplificação, é claro), então, o C1 precisa verificar a mensagem azul antes de prosseguir. Em termos de extração das mensagens - você não. Mensagens ao vivo no buffer do anel para serem lidas por (e processadas por) todos os consumidores, até que cada consumidor tenha feito o que precisa fazer com ele (ou seja, cada consumidor aumentou seu número de seqüência para, pelo menos, o número da mensagem), então ele obterá Escrito por excesso quando o anel envolve. Se você quiser fazer algo com essa mensagem, basta lê-lo e faça o que quiser com ele, mesmo que isso seja transmitido para outro Disruptor ou outra parte do sistema. Oi Trisha, Obrigado por esta e outras apresentações. Eu tenho uma pergunta sobre o disruptor que é bastante básico. Os consumidores (processadores de eventos) não estão implementando nenhuma das interfaces Callable ou Runnable que implementam EventHandler. Então, como elas podem ser executadas em paralelo, então, por exemplo, eu tenho uma implementação de disruptor onde há um padrão de diamante como esse P1 - c1, c2, C3 - c4 - c5 Onde c1 a c3 pode funcionar em paralelo após p1 e C4 e C5 funcionam após eles. Então, convencionalmente I39d tem algo como isto (com P1 e C1-C5 sendo runnablescallables) Mas, no caso do Disruptor, nenhum dos manipuladores de eventos implementa Runnable ou Callable, então, como a estrutura do disruptor acaba executando-os em paralelo. Tome o seguinte sceanrio: Meu consumidor C2 requer para fazer uma chamada de webservice para alguma anotação para o Evento. Na SEDA, posso iniciar 10 threads para esses pedidos de 10 C2 para puxar a mensagem da fila, fazer Webservice Call e atualizar a próxima fila SEDA e isso irá garantir que eu não faça Espera sequencialmente por uma resposta do serviço web para cada um dos 10 pedidos, onde, como neste caso, meu processador de eventos C2 (if) sendo a única instância esperaria sequencialmente para pedidos de 10 C2. Em Java, criar uma matriz de objetos Java não aloca memória para os objetos. Apenas aloca memória para referências aos objetos. Como uma série de referências de objeto ajudam a melhorar a eficiência de armazenamento em cache da CPU porque os objetos reais ainda estão dispersos no heap. Você está absolutamente certo, e é por isso que no caso LMAX temos uma matriz de arrays de bytes, não uma matriz de objetos - pelo menos para As instâncias de alto desempenho do disruptor. Uma série de referências de objeto ainda é valiosa em muitos casos, mas, como você diz, não necessariamente lhe dá a afinidade da linha de cache. Isso surgiu várias vezes nas discussões do Grupo do Google (groups. googleforumforumlmax-disruptor), acho que você encontrará discussões mais detalhadas lá. Eu sei que estou muito atrasado, mas uma série de arrays de bytes é, por definição, uma variedade de objetos. As matrizes de bytes bidimensionais não garantem a localidade, especialmente após uma passagem de GC (as matrizes unidimensionais que compõem o bidimensional são objetos na pilha, para que eles se movam). A localidade dentro das matrizes de bytes unidimensionais pode ser preservada, mas não em toda a matriz bidimensional (isto é, preserva intra-array, perdeu inter-array). Acabo de passar uma boa parte do meu dia passando pelo seu disruptor e posso ver do seu exemplo as centenas de milhões de operações por segundo e também da apresentação de 6 milhões de negócios por segundo. Acabei de escrever um exemplo com o produtor recuperando o pedido de operação de um serviço web com 2 consumidores um para empacotamento e outro para lógica de negócios e meu throughput é um pouco mais de 1000 ops por segundo fonte (githubejosiahactivemq-vs-distruptor). Minha pergunta faz parte de Suas métricas incluem operações de IO de outros consumidores, como as de (Journalling, replicação, serialização, etc.) Não, as métricas citadas são apenas para a lógica de negócios, não para IO, etc. Eu acho que se você verificar o histórico do Google Group, você encontrará mais Informações específicas sobre o que foi medido e como, esta pergunta já surgiu antes:

No comments:

Post a Comment