Pergunta

Eu estou construindo um banco de dados de brinquedo em C # para aprender mais sobre compilador, otimizador, e tecnologia de indexação.

Eu quero manter o máximo de paralelismo entre (pelo menos ler) pedidos de trazendo páginas para o pool de buffer, mas estou confuso sobre a melhor forma de fazer isso em .NET.

Aqui estão algumas opções e os problemas que eu me deparei com cada um:

  1. Use System.IO.FileStream eo método BeginRead

    Mas, a posição no arquivo não é um argumento para BeginRead, é uma propriedade do FileStream (definido através do método Seek), então eu só pode emitir um pedido de cada vez e tem que bloquear o fluxo para a duração. (Ou eu? A documentação não é clara sobre o que aconteceria se eu segurei o bloqueio apenas entre as chamadas Seek e BeginRead mas liberado antes de chamar EndRead. Alguém sabe?) Eu sei como fazer isso, eu não sou apenas certo é o melhor caminho.

  2. Parece haver uma outra maneira, centrada em torno da estrutura System.Threading.Overlapped e P \ Chamar para a função ReadFileEx em kernel32.dll.

    Infelizmente, há uma escassez de amostras, especialmente em idiomas gerenciados. Esta rota (se ele pode ser feito para trabalhar em tudo), aparentemente, também envolve o método ThreadPool.BindHandle e os fios de conclusão IO no pool de threads. Tenho a impressão de que este é o caminho sancionada de lidar com esse cenário sob janelas, mas eu não entendo isso e não consigo encontrar um ponto de entrada para a documentação que é útil para os não iniciados.

  3. Algo mais?

  4. Em um comentário, jacob sugere a criação de um novo FileStream para cada leitura em vôo.

  5. Leia o arquivo inteiro na memória.

    Isso poderia funcionar se o banco de dados era pequeno. A base de código é pequeno, e há uma abundância de outras ineficiências, mas o banco de dados em si não é. Eu também quero ter certeza que eu estou fazendo toda a contabilidade necessária para lidar com um grande banco de dados (o que acaba por ser uma grande parte da complexidade: paginação, ordenação externa, ...) e eu estou preocupado que pode ser muito fácil de enganar acidentalmente.

Editar

Clarificação de porque eu sou suspeito com uma solução de 1: segurando um único bloqueio todo o caminho de BeginRead para EndRead significa que eu preciso para bloquear qualquer pessoa que queira iniciar uma leitura só porque outra leitura está em andamento. Que se sente mal, porque o segmento de iniciar a nova leitura pode ser capaz (em geral) para fazer mais algum trabalho antes que os resultados se tornam disponíveis. (Na verdade, apenas escrevendo isso me levou a pensar em uma nova solução, eu coloquei como uma nova resposta.)

Foi útil?

Solução

O que fizemos foi para escrever uma pequena camada em torno de portas de conclusão de E / S, ReadFile, e status GetQueuedCompletion em C ++ / CLI, e em seguida, chamar de volta para C # quando a operação foi concluída. Nós escolhemos esta rota ao longo BeginRead eo # assíncrono padrão de operação c para fornecer mais controle sobre os buffers usados ??para ler a partir do arquivo (ou socket). Este foi um ganho muito grande desempenho ao longo da abordagem puramente gerenciado que aloca novo byte [] na pilha com cada leitura.

Além disso, há muito mais completa C ++ exemplos do uso de portas IO de conclusão para fora nas interwebs

Outras dicas

Eu não estou certo que eu vejo por que a opção 1 não iria trabalhar para você. Tenha em mente que você não pode ter dois tópicos diferentes tentando usar o mesmo FileStream ao mesmo tempo - isso irá certamente causar-lhe problemas. BeginRead / EndRead destina-se a deixar o seu código de continuar a executar enquanto a operação potencialmente caro IO leva lugares, para não permitir que algum tipo de acesso multi-threaded para um arquivo.

Então, eu sugeriria que você procura e, em seguida, fazer uma BeginRead.

E se você carregou o recurso (dados de arquivo ou qualquer outro) para a memória primeiro e depois compartilhá-la entre segmentos? Uma vez que é um pequeno db. -. Você não vai ter tantos problemas para lidar com

Use abordagem nº 1, e

  1. Quando uma solicitação chega, take bloqueio A. usá-lo para proteger uma fila de pedidos pendentes ler. Adicioná-lo à fila e retornar algum novo resultado assíncrona. Se isso resulta na primeira adição à fila, chamar o passo 2 antes de retornar. bloqueio Release A antes de retornar.

  2. Quando A completa ler (ou chamados a passo 1), tome bloqueio A. usá-lo para proteger estalando um pedido de leitura da fila. Tome bloqueio B. usá-lo para proteger o Seek -> BeginRead -> sequência EndRead. bloqueio de liberação B. atualizar o resultado assíncrono criado por etapa 1 para esta operação de leitura. (Uma vez que uma operação de leitura concluída, chamar isso de novo).

Isto resolve o problema de não bloquear qualquer segmento que começa uma leitura só porque outra leitura está em andamento, mas as sequências ainda lê modo que a posição atual do fluxo de arquivo não ficar confuso.

Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top