Сравнение способов присваивания в perl

Perl
Последнее время я стал исследовать вопросы производительности и эффективности perl-программ, и появилась идея опубликовать цикл простых, но наглядных тестов-сравнений. Начну с самого простого и типичного — с присваиваний. Если тема интересна — буду продолжать.

Ни одна большая программа не обходится без присваиваний. В программах на perl (да и не только на нем) данные часто хранят в структурах хэшей, и обновление полей этих хэшей — очень типичная задача. Разумеется, в perl существует множество способов написать код для этого — и все они различаются как по читабельности и красоте, так и по скорости. Мне стало интересно, каковы эти различия — и я провел небольшое исследование.

Пусть у нас есть $hash — ссылка на хэш с несколькими полями, и мы хотим обновить три из них. Банальный способ записать это:
<font color="gray">$hash->{foo} = 456;
$hash->{bar} = $bar;
$hash->{baz} = 'baz';

</font>


Можно воспользоваться hash slice — и вместо трех операторов скалярного присваивания достичь того же эффекта одним оператором присваивания списков:
<font color="gray">@$hash{qw(foo bar baz)} = ( 456, $bar, 'baz' );

</font>

Можно просто присвоить хэши (указав внутри %$hash для того, чтобы сохранились прочие поля):
<font color="gray">%$hash = ( %$hash, foo => 456, bar => $bar, baz => 'baz' );

</font>

Наконец, можно воспользоваться циклом (часто это бывает удобно):
<font color="gray">my %h = ( foo => 456, bar => $bar, baz => 'baz' );
$hash->{$_} = $h{$_} for qw(foo bar baz);

</font>

Или записать то же самое в виде конструкции map:
<font color="gray">map { $hash->{$_} = $h{$_} } qw(foo bar baz);

</font>


Как вы думаете, какой способ самый быстрый? Запустим тест, который сравнит скорость выполнения разных способов обновить поля хэша:
<font color="gray">#!/usr/bin/perl

use strict;
use Benchmark qw(cmpthese);

my $hash = {
	foo => 'foo',
	bar => 123,
	baz => undef,
};

my $bar = 'bar';

cmpthese(
	-10,
	{
		h_direct => sub {
			$hash->{foo} = 456;
			$hash->{bar} = $bar;
			$hash->{baz} = 'baz';
		},
		h_list => sub {
			@$hash{qw(foo bar baz)} = ( 456, $bar, 'baz' );
		},
		h_hash => sub {
			%$hash = ( %$hash, foo => 456, bar => $bar, baz => 'baz' );
		},
		h_for => sub {
			my %h = ( foo => 456, bar => $bar, baz => 'baz' );
			$hash->{$_} = $h{$_} for qw(foo bar baz);
		},
		h_forref => sub {
			my $h = { foo => 456, bar => $bar, baz => 'baz' };
			$hash->{$_} = $h->{$_} for qw(foo bar baz);
		},
		h_map => sub {
			my %h = ( foo => 456, bar => $bar, baz => 'baz' );
			map { $hash->{$_} = $h{$_} } qw(foo bar baz);
		},
		h_mapref => sub {
			my $h = { foo => 456, bar => $bar, baz => 'baz' };
			map { $hash->{$_} = $h->{$_} } qw(foo bar baz);
		},
	} );

</font>


Вот его результаты:
              Rate   h_hash h_forref h_mapref    h_for    h_map  h_list h_direct
h_hash    100913/s       --     -30%     -42%     -43%     -53%    -80%     -91%
h_forref  144297/s      43%       --     -17%     -19%     -32%    -71%     -88%
h_mapref  174524/s      73%      21%       --      -2%     -18%    -65%     -85%
h_for     177449/s      76%      23%       2%       --     -17%    -65%     -85%
h_map     213368/s     111%      48%      22%      20%       --    -58%     -82%
h_list    505768/s     401%     251%     190%     185%     137%      --     -57%
h_direct 1169409/s    1059%     710%     570%     559%     448%    131%       --



Как видим, самый быстрый способ — прямое присваивание. Hash slice работает в два раза медленнее, конструкции с циклом — еще в 2-3 раза медленнее. Цикл map работает несколько быстрее, чем аналогичный for.
Однако данные можно хранить не только в хэшах, но и в массивах. Аналогичный тест для массива дает аналогичные результаты:
<font color="gray">#!/usr/bin/perl

use strict;
use Benchmark qw(cmpthese);

my $list = [ 'foo', 123, undef ];

my $bar = 'bar';

cmpthese(
	-10,
	{
		l_direct => sub {
			$list->[0] = 456;
			$list->[1] = $bar;
			$list->[2] = 'baz';
		},
		l_list => sub {
			@$list[ 0 .. 2 ] = ( 456, $bar, 'baz' );
		},
		l_for => sub {
			my @l = ( 456, $bar, 'baz' );
			$list->[$_] = $l[$_] for 0 .. 2;
		},
		l_forref => sub {
			my $l = [ 456, $bar, 'baz' ];
			$list->[$_] = $l->[$_] for 0 .. 2;
		},
		l_map => sub {
			my @l = ( 456, $bar, 'baz' );
			map { $list->[$_] = $l[$_] } 0 .. 2;
		},
		l_mapref => sub {
			my $l = [ 456, $bar, 'baz' ];
			map { $list->[$_] = $l->[$_] } 0 .. 2;
		},
	} );

</font>
              Rate l_forref    l_for l_mapref    l_map   l_list l_direct
l_forref  197498/s       --     -11%     -17%     -38%     -65%     -86%
l_for     222737/s      13%       --      -6%     -30%     -60%     -84%
l_mapref  237429/s      20%       7%       --     -25%     -58%     -83%
l_map     318127/s      61%      43%      34%       --     -43%     -77%
l_list    559192/s     183%     151%     136%      76%       --     -60%
l_direct 1395278/s     606%     526%     488%     339%     150%       --



Сравнение проводилось для perl 5.8.8 на трех машинах (десктоп под Windows XP и два сервера под FreeBSD). Конкретные цифры различаются, но соотношение везде примерно такое.

P.S. Как-то некрасиво тут смотрится код, обернутый в <pre>. Есть ли способ сделать его покрасивее?
Теги:highloadпроизводительностьwebdevbenchmarkperl
Хабы: Perl
+10
1,8k 3
Комментарии 38

Похожие публикации

Perl-разработчик
от 80 000 до 140 000 ₽Гнездо.ру - платформа эффективной рекламыМожно удаленно
Разработчик С/C++ (Junior/middle)
от 80 000 ₽EltexНовосибирск
PHP-разработчик Highload (Symfony)
до 220 000 ₽OFFERRUMСанкт-Петербург
PHP developer (symfony, highload service)
от 100 000 ₽ВсеИнструменты.руМожно удаленно
Senior PHP developer (symfony, highload, e-commerce)
от 150 000 ₽ВсеИнструменты.руМожно удаленно

Лучшие публикации за сутки