Os descritores de arquivos otimizam a gravação em arquivos?

6

É equivalente ter os comandos imprimindo em um arquivo diretamente, ao invés de gravar em um descritor de arquivo?

Ilustração

Escrevendo para o arquivo diretamente:

for i in {1..1000}; do >>x echo "$i"; done

Usando um fd:

exec 3>&1 1>x
for i in {1..1000}; do echo "$i"; done
exec 1>&3 3>&-

O último é mais eficiente?

    
por Tomasz 18.05.2018 / 16:28

3 respostas

12

A principal diferença entre abrir o arquivo antes do loop com exec e colocar o redirecionamento no comando no loop é que o primeiro requer a configuração do descritor de arquivo apenas uma vez, enquanto o segundo abre e fecha o arquivo para cada iteração do loop.

Fazê-lo uma vez provavelmente será mais eficiente, mas se você executar um comando externo dentro do loop, a diferença provavelmente desaparecerá no custo de iniciar o comando. ( echo aqui provavelmente está embutido, então isso não se aplica)

Se a saída for enviada para algo diferente de um arquivo normal (por exemplo, se x for um pipe nomeado), o ato de abrir e fechar o arquivo pode ficar visível para outros processos, portanto, pode haver diferenças no comportamento também.

Note que não há realmente nenhuma diferença entre um redirecionamento através de exec e um redirecionamento no comando, ambos abrem o arquivo e fazem juggle nos números do descritor de arquivo.

Esses dois devem ser praticamente equivalentes, pois ambos open() o arquivo e write() . (Existem diferenças em como o fd 1 é armazenado durante o comando.):

for i in {1..1000}; do 
    >>x echo "$i"
done


for i in {1..1000}; do
    exec 3>&1 1>>x         # assuming fd 3 is available
    echo "$i"              # here, fd 3 is visible to the command
    exec 1>&3 3>&-
done
por 18.05.2018 / 16:32
7

Sim, é mais eficiente

A maneira mais fácil de testar é aumentar a contagem para dizer 500000 e o tempo:

> time bash s1.sh; time bash s2.sh
bash s1.sh  16,47s user 10,00s system 99% cpu 26,537 total
bash s2.sh  10,51s user 3,50s system 99% cpu 14,008 total

strace (1) revela porque (temos um simples write , em vez de open + 5 * fcntl + 2 * dup + 2 * close + write ):

para for i in {1..1000}; do >>x echo "$i"; done , obtemos:

open("x", O_WRONLY|O_CREAT|O_APPEND, 0666) = 3
fcntl(1, F_GETFD)                       = 0
fcntl(1, F_DUPFD, 10)                   = 10
fcntl(1, F_GETFD)                       = 0
fcntl(10, F_SETFD, FD_CLOEXEC)          = 0
dup2(3, 1)                              = 1
close(3)                                = 0
write(1, "997\n", 4)                    = 4
dup2(10, 1)                             = 1
fcntl(10, F_GETFD)                      = 0x1 (flags FD_CLOEXEC)
close(10)                               = 0
open("x", O_WRONLY|O_CREAT|O_APPEND, 0666) = 3
fcntl(1, F_GETFD)                       = 0
fcntl(1, F_DUPFD, 10)                   = 10
fcntl(1, F_GETFD)                       = 0
fcntl(10, F_SETFD, FD_CLOEXEC)          = 0
dup2(3, 1)                              = 1
close(3)                                = 0
write(1, "998\n", 4)                    = 4
dup2(10, 1)                             = 1
fcntl(10, F_GETFD)                      = 0x1 (flags FD_CLOEXEC)
close(10)                               = 0
open("x", O_WRONLY|O_CREAT|O_APPEND, 0666) = 3
fcntl(1, F_GETFD)                       = 0
fcntl(1, F_DUPFD, 10)                   = 10
fcntl(1, F_GETFD)                       = 0
fcntl(10, F_SETFD, FD_CLOEXEC)          = 0
dup2(3, 1)                              = 1
close(3)                                = 0
write(1, "999\n", 4)                    = 4
dup2(10, 1)                             = 1
fcntl(10, F_GETFD)                      = 0x1 (flags FD_CLOEXEC)
close(10)                               = 0
open("x", O_WRONLY|O_CREAT|O_APPEND, 0666) = 3
fcntl(1, F_GETFD)                       = 0
fcntl(1, F_DUPFD, 10)                   = 10
fcntl(1, F_GETFD)                       = 0
fcntl(10, F_SETFD, FD_CLOEXEC)          = 0
dup2(3, 1)                              = 1
close(3)                                = 0
write(1, "1000\n", 5)                   = 5
dup2(10, 1)                             = 1
fcntl(10, F_GETFD)                      = 0x1 (flags FD_CLOEXEC)
close(10)                               = 0

enquanto para exec 3>&1 1>x ficamos muito mais limpos

write(1, "995\n", 4)                    = 4
write(1, "996\n", 4)                    = 4
write(1, "997\n", 4)                    = 4
write(1, "998\n", 4)                    = 4
write(1, "999\n", 4)                    = 4
write(1, "1000\n", 5)                   = 5

Mas note que a diferença não se deve a "usar um FD", mas devido ao local onde você faz o redirecionamento. Por exemplo, se você fizesse for i in {1..1000}; do echo "$i"; done > x , teria praticamente o mesmo desempenho que seu segundo exemplo:

bash s3.sh  10,35s user 3,70s system 100% cpu 14,042 total
    
por 18.05.2018 / 18:47
0

Para somar as coisas e adicionar uma nova informação neste tópico, aqui está uma comparação de quatro maneiras de fazer isso, ordenadas por eficiência. Estimo a eficiência por medição de tempo (user + sys) para 1 milhão de iterações, com base em duas séries de testes.

  1. Esses dois são quase os mesmos:
    • Redirecionamento de loop > simples (tempo: 100% )
    • Usando exec uma vez para todo o loop (tempo: ~ 100% )
  2. Usando >> para cada iteração (tempo: 200% - 250% )
  3. Usando exec para cada iteração (tempo: 340% - 480% )

A conclusão é esta:

Há uma diferença pequena entre o uso de exec vs. simples redirecionamentos como >> . (Simples é mais barato). Ele não aparece no nível de execução de um único comando, mas com um alto número de repetições, a diferença se torna visível. Embora o peso de execução do comando seja redirecionado para sombras, as diferenças, conforme observado pelo ikkachu na outra resposta.

    
por 19.05.2018 / 01:55