Precisa de um loop para dormir por uma fração de segundo

13

Na minha máquina, preciso executar um ciclo que itere 1 comando simples que deve ter um atraso expresso em frações de segundo.

Digamos que eu preciso:

  • para salvar um arquivo com uma crescente enumeração (arquivo-0, arquivo-1, arquivo-2, ...) gerado por algo trivial para este exemplo como time > file-$x
  • Eu preciso fazer isso a cada 1/70 de segundo (como exemplo), porque gostaria de expressar meu tempo com frações de segundo.

Como posso ser realmente preciso e ter tudo expresso com um script bash?

A fração pode gerar uma quantidade indeterminável, preciso ser preciso e preciso de pelo menos 4-5 casas decimais.

user1717079
fonte

Respostas:

11

Para converter de frações para decimais no bash, faça algo como

myvar=$(echo "scale=4; 5/10" | bc)

Então, para fazer um loop nesse valor,

for i in $(seq 1 1000); do sleep $myvar; done

Minha implementação de suspensão no Debian (GNU) parece aceitar valores decimais de suspensão.

Infelizmente..

Com esse tipo de precisão (4-5 casas decimais), você desejará algo como um script perl ou um programa compilado; a sobrecarga de chamar qualquer programa dentro do loop adicionará muita instabilidade. Chamar o sono em si levará alguns milissegundos.

Considere o seguinte, 1/10.000 de segundo de sono, feito 1000 vezes:

time for i in $(seq 1 1000); do sleep 0.0001; done

real    0m2.099s
user    0m3.080s
sys     0m1.464s

O resultado esperado seria 1/10 de segundo. O sono não chega nem perto das tolerâncias que você deseja.

/programming/896904/how-do-i-sleep-for-a-millisecond-in-perl

usando Time :: HiRes do perl, 1000 * 1000 microssegundos:

my $i=0;
for($i=0;$i<=1000;$i++) {
        usleep(1000);
}

real    0m1.133s
user    0m0.024s
sys     0m0.012s

nos dá muito mais perto de um segundo.

Rob Bos
fonte
se não for possível obter 4-5 decimais, eu usaria o melhor resultado possível como alternativa, mas o que quero dizer é que preciso expressar isso em frações, não decimais ou segundos.
user1717079
Seria absolutamente trivial converter uma fração para um decimal em qualquer linguagem de script, incluindo o bash. por exemplo, echo "scale = 4; 5/10" | bc
Rob Bos
agora eu tenho como converter a expressão, o problema agora é que uma simples festa é muito lento e não pode simplesmente manter-se com esta freqüência ...
user1717079
Sim, se você quiser algo muito mais preciso do que 1/10 de segundo, provavelmente desejará perl ou python, para evitar que o programa chame sobrecarga no loop.
Rob Bos
7

Talvez você possa simplesmente correr

sleep 0.7

?

man 1 sleep

na minha archlinuxdistribuição:

DESCRIÇÃO Pausa por NUMBER segundos. SUFIXO pode ser 's' por segundos (o padrão), 'm' por minutos, 'h' por horas ou 'd' por dias. Diferente da maioria das implementações que exigem que NUMBER seja um número inteiro, aqui NUMBER pode ser um número de ponto flutuante arbitrário. Dados dois ou mais argumentos, faça uma pausa pela quantidade de tempo especificada pela soma de seus valores.

Gilles Quenot
fonte
sleeppermite frações? você pode oferecer um exemplo completo com um loop falso?
user1717079
0,7, não é uma fração que expressa meu problema ... meu problema é sobre granularidade; pense em 1/3 e tente ser preciso durante o sono.
user1717079
6

Gerar um processo e carregar um novo executável provavelmente leva alguns milissegundos, para que esse tipo de precisão realmente não faça sentido. Observe também que o tempo de CPU em muitos sistemas é alocado para processos por fatias de até 10ms.

Dito isto, algumas sleepimplementações levam números fracionários de segundos, e o zsh e o ksh93 podem tornar sua $SECONDSvariável especial fracionária typeset -F SECONDS.

Exemplo (zsh):

$ typeset -F SECONDS=0; for ((i=1; i<=70; i++)); do sleep $((1./70)); date +%s.%N; done | { head -n3;echo ..;tail -n3; }; echo $SECONDS
1350076317.374870501
1350076317.391034397
1350076317.407278461
..
1350076318.464585550
1350076318.480887660
1350076318.497133050
1.1393780000

Opa, ele flutuou. Você pode ajustar o tempo de sono com base em $SECONDS:

$ typeset -F SECONDS=0; for ((i=1; i<=70; i++)); do sleep $((i/70. - SECONDS)); date +%s.%N; done | { head -n3;echo ...;tail -n3; }; echo $SECONDS
1350076420.262775654
1350076420.277012997
1350076420.291302750
../..
1350076421.219682227
1350076421.234134663
1350076421.248255685
1.0020580000

Aqueles 2 milisegundos extras são, provavelmente, a ser contabilizado para executar a última sleepe datecomandos.

Observe também que o zsh possui um zselecttempo limite expresso em centésimos de segundo. E o ksh93 foi sleepincorporado (e aceita pontos flutuantes) e printfpode imprimir data / hora.

$ typeset -F SECONDS=0; for ((i=1; i<=70; i++)); do ((i<4 || i>67)) && printf '%(%S.%N)T\n' now; sleep $((i/70.-SECONDS)); done; echo $SECONDS
20.823349000
20.837510000
20.851663000
21.780099000
21.794254000
21.808405000
0.9992358685

Se você quiser algo mais preciso, provavelmente desejará um sistema operacional em tempo real ou um sistema operacional com recursos em tempo real e certamente não usará um shell.

Stéphane Chazelas
fonte
sleep 1/70não é permitido na minha máquina ...
user1717079
nem mesmo perto do que eu quero alcançar, como posso executar as coisas mais rapidamente?
user1717079
Desculpe, por fracionário, não quis dizer expresso como n / m onde n e m são números inteiros, mas como decimais com uma parte fracionária. Eu suponho que você também pode chamá-los decimais números de ponto flutuante embora eu não tenho certeza sobre a terminologia bom Inglês
Stéphane Chazelas
Eu acho que estou indo para evitar o da casca ...
user1717079
O atendedor está certo. Os shells e processos geralmente não são adequados para a resolução de milissegundos. Para um desempenho confiável, você precisará compilar um programa que chame usleep (3) ou similar - e também poderá recompilar seu kernel com uma configuração diferente; ou pelo menos alimente parâmetros diferentes ao agendador de tempo de execução do seu kernel. Isso não é trivial de se fazer. Uma instalação Debian padrão com um kernel padrão possui recursos limitados em tempo real. Ainda assim, você pode querer verificar o comando nice (1); isso pode ajudar.
THB
3

Se o seu shell sleepnão aceita fração, use perl.

sleep_fraction() {
  /usr/bin/perl -e "select(undef, undef, undef, $1)"
}

sleep_fraction 0.01428

Se você precisar descobrir a fração, use echo "scale=5; 1/70" | bc

lolesque
fonte
0

No Alpine Linux (Busybox), você pode fazer um loop por microssegundos com usleep 10(equivalente a 0.00001um segundo)

GNU sleep suporta frações de segundo: sleep 0.00001

Stuart Cardall
fonte