Нужна петля для сна на долю секунды

13

На моей машине мне нужно выполнить цикл, который повторяет одну простую команду, которая должна иметь задержку, выраженную в долях секунды.

Допустим, мне нужно:

  • чтобы сохранить файл с возрастающим перечислением (файл-0, файл-1, файл-2, ...), сгенерированный чем-то тривиальным для этого примера, например time > file-$x
  • Мне нужно делать это каждые 1/70 секунды (как пример), потому что я хотел бы выразить свое время с долями секунды.

Как я могу быть по-настоящему точным и выразить все с помощью bash-скрипта?

Фракция может генерировать неопределенное количество, мне нужно быть точным, и поэтому мне нужно по крайней мере 4-5 десятичных знаков.

user1717079
источник

Ответы:

11

Чтобы преобразовать дробные дроби в bash, сделайте что-то вроде

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

Затем, чтобы сделать цикл с этим значением,

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

Моя реализация сна в Debian (GNU), кажется, принимает десятичные значения сна.

К сожалению..

С такой точностью (4-5 десятичных знаков) вам понадобится что-то вроде Perl-скрипта или скомпилированной программы; накладные расходы на вызов любой программы в цикле добавят много дрожания. Вызов самого сна займет несколько миллисекунд.

Рассмотрим следующее: 1/10000 тысячный сон, выполненный 1000 раз:

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

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

Ожидаемый результат будет 1/10 секунды. Сон далеко не так, как вы хотите.

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

используя perl's Time :: HiRes, 1000 * 1000 микросекунд:

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

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

дает нам гораздо ближе к секунде.

Роб Бос
источник
если невозможно достичь 4-5 десятичных знаков, я бы взял наилучший возможный результат в качестве альтернативы, но я хотел бы выразить это в долях, а не в десятичных или секундах.
user1717079
Было бы совершенно тривиально преобразовать дробь в десятичную на любом языке сценариев, включая bash. например, echo "scale = 4; 5/10" | до н.э.
Роб Бос
теперь у меня есть, как преобразовать выражение, проблема в том, что простой bash очень медленный и не может просто поспевать за этой частотой ...
user1717079
Да, если вы хотите что-то гораздо более точное, чем примерно 1/10 секунды, вам, вероятно, понадобится Perl или Python, чтобы избежать вызова программы внутри цикла.
Роб Бос
7

Может быть, вы можете просто запустить

sleep 0.7

?

man 1 sleep

в моем archlinuxдистрибутиве:

ОПИСАНИЕ Пауза в течение нескольких секунд. SUFFIX может быть «s» для секунд (по умолчанию), «m» для минут, «h» для часов или «d» для дней. В отличие от большинства реализаций, которые требуют, чтобы NUMBER был целым числом, здесь NUMBER может быть произвольным числом с плавающей запятой. Учитывая два или более аргумента, делайте паузу на количество времени, указанное в сумме их значений.

Жиль Квено
источник
sleepразрешает дроби? Можете ли вы предложить полный пример с поддельной петлей?
user1717079
0,7 это не дробь, которая выражает мою проблему ... моя проблема в гранулярности; подумайте о 1/3 и постарайтесь быть точным со сном.
user1717079
6

Создание процесса и загрузка в него нового исполняемого файла, вероятно, займет несколько миллисекунд, поэтому такая точность не имеет смысла. Также обратите внимание, что процессорное время во многих системах распределяется между процессами срезами до 10 мс.

Тем не менее, некоторые sleepреализации занимают дробные числа секунд, и zsh и ksh93 могут сделать свою $SECONDSспециальную переменную дробной с typeset -F SECONDS.

Пример (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

Ой, это дрейфовало. Вы можете настроить время сна на основе $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

Эти 2 дополнительные миллисекунды, вероятно, должны быть учтены при запуске команды last sleepи date.

Также обратите внимание, что у zsh есть zselectвстроенная функция с таймаутом, выраженным в сотых долях секунды. И ksh93 имеет sleepвстроенные (и принимает с плавающей запятой) и printfможет печатать дату / время.

$ 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

Если вы хотите что-то более точное, вы, вероятно, захотите операционную систему реального времени или операционную систему с возможностями реального времени и, конечно, не использовать оболочку.

Стефан Шазелас
источник
sleep 1/70не разрешено на моей машине ...
user1717079
даже близко к тому, чего я хочу достичь, как я могу выполнять вещи быстрее?
user1717079
Извините, под дробным, я имел в виду не выраженное как n / m, где n и m целые числа, а как десятичные дроби с дробной частью. Я полагаю, вы могли бы также назвать их десятичными числами с плавающей запятой, хотя я не уверен в правильной английской терминологии
Стефан Шазелас
Я думаю, что я собираюсь избежать оболочки ...
user1717079
Ответчик прав. Оболочки и процессы обычно плохо подходят для разрешения в миллисекундах. Для надежной работы вам нужно скомпилировать программу, которая вызывает usleep (3) или что-то подобное - и вам может понадобиться перекомпилировать ваше ядро ​​с другой конфигурацией; или, по крайней мере, передать разные параметры в планировщик времени выполнения вашего ядра. Это нетривиально делать. Стандартная установка Debian со стандартным ядром имеет ограниченные возможности в реальном времени. Тем не менее, вы можете проверить команду nice (1); это может помочь.
THB
3

Если ваша оболочка sleepне принимает дробь, используйте perl.

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

sleep_fraction 0.01428

Если вам нужно выяснить дробь, используйте echo "scale=5; 1/70" | bc

lolesque
источник
0

Под Alpine Linux (Busybox) вы можете зацикливаться на микросекунды с usleep 10(эквивалентно 0.00001секунде)

GNU sleep поддерживает доли секунды: sleep 0.00001

Стюарт Кардалл
источник