2010-03-20 5 views
6

Я сделал небольшой эксперимент, как будет показано ниже, и похоже, что цикл while быстрее, чем цикл for в Perl. Но поскольку эксперимент был довольно грубым, и предмет мог быть намного сложнее, чем кажется, я хотел бы услышать, что вы можете сказать об этом. Спасибо за любые комментарии/предложения :)В Perl это цикл while, который быстрее, чем цикл for?

В следующих двух небольших сценариях я попытался в то время как и для циклов отдельно для вычисления факториала 100 000. Тот, у которого есть цикл while, занял 57 минут 17 секунд, чтобы закончить, в то время как эквивалент for loop занял 1 час 7 минут 54 секунды.

скрипт, который имеет в то время как цикл:

use strict; 
use warnings; 
use bigint; 

my $now = time; 

my $n = shift; 
my $s = 1; 

while(1){ 
$s *= $n; 
$n--; 
last if $n==2; 
} 

print $s*$n; 
$now = time - $now; 
printf("\n\nTotal running time: %02d:%02d:%02d\n\n", int($now/3600), 
      int(($now % 3600)/60), int($now % 60)); 

скрипт, который имеет цикл:

use strict; 
use warnings; 
use bigint; 

my $now = time; 

my $n =shift; 
my $s=1; 

for (my $i=2; $i<=$n;$i++) { 
$s = $s*$i; 
} 

print $s; 
$now = time - $now; 
printf("\n\nTotal running time: %02d:%02d:%02d\n\n", int($now/3600), 
      int(($now % 3600)/60), int($now % 60)); 
+3

Возможно, вы пытаетесь оптимизировать неправильную вещь. Мне интересно, почему вы считаете, что эта конкретная часть языка так важна? – Ether

+0

Запустите их каждый раз несколько раз, и они, вероятно, усреднят примерно то же самое – CaffGeek

+0

@Chad, на самом деле я уже тестировал код пару раз. Они занимали разные промежутки времени, чтобы закончить ту же работу. Я думаю, что объяснение @Jonathan Leffler с иллюстрационным кодом имеет очень хороший смысл. – Mike

ответ

8

Петли не эквивалентны, и вы в первую очередь обманываете пакет bigint, и это не имеет никакого отношения к for vs while как таковой.

Цикл while использует нотацию '$s *= $i', но для цикла for используется '$s = $s * $i'. Достаточно просто показать, что они не идентичны. Также подсчитывается одна петля; другой отсчет. Это влияет на то, насколько велики числа, которые нужно умножить. Это эффект второго порядка, но не полностью пренебрежимо малый.

[Обновление: пересмотрено, чтобы показать только одну версию кода, с подсетевыми таймингами. Можно подумать, что печать должна быть исключена из расчетов времени; что делает вещи более грязными, хотя, поэтому я не беспокоился. Я исправил ошибку в предыдущей версии: loop 4 был таким же, как и цикл 3 - теперь это не так. Я также отформатировал форматирование вывода (хотя обработка подсегмента может быть улучшена - упражнение для читателя), и есть лучшая «отчетность о прогрессе».]

Результаты синхронизации на Mac Mini (Snow Leopard 10.6.2) были:

Count up $s *= $i:  00:00:12.663337 
Count up $s = $s * $i: 00:00:20.686111 
Count down $s *= $i:  00:00:14.201797 
Count down $s = $s * $i: 00:00:23.269874 

Сценарий:

use Time::HiRes qw(gettimeofday); 
use strict; 
use warnings; 
use bigint; 
use constant factorial_of => 13000; 

sub delta_t 
{ 
    my($tag, $t1, $t2) = @_; 
    my($d) = int($t2 - $t1); 
    my($f) = ($t2 - $t1) - $d; 
    my($s) = sprintf("%.6f", $f); 
    $s =~ s/^0//; 
    printf "%-25s %02d:%02d:%02d%s\n", 
      $tag, int($d/3600), int(($d % 3600)/60), int($d % 60), $s; 
} 

my $t1 = gettimeofday; 

{ 
    my $n = factorial_of; 
    my $s = 1; 
    for (my $i = 2; $i <= $n; $i++) 
    { 
     $s *= $i; 
    } 
    print "$s\n: Loop 1\n"; 
} 

my $t2 = gettimeofday; 
delta_t('Count up $s *= $i:',  $t1, $t2); 

{ 
    my $n = factorial_of; 
    my $s = 1; 
    for (my $i = 2; $i <= $n; $i++) 
    { 
     $s = $s * $i; 
    } 
    print "$s\n: Loop 2\n"; 
} 

