Suponha que haja dois encadeamentos, que se comunicam enviando mensagens de dados de forma assíncrona. Cada encadeamento possui algum tipo de fila de mensagens.
Minha pergunta é de nível muito baixo: qual pode ser a maneira mais eficiente de gerenciar a memória? Eu posso pensar em várias soluções:
- Remetente cria o objeto via
new
. Chamadas do receptordelete
. - Pool de memória (para transferir a memória de volta para o remetente)
- Coleta de lixo (por exemplo, Boehm GC)
- (se os objetos forem pequenos o suficiente) copie por valor para evitar a alocação de heap completamente
1) é a solução mais óbvia, por isso vou usá-lo como protótipo. As chances são de que já é bom o suficiente. Independentemente do meu problema específico, pergunto-me qual técnica é mais promissora se você está otimizando o desempenho.
Eu esperava que o pool fosse teoricamente o melhor, especialmente porque você pode usar conhecimento extra sobre o fluxo de informações entre os threads. Receio, no entanto, que também seja o mais difícil de acertar. Muita sintonia ... :-(
A coleta de lixo deve ser bem fácil de ser adicionada posteriormente (após a solução 1), e eu esperaria que ele funcionasse muito bem. Então, acho que é a solução mais prática se 1) for muito ineficiente.
Se os objetos forem pequenos e simples, copiar por valor pode ser o mais rápido. No entanto, receio que isso force limitações desnecessárias à implementação das mensagens suportadas, por isso quero evitá-lo.
fonte
unique_ptr
, eu acho que você quer dizershared_ptr
. Mas, embora não haja dúvida de que o uso de um ponteiro inteligente seja bom para o gerenciamento de recursos, isso não muda o fato de você estar usando alguma forma de alocação e desalocação de memória. Eu acho que essa pergunta é de nível mais baixo.O maior impacto no desempenho ao comunicar um objeto de um encadeamento para outro é a sobrecarga de agarrar um cadeado. Isso é da ordem de vários microssegundos, que é significativamente mais que o tempo médio que um par de
new
/delete
leva (da ordem de cem nanossegundos). Asnew
implementações sãs tentam evitar o travamento a quase todo custo para evitar o impacto no desempenho.Dito isso, você deseja garantir que não precise bloquear bloqueios ao comunicar os objetos de um encadeamento para outro. Eu conheço dois métodos gerais para conseguir isso. Ambos funcionam apenas unidirecionalmente entre um remetente e um destinatário:
Use um buffer de anel. Ambos os processos controlam um ponteiro nesse buffer, um é o ponteiro de leitura e o outro é o ponteiro de gravação.
O remetente primeiro verifica se há espaço para adicionar um elemento comparando os ponteiros, depois adiciona o elemento e depois incrementa o ponteiro de gravação.
O receptor verifica se há um elemento para ler comparando os ponteiros, depois lê o elemento e depois incrementa o ponteiro de leitura.
Os ponteiros precisam ser atômicos, pois são compartilhados entre os threads. No entanto, cada ponteiro é modificado apenas por um thread, o outro precisa apenas de acesso de leitura ao ponteiro. Os elementos no buffer podem ser ponteiros, o que permite dimensionar facilmente seu buffer de anel para um tamanho que não fará o remetente bloquear.
Use uma lista vinculada que sempre contém pelo menos um elemento. O receptor possui um ponteiro para o primeiro elemento, o remetente possui um ponteiro para o último elemento. Esses ponteiros não são compartilhados.
O remetente cria um novo nó para a lista vinculada, configurando seu
next
ponteiro paranullptr
. Em seguida, ele atualiza onext
ponteiro do último elemento para apontar para o novo elemento. Por fim, ele armazena o novo elemento em seu próprio ponteiro.O receptor observa o
next
ponteiro do primeiro elemento para ver se há novos dados disponíveis. Nesse caso, ele exclui o primeiro elemento antigo, avança seu próprio ponteiro para apontar para o elemento atual e começa a processá-lo.Nessa configuração, os
next
ponteiros precisam ser atômicos, e o remetente deve certificar-se de não desreferenciar o segundo último elemento depois de definir onext
ponteiro. A vantagem é, obviamente, que o remetente nunca precisa bloquear.Ambas as abordagens são muito mais rápidas do que qualquer abordagem baseada em bloqueio, mas exigem uma implementação cuidadosa para serem acertadas. E, é claro, eles exigem atomicidade de hardware nativo de gravações / cargas de ponteiros; se sua
atomic<>
implementação usa um bloqueio internamente, você está praticamente condenado.Da mesma forma, se você tem vários leitores e / ou escritores, está praticamente condenado: você pode tentar criar um esquema sem bloqueio, mas será difícil implementá-lo na melhor das hipóteses. Essas situações são muito mais fáceis de lidar com uma trava. No entanto, depois de abrir um cadeado, você pode parar de se preocupar com
new
/delete
desempenho.fonte