2009-06-27 1 views
2

Следующий код делает список имен и номеров «» и дает каждому человеку случайный возраст между 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." 

С этим я пытаюсь составить список имен 1M. Это медленно, я ожидал этого; это было так медленно, что я потерял терпение и попробовал имена 10K. Это было слишком медленно, но это произошло через несколько секунд.

Причина, по которой я генерирую имена, заключается в их сортировке. Меня удивило то, что когда я отсортировал список имен 10K, это было мгновенно.

Как это может быть?

Есть ли что-то, что заставляет это идти нечестиво медленно? И сортировка, и генерация - это доступ к файлам, так как сортировка может быть быстрее? Является ли мой случайный номер математики в генераторе списка, что замедляет его?

Вот мой сценарий сортировки.

#!/bin/sh 
#first argument is list to be sorted, second is output file 
tr -s '' < $1 | sort -n -k2 > $2 
+0

Возможно, попробуйте использовать лучший инструмент для этой работы? – Juliet

+0

Могу я предложить скрипт Perl, который сделает это за долю времени? – cletus

+0

Проще говоря, вы выполняете новый процесс (эхо) 10k раз. И выходной файл открывается 10k раз. Это замедляется на больших количествах. Также bash '' for 'не подходит для обработки больших списков. Какое другое замедление, создание такого большого списка, как переменная bash, является медленным. Принимая во внимание, что команда sort? Он запускается только один раз. И сортировка идет быстро. – nos

ответ

5

Не новый ответ, просто новый код.

Это то, что ИМХО хороший средний путь между красивым и эффективным кодом (так эффективно, как вы можете быть в 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" 

Оба имеют одинаковую скорость.

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

  • не часто близко и повторно открыть -файл
  • использование оболочки арифметика
  • ах да, и использовать ЦИТАТЫ, но это для здравомыслия, а не для скорости
+0

Вау, спасибо за это. Я не знал, что вы можете сделать вывод файла после «done». i действительно надеялся, что смогу сохранить свои сценарии так, как они были до того, как они принимают аргументы. что действительно помогает. – Victor

+0

Очень приятно. Этот трюк работал в моем коде с двумя циклами: for ((x = $ x_min; x <= $ x_max; x + = 1)); do for ((y = $ y_min; y <= $ y_max; y + = 1)); do echo "$ x, $ y" сделано сделано >> $ fname – OutputLogic

2

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

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

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

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

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

3

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

+0

Я думаю, что это самое большое улучшение производительности. Добавление к файлам дорого! –

5

Использование оболочки для генерации случайных чисел, таких как это не совсем то, что она была предназначена для выполнения. Скорее всего, вам будет лучше кодировать что-то, чтобы генерировать случайные числа из равномерного распределения на другом языке, например, 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 секунд. Назначение seq от 1 до 1000000 занимает ~ 10 секунд, когда вы добавляете генерацию случайных чисел, ваш сценарий занимает три минуты на одном компьютере. Я расстроился так же, как вы, и играл со сценарием, чтобы попытаться сделать это быстрее. Вот моя сокращенная версия вашего кода, в котором я играю:

for x in `seq 1 10000`; do 
    let "NUM=($RANDOM%75)+15" 
    echo $NUM >> test.txt 
done 

Выполнение этого требует около 5.3s:

$ 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
for i in `seq 1 $n` 

Yikes! Это генерирует 1 000 000 аргументов в цикл for. Это вызов seq займет длинный, длинный, длинный раз. Попробуйте

for ((i = 1; i <= n; i++)) 

Обратите внимание на отсутствие знаков доллара, между прочим. В частности, синтаксис var++ требует опускать знак доллара от имени переменной. Вам также разрешено использовать или опустить их в другом месте: это может быть i <= n или $i <= $n, либо один. Как мне кажется, вы должны опустить знаки доллара полностью в let, declare и for ((x; y; z)) заявления. Подробное объяснение см. В разделе АРИФМЕТИЧЕСКАЯ ОЦЕНКА справочной страницы sh.

+0

хорошо пункт. более раннее воплощение моего скрипта имело этот тип цикла, но поскольку я все еще изучаю синтаксис, и все, что я заменил, потому что что-то не так. в вашей второй петле нужны «$»? как «for ((i = 1; $ i <= $ n; $ i ++))« , когда вы должны использовать «$», а когда не должно быть? – Victor

+0

Вы можете опустить их в арифметическом контексте (для ((, (,, $ ((, let, индексы массивов ...), если они делают то, что вы хотите. Вы ДОЛЖНЫ использовать их, если они содержат символы, которые используются для обнаружения база, как «0090», будет восьмеричной 90, вам нужно использовать «10 # $ foo», а затем заставить базу 10 – TheBonsai

+2

В моих тестах с телом no-op, использование seq было примерно в два раза быстрее, чем C- стиль для цикла. Вы торгуете большим количеством памяти, чтобы обрабатывать приращение и тестировать с каждой итерацией. –

2

(У меня есть ощущение, что вы не можете, как этот ответ, но технически не уточнил, ответ должен был остаться в Баш: 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:])) 

Примечание: только используя стандартный вывод для «реального производства» вместо уведомлений о состоянии позволяет эта программа будет работать параллельно с другими, данные трубопроводов непосредственно из одна из них - одна. (Это возможно с помощью специальных файлов в форматах * Никс, но только проще, если вы можете использовать стандартный вывод.) Пример:

 
$./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 
4

Я предполагаю, что «>> $ файл 'может быть источником вашей проблемы. В моей системе ваш сценарий занимает 10 секунд, чтобы генерировать 10000. Если я удалю аргумент $ file и вместо этого просто использую stdout и захвачу все это в файл, то он займет секунду.

$ time ./gen1.sh n1.txt 10000 Создание списка из 10000 человек. Созданный список.

реальных 0m7.552s пользователя 0m1.355s SYS 0m1.886s

$ времени ./gen2.sh 10000> n2.txt

реальных 0m0.806s пользователя 0m0.576s SYS 0m0 ,140s

Смежные вопросы