my $t3 = gettimeofday; 
delta_t('Count up $s *= $i:',  $t1, $t2); 
delta_t('Count up $s = $s * $i:', $t2, $t3); 

{ 
    my $n = factorial_of; 
    my $s = 1; 
    for (my $i = $n; $i > 1; $i--) 
    { 
     $s *= $i; 
    } 
    print "$s\n: Loop 3\n"; 
} 

my $t4 = gettimeofday; 
delta_t('Count up $s *= $i:',  $t1, $t2); 
delta_t('Count up $s = $s * $i:', $t2, $t3); 
delta_t('Count down $s *= $i:',  $t3, $t4); 

{ 
    my $n = factorial_of; 
    my $s = 1; 
    for (my $i = $n; $i > 1; $i--) 
    { 
     $s = $s * $i; 
    } 
    print "$s\n: Loop 4\n"; 
} 

my $t5 = gettimeofday; 
delta_t('Count up $s *= $i:',  $t1, $t2); 
delta_t('Count up $s = $s * $i:', $t2, $t3); 
delta_t('Count down $s *= $i:',  $t3, $t4); 
delta_t('Count down $s = $s * $i:', $t4, $t5); 

А вот гораздо более компактная версия вышеприведенный код, расширенный для тестирования циклов «while» и «for». В нем также рассматриваются большинство сроков. Единственное, что не является идеальным (для меня) заключается в том, что он использует пару глобальных переменных, и я немного сработал код в коде refs, так что все это соответствует одной строке без запуска полосы прокрутки (на моем дисплее, в любом случае). Очевидно, что с немного большей работой тестирование может быть завершено в массив, так что тестирование будет выполняться итеративно - цикл через массив, в котором запущена функция таймера для информации в массиве. И т.д...это SMOP - простая программа программирования. (Он печатает хеш MD5 факториала, а не самого факториала, потому что легче сравнивать результаты и т. Д. Это указывало на пару ошибок, поскольку я был рефакторинг кода выше. Да, MD5 небезопасен - но я не использую его для безопасности, просто чтобы определить желательные изменения)

use Time::HiRes qw(gettimeofday); 
use Digest::MD5 qw(md5_hex); 
use strict; 
use warnings; 
use bigint; 
use constant factorial_of => 13000; 

my ($s, $i); 

my $l1 = sub {my($n) = @_; for ($i = 2; $i <= $n; $i++) { $s *= $i;  }}; 
my $l2 = sub {my($n) = @_; for ($i = 2; $i <= $n; $i++) { $s = $s * $i; }}; 
my $l3 = sub {my($n) = @_; for ($i = $n; $i > 1; $i--) { $s *= $i;  }}; 
my $l4 = sub {my($n) = @_; for ($i = $n; $i > 1; $i--) { $s = $s * $i; }}; 
my $l5 = sub {my($n) = @_; $i = 2; while ($i <= $n) { $s *= $i;  $i++; }}; 
my $l6 = sub {my($n) = @_; $i = 2; while ($i <= $n) { $s = $s * $i; $i++; }}; 
my $l7 = sub {my($n) = @_; $i = $n; while ($i > 1) { $s *= $i;  $i--; }}; 
my $l8 = sub {my($n) = @_; $i = $n; while ($i > 1) { $s = $s * $i; $i--; }}; 

sub timer 
{ 
    my($n, $code, $tag) = @_; 
    my $t1 = gettimeofday; 
    $s = 1; 
    &$code(factorial_of); 
    my $t2 = gettimeofday; 
    my $md5 = md5_hex($s); 
    printf "Loop %d: %-33s %09.6f (%s)\n", $n, $tag, $t2 - $t1, $md5; 
} 

my $count = 1; 
timer($count++, $l1, 'for - Count up $s *= $i:'); 
timer($count++, $l2, 'for - Count up $s = $s * $i:'); 
timer($count++, $l3, 'for - Count down $s *= $i:'); 
timer($count++, $l4, 'for - Count down $s = $s * $i:'); 
timer($count++, $l5, 'while - Count up $s *= $i:'); 
timer($count++, $l6, 'while - Count up $s = $s * $i:'); 
timer($count++, $l7, 'while - Count down $s *= $i:'); 
timer($count++, $l8, 'while - Count down $s = $s * $i:'); 

Пример вывода (контрольная сумма MD5 сжатую, чтобы избежать разбивая строчки - полное значение 584b3ab832577fd1390970043efc0ec8):

