Следующий код составляет список имен и «чисел» и дает каждому человеку случайный возраст от 15 до 90 лет.
#!/bin/sh
file=$1
n=$2
# if number is zero exit
if [ "$n" -eq "0" ]
then
exit 0
fi
echo "Generating list of $n people."
for i in `seq 1 $n`;
do
let "NUM=($RANDOM%75)+15"
echo "name$i $NUM (###)###-####" >> $file
done
echo "List generated."
С его помощью я пытаюсь составить список из 1 миллиона имен. Я ожидал, что это медленно; это было настолько медленно, что я потерял терпение и попробовал 10К имен. Это тоже было медленным, но это было сделано за несколько секунд.
Причина, по которой я генерирую имена, состоит в том, чтобы отсортировать их. Что меня удивило, так это то, что когда я отсортировал список из 10 тысяч имен, это произошло мгновенно.
Как это может быть?
Есть ли что-то, что делает это безбожно медленным? И сортировка, и генерация обращаются к файлам, так как же сортировка может быть быстрее? Моя математика случайных чисел в генераторе списков замедляет его?
Вот мой сценарий сортировки.
#!/bin/sh
#first argument is list to be sorted, second is output file
tr -s '' < $1 | sort -n -k2 > $2
7 ответов
Не новый ответ, просто новый код.
Это то, что IMHO является хорошим промежуточным звеном между красивым и эффективным кодом (настолько эффективным, насколько вы можете быть в Bash, он медленный, это оболочка ...)
for ((i=1;i<=n;i++));
do
echo "name$i $((NUM=(RANDOM%75)+15)) (###)###-####"
done > "$file"
Альтернатива, без использования классической петли счетчика
i=1
while ((i<=n)); do
echo "name$((i++)) $((NUM=(RANDOM%75)+15)) (###)###-####"
done > "$file"
У обоих примерно одинаковая скорость.
Исправления такие же, как и все остальные:
- не часто закрывать и повторно открывать файл
- использовать арифметику оболочки
- ах да, и используйте ЦИТАТЫ, но это для здравомыслия, а не для скорости
for i in `seq 1 $n`
Ой! Это генерирует 1 000 000 аргументов для цикла for
. Этот звонок seq
займет долгое, долгое, долгое время. Пытаться
for ((i = 1; i <= n; i++))
Кстати, обратите внимание на отсутствие знаков доллара. В частности, синтаксис var++
требует, чтобы вы опускали знак доллара в имени переменной. Вы также можете использовать или опускать их где-либо еще: это может быть i <= n
или $i <= $n
, любое из них. Как я понял, вам следует полностью опускать знаки доллара в операторах let
, declare
и for ((x; y; z))
. Полное объяснение см. В разделе «АРИФМЕТИЧЕСКАЯ ОЦЕНКА» справочной страницы sh
.
Использование оболочки для генерации таких случайных чисел не совсем то, для чего она предназначена. Вероятно, вам будет лучше кодировать что-нибудь для генерации случайных чисел из однородного распределения на другом языке, таком как Fortran, Perl или C.
В вашем коде одна вещь, которая будет очень медленной, - это создание последовательности чисел от 1..1e7 и присвоение их всех переменной. Это, вероятно, очень расточительно, но вы должны профилировать, если хотите быть уверенным. Как указывает chaos, добавление в файл также, вероятно, будет очень дорогостоящим!
В Python вы можете сделать что-то вроде этого:
#!/usr/bin/python
import random
count = 1
print ' '.join( ['name', 'age'] )
while count <= 1000000:
age = random.randrange(15,90)
count = count + 1
name = 'name' + str(count)
print ' '.join( [ name, str(age) ] )
Запуск этого на моем ноутбуке занимает ~ 10 секунд. Назначение последовательности от 1 до 1000000 занимает ~ 10 секунд, когда вы добавляете генерацию случайных чисел, ваш скрипт на той же машине занимает более трех минут. Я был разочарован так же, как и вы, и поигрался со сценарием, чтобы попытаться сделать его быстрее. Вот моя сокращенная версия вашего кода, с которой я играю:
for x in `seq 1 10000`; do
let "NUM=($RANDOM%75)+15"
echo $NUM >> test.txt
done
Это занимает около 5,3 с:
$ time ./test.sh
real 0m5.318s
user 0m1.305s
sys 0m0.675s
Удаление файла с добавлением и простое перенаправление STDOUT в один файл дает следующий сценарий:
for x in `seq 1 10000`; do
let "NUM=($RANDOM%75)+15"
echo $NUM
done
Это занимает около полсекунды:
$ time ./test.sh > test.txt
real 0m0.516s
user 0m0.449s
sys 0m0.067s
Медлительность вашей программы, по крайней мере частично, связана с добавлением в этот файл. Любопытно, что когда я попытался заменить вызов seq циклом for, я не заметил никакого ускорения.
Я предполагаю, что ">> $ file" может быть источником вашей проблемы. В моей системе вашему сценарию требуется 10 секунд для генерации 10000. Если я удалю аргумент $ file и вместо этого просто использую стандартный вывод и сохраню все это в файл, это займет менее секунды.
$ time ./gen1.sh n1.txt 10000 Формируем список из 10000 человек. Список создан.
Реальный 0m7.552s пользователь 0m1.355s sys 0m1.886s
$ time ./gen2.sh 10000> n2.txt
Реальный 0m0.806s пользователь 0m0.576s sys 0m0.140s
Не знаю, вся ли в этом вся история, но повторное открытие файла для добавления к нему каждого имени ничего не может помочь. Выполнение всего этого в любом контексте, где вы можете сохранить дескриптор открытого файла для записи, должно очень помочь.
(У меня такое чувство, что вам может не понравиться этот ответ, но вы технически не указали, что ответ должен оставаться в bash!: P)
Обычно что-то быстро разрабатывают на языке прототипов, а затем, возможно, переключаются на другой язык (часто C) по мере необходимости. Вот вам для сравнения очень похожая программа на Python:
#!/usr/bin/python
import sys
import random
def main(args=None):
args = args or []
if len(args) == 1:
# default first parameter
args = ["-"] + args
if len(args) != 2:
sys.stderr.write("error: invalid parameters\n")
return 1
n = int(args[1])
output = sys.stdout if args[0] == "-" else open(args[0], "a")
for i in xrange(1, n + 1):
num = random.randint(0, 74)
output.write("name%s %s (###)###-####\n" % (i, num))
sys.stderr.write("List generated.\n") # see note below
if __name__ == "__main__":
sys.exit(main(sys.argv[1:]))
Примечание. Использование только стандартного вывода для «реального вывода» вместо уведомлений о состоянии позволяет этой программе работать параллельно с другими, передавая данные напрямую из стандартного вывода одного в стандартный ввод другого. (Это возможно со специальными файлами в * nix, но проще, если вы можете использовать stdout.) Пример:
$./rand_names.py 1000000 | sort -n -k2 > output_file
И это должно быть достаточно быстро:
$time ./rand_names.py 1000000 > /dev/null List generated. real 0m16.393s user 0m15.108s sys 0m0.171s
Попробуйте это для своего основного цикла:
seq 1 $n | while read i
do
let "NUM=($RANDOM%75)+15"
echo "name$i $NUM (###)###-####"
done > $file
Это заставит seq
и цикл работать параллельно, а не ждать завершения seq перед запуском цикла. Это будет быстрее на нескольких ядрах / процессорах, но немного медленнее на одном ядре.
И я согласен с остальными здесь: это обязательно должно быть bash?
Изменить: добавить предложение хаоса, чтобы файл оставался открытым, а не открывался для добавления для каждого имени.
Похожие вопросы
Новые вопросы
bash
Для вопросов о скриптах, написанных для командной оболочки Bash. Для сценариев оболочки с ошибками / синтаксическими ошибками, пожалуйста, проверьте их с помощью программы shellcheck (или на сервере веб-проверки оболочки по адресу https://shellcheck.net) перед размещением здесь. Вопросы об интерактивном использовании Bash, скорее всего, будут касаться темы Super User, а не переполнения стека.