Pergunta

Eu tenho uma situação em um código onde existe uma enorme função que analisa registros linha por linha, valida e grava em outro arquivo.

No caso de existirem erros no arquivo, ele chama outra função que rejeita o registro e escreve a razão rejeitar.

Devido a um vazamento de memória no programa, ele trava com SIGSEGV. Uma solução para o tipo de "Restart" o arquivo de onde ele caiu, foi escrever o último registro processado para um arquivo simples.

Para conseguir isso o número recorde atual nas necessidades de processamento de loop a ser gravados em um arquivo. Como posso ter certeza de que os dados são substituídos no arquivo dentro do loop?

Does usando fseek a primeira posição / retroceder dentro de um loop degradar o desempenho?

O número de registros pode ser muito, às vezes (até 500K).

Graças.

EDIT: A fuga de memória já foi corrigido. A solução de reinício foi sugerido como uma medida de segurança adicional e meios para fornecer um mecanismo de reinicialização, juntamente com uma solução de n registos SKIP. Desculpe por não mencioná-lo mais cedo.

Foi útil?

Solução

Quando confrontados com este tipo de problema, você pode adotar um dos dois métodos:

  1. o método que você sugeriu : para cada registro que você lê, escrever o número do registro (ou a posição retornado por ftell no arquivo de entrada) a uma separado marcador arquivo. Para garantir que você retomar exatamente onde você parou, para não introduzir registros duplicados, você deve fflush após cada write (tanto bookmark e saída / rejeitar arquivos). Este, e as operações de gravação sem buffer em geral, diminuir o típico (sem -failure) cenário significativamente. Para completar amor, nota que você tem três maneiras de escrever para o seu arquivo de favoritos:
    • fopen(..., 'w') / fwrite / fclose - extremamente lento
    • rewind / truncate / fwrite / fflush - marginalmente mais rápido
    • rewind / fwrite / fflush - um pouco mais rápido ; você pode pular truncate uma vez que o número do registro (ou posição ftell) será sempre tão longo ou maior que o número recorde anterior (ou posição ftell), e irá substituí-lo completamente, desde que você truncar o arquivo uma vez na inicialização (este responde a sua pergunta original)
  2. assumir tudo vai correr bem na maioria dos casos ; ao retomar após a falha, simplesmente contar o número de registros já de saída (saída normal mais rejeitos), e ignorar um número equivalente de registros do arquivo de entrada.
    • Isso mantém o típico (sem falha) cenários muito rápido, sem comprometer significativamente o desempenho em caso de cenários de currículo-pós-falha.
    • Você não precisa de arquivos fflush, ou pelo menos não com tanta frequência. Você ainda precisa fflush o arquivo de saída principal antes de mudar para gravar o arquivo de rejeitos, e fflush o arquivo de rejeições antes de voltar a escrever para o arquivo de saída principal (provavelmente algumas centenas ou milhares de vezes para uma entrada de 500k-registro.) Simplesmente remover a última linha não terminada da saída / rejeitar arquivos, tudo até que a linha será consistente.

Eu recomendo fortemente o método # 2 . A escrita implicava pelo método nº 1 (qualquer das três possibilidades) é extremamente caro em comparação com qualquer adicional (tamponada) lê exigido pelo método # 2 (fflush pode demorar vários milissegundos; Multiplique isso por 500k e você ganhar minutos - enquanto a contagem do número de linhas em um arquivo de 500k-registro leva apenas alguns segundos e, além do mais, o sistema de arquivos de cache está trabalhando com, e não contra você sobre isso.)