Loop 1: for - Count up $s *= $i:  12.853630 (584b3ab8...3efc0ec8) 
Loop 2: for - Count up $s = $s * $i: 20.854735 (584b3ab8...3efc0ec8) 
Loop 3: for - Count down $s *= $i:  14.798155 (584b3ab8...3efc0ec8) 
Loop 4: for - Count down $s = $s * $i: 23.699913 (584b3ab8...3efc0ec8) 
Loop 5: while - Count up $s *= $i:  12.972428 (584b3ab8...3efc0ec8) 
Loop 6: while - Count up $s = $s * $i: 21.192956 (584b3ab8...3efc0ec8) 
Loop 7: while - Count down $s *= $i:  14.555620 (584b3ab8...3efc0ec8) 
Loop 8: while - Count down $s = $s * $i: 23.790795 (584b3ab8...3efc0ec8) 

I. последовательно наблюдайте небольшой (< 1%) штраф за цикл «while» за соответствующий цикл «за», но у меня нет хорошего объяснения Это.

+0

@ Джонатан Леффлер, спасибо большое! Ваш код иллюстрации очень поучителен. Спасибо :) – Mike

+0

@Joanthan, спасибо за обновленный код. Я всегда думал, что $ s * = $ i 'и' $ s = $ s * $ i 'и $ i ++ и $ i-- делали то же самое разными способами но я был неправ.Большое спасибо за указание на это :) Я сейчас изменил vs для скриптов, и теперь у меня есть: my $ now = time; my $ n = shift; my $ i = 2; my $ s = 1; for (; $ i <= $ n; $ i ++) { $ s * = $ i; } И my $ n = shift; my $ i = 2; my $ s = 1; while ($ i <= $ n) { $ s * = $ n; $ i ++; } Они выглядят похожими. Результат: быстрее. Я не уверен, но что-то не так с моим дизайном эксперимента? Я запустил ваш код, результат был медленным. – Mike

+1

@Mike: У меня нет хорошего отношения к остальным проблемам. Основными моментами являются (1) проблема в основном в «bigint» и (2) вполне вероятно, что остаточные различия «while vs for» глубоко скрыты в байтовом коде Perl. Я получил некоторые изменения в таймингах - в основном порядка 0,1 секунды или около того, если не было также резервного копирования (TimeMachine - TimeCapsule); Я выбрал 13000 в качестве своего тестового номера, чтобы получить достаточно большое количество, чтобы получить разумные моменты, не будучи настолько большим, чтобы сделать его неудобным для запуска тестов (например, 1 час). –

5

Я бы упасть в шоке, если на самом деле какой-либо «реальной» разница между время и петлями , Предполагая, что они выполняют «точные» одно и то же, они должны быть оптимизированы интерпретатором, чтобы быть более или менее идентичными.

Я бы сказал, что разница была, вероятно, не более чем другими процессами, которые по-разному соперничали за ресурсы во время двух казней.

Даже если была разница, не попадайте в The Sad Tragedy of Micro-Optimization Theater.

+0

@ Моринар, я только что закончил предложенную вами статью. Спасибо, спасибо. – Mike

5

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

  • Эти две петли не так похожи, как могли бы быть. Один использует $s *= $n, а другой использует $s = $s * $i (как указывает Джонатан Леффлер). Один использует $n--, а другой использует $i++ (кто знает, отличаются ли они скоростью?).

  • Если нас интересует for против while, нет необходимости привлекать bigint. Это только путает тему. В частности, ваш скрипт while зависит только от одного объекта bigint ($s), тогда как ваш сценарий for использует два из них ($s и $i). Меня не удивляет, что скрипт for работает медленнее.

Перепишите свою петлю, чтобы быть как можно более близкими, держать факториал достаточно малы, так что вам не придется использовать bigint и использовать Benchmark модуль. Затем вы можете провести честную гонку «голова к голове» for против while. Мне будет интересно узнать, что вы найдете.

+1

@FM, мой эксперимент был настолько плохо спроектирован, что мой вывод из результата почти не имел отношения к вопросу, который я опубликовал. Это был полный провал. Ну, в любом случае спасибо за то, что оставили мне эти поучительные комментарии. Похоже, я всегда могу узнать кое-что из вас, ребята :) – Mike

+1

@Mike Не будьте слишком строги к себе. Бенчмаркинг сложный, и даже опытные программисты ошибаются в их настройке. Например: http://stackoverflow.com/questions/1083269/is-perls-unpack-ever-faster-than-substr и http://stackoverflow.com/questions/1960779/why-does-perls-tr-n -get-медленнее и медленнее, по мере линии длины-увеличение. Возможно, ваш тест был ошибочным, но вопрос был успешным, потому что вы узнали некоторые полезные вещи. :) – FMc