Следующий код составляет список имен и «чисел» и дает каждому человеку случайный возраст от 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
2
Victor 27 Июн 2009 в 05:36

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"

У обоих примерно одинаковая скорость.

Исправления такие же, как и все остальные:

  • не часто закрывать и повторно открывать файл
  • использовать арифметику оболочки
  • ах да, и используйте ЦИТАТЫ, но это для здравомыслия, а не для скорости
6
TheBonsai 27 Июн 2009 в 10:22
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.

5
John Kugelman 27 Июн 2009 в 03:19

Использование оболочки для генерации таких случайных чисел не совсем то, для чего она предназначена. Вероятно, вам будет лучше кодировать что-нибудь для генерации случайных чисел из однородного распределения на другом языке, таком как 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, я не заметил никакого ускорения.

5
Community 23 Май 2017 в 12:15

Я предполагаю, что ">> $ 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

4
John Nilsson 27 Июн 2009 в 02:00

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

3
chaos 27 Июн 2009 в 01:48

(У меня такое чувство, что вам может не понравиться этот ответ, но вы технически не указали, что ответ должен оставаться в 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
2
27 Июн 2009 в 02:12

Попробуйте это для своего основного цикла:

seq 1 $n | while read i
do
    let "NUM=($RANDOM%75)+15"
    echo "name$i $NUM (###)###-####"
done > $file

Это заставит seq и цикл работать параллельно, а не ждать завершения seq перед запуском цикла. Это будет быстрее на нескольких ядрах / процессорах, но немного медленнее на одном ядре.

И я согласен с остальными здесь: это обязательно должно быть bash?

Изменить: добавить предложение хаоса, чтобы файл оставался открытым, а не открывался для добавления для каждого имени.

2
pgs 27 Июн 2009 в 02:20