Editar Só queria esclarecer as etapas exatas que você precisa para implementar o método 2:

  • quando estiver escrevendo para os arquivos de saída e rejeita respectivamente Você só precisa nivelado quando se muda de escrever para um arquivo para escrever para outro. Considere o seguinte cenário como ilustração do ncessity de fazer essas ondas de-on-file-chave:

    • suponha que você escrever 1000 registros para o arquivo de saída principal, em seguida,
    • você tem que escrever uma linha ao arquivo rejeições, sem rubor manualmente o arquivo de saída principal em primeiro lugar, em seguida,
    • você escrever mais de 200 linhas para o arquivo de saída principal, sem rubor manualmente os rejeitados arquivo primeiro, depois
    • o tempo de execução automaticamente libera o arquivo de saída principal para você, porque você tem acumulado um grande volume de dados nos buffers para o arquivo de saída principal, ou seja, 1.200 registros
      • e o tempo de execução ainda não liberado automaticamente o arquivo de rejeitos para o disco para você, como o arquivo de buffer contém apenas um registro, que não é um volume suficiente para automaticamente alinhada
    • seu programa trava neste momento
    • retomar e conte 1200 registros no arquivo de saída principal (o tempo de execução corou aqueles para fora para você), mas 0 (!) Registros no arquivo rejeitados (não corada).
    • você volte a processar os Inpuarquivo de t no registro # 1201, supondo que você só tinha 1.200 registros processados ??com sucesso para o arquivo de saída principal; o registro rejeitado seria perdido, eo registro válido 1200'th será repetido
    • você não quer isso!
  • Agora considere rubor manualmente após a saída de comutação / rejeitar arquivos:
    • suponha que você escrever 1000 registros para o arquivo de saída principal, em seguida,
    • você encontrar um registro inválido que pertence ao arquivo de rejeitos; o último registro era válido; Isso significa que você mudar para gravar o arquivo de rejeitos: Resplendor o arquivo de saída principal antes de gravar o arquivo de rejeitos
    • Você agora escrever uma linha ao arquivo rejeições, então
    • você encontrar um registo válido que pertence ao arquivo de saída principal; o último registro era inválido; Isto significa que você está alternando a escrever para o arquivo de saída principal: lave o arquivo de rejeições antes de escrever para o arquivo de saída principal
    • você escrever mais de 200 linhas para o arquivo de saída principal, sem rubor manualmente os rejeitados arquivo primeiro, depois
    • assumir que o tempo de execução não fez automaticamente alinhada qualquer coisa para você, porque 200 registros tamponada desde a última nivelado manual sobre o arquivo de saída principal não são suficientes para desencadear um autoclismo automático
    • seu programa trava neste momento
    • retomar e conte 1000 registros válidos no arquivo de saída principal (você toda velocidade manualmente aqueles antes de mudar para o arquivo de rejeitos), e 1 registro no arquivo de rejeitos (você toda velocidade manualmente antes de voltar para o arquivo de saída principal).
    • retomar corretamente o processamento do arquivo de entrada no registro # 1001, que é o primeiro registro válido imediatamente após o registro inválido.
    • você reprocessar os próximos 200 registros válidos porque não foram liberados, mas você não obter registros desaparecidos e não duplicados à
  • Se você não está feliz com o intervalo entre os fluxos automáticos do tempo de execução, você também pode fazer descargas manuais a cada 100 ou a cada 1000 registros. Isso depende se o processamento de um registro é mais caro do que a lavagem ou não (se procesing é mais caro, lave frequentemente, talvez depois de cada registro, caso contrário, apenas rente ao alternar entre saída / rejeições.)

  • retomar a partir de falha

    • abrir o arquivo de saída eo arquivo de rejeitos para leitura e escrita , e começar lendo e contando cada registro (dizer em records_resume_counter) até chegar ao final do arquivo
    • a menos que você foram rubor após cada gravar você é outputting , você também vai precisar para realizar um pouco de tratamento especial para o último registro, tanto na saída e rejeita arquivo :
      • antes de ler um registro do arquivo de saída / rejeitos interrompido, lembre-se a posição que você está em na saída disse / rejeita arquivo (use ftell), vamos chamá-lo last_valid_record_ends_here
      • ler o registro. Validar que o registro não é um registro parcial (ou seja, o tempo de execução não tenha liberado o arquivo até o Médio de um registro).
      • se você tem um registro por linha, este é facilmente verificado, verificando que o último caractere no registro é um retorno de carro ou alimentação de linha (\n ou `r`)
        • Se o registro estiver completo, incrementar os registros de balcão e prosseguir com o próximo registro (ou o fim do arquivo, o que ocorrer primeiro).
        • se o registro é parcial, fseek volta para last_valid_record_ends_here, e parar de ler a partir desta saída / rejeitar arquivos; não incrementar o contador; avance para o arquivo de saída próxima ou rejeita a menos que você já passou por todos eles
    • abrir o arquivo de entrada para a leitura e ignorar registros records_resume_counter a partir dele
      • continuar o processamento e saída para o arquivo de saída / rejeitos; este será automaticamente acréscimo para a saída / rejeita arquivo de onde você parou de ler / contar registros já processados ??
      • se você tivesse que realizar especialprocessamento de ondas de registro parcial, o próximo registro você saída irá substituir a sua informação parcial da execução anterior (pelo last_valid_record_ends_here) -. você não terá duplicado, lixo ou registros ausentes

Outras dicas

Se você pode alterar o código para que ele escrever o último registro processado para um arquivo, porque você não pode mudá-lo para corrigir o vazamento de memória?

Parece-me ser uma melhor solução para corrigir a causa raiz do problema, em vez de tratar os sintomas.

fseek() e fwrite() irá degradar o desempenho, mas nem de longe tanto quanto uma operação / gravação / tipo perto aberto.

Eu estou supondo que você estará armazenando o valor ftell() no segundo arquivo (para que você possa pegar onde você parou). Você deve sempre fflush() o arquivo, assim como para garantir que os dados são escritos a partir da biblioteca C de tempo de execução para baixo para os buffers do sistema operacional. Caso contrário, seu SEGV irá garantir o valor não está atualizado.

Ao invés de escrever o registro inteiro, provavelmente seria mais fácil chamar ftell () no início de cada um, e escrever a posição do ponteiro de arquivo. Quando você tem que reiniciar o programa, fseek () para a última posição gravada no arquivo e continuar.

É claro, fixando o vazamento de memória seria melhor;)

Se você escrever a última posição processados ??para cada registro, isso terá um impacto notável no desempenho porque você vai precisar para cometer o write (normalmente, fechando o arquivo) e, em seguida, reabrir o arquivo novamente. Em outras obras, o fseek é a menor das suas preocupações.

Gostaria de parar de cavar um buraco mais profundo e simplesmente executar o programa através Valgrind . Se o fizer, deve evitar o vazamento, bem como outros problemas.

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