fseek / rebobinagem num loop
-
03-07-2019 - |
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.
Solução
Quando confrontados com este tipo de problema, você pode adotar um dos dois métodos:
- 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ê devefflush
após cada write (tantobookmark
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 pulartruncate
uma vez que o número do registro (ou posiçãoftell
) será sempre tão longo ou maior que o número recorde anterior (ou posiçãoftell
), e irá substituí-lo completamente, desde que você truncar o arquivo uma vez na inicialização (este responde a sua pergunta original)
-
- 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 precisafflush
o arquivo de saída principal antes de mudar para gravar o arquivo de rejeitos, efflush
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 ??em>, 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á-lolast_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 paralast_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
- 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
- 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
- abrir o arquivo de saída eo arquivo de rejeitos para leitura e escrita ??em>, e começar lendo e contando cada registro (dizer em
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.