📜 ⬆️ ⬇️

Comparison of assignment methods in perl

Recently, I began to investigate the issues of performance and efficiency of Perl programs, and the idea appeared to publish a cycle of simple but illustrative benchmarking tests. I'll start with the simplest and most typical - with assignments. If the topic is interesting - I will continue.

No large program can do without assignments. In perl programs (and not only on it), data is often stored in hash structures, and updating the fields of these hashes is a very typical task. Of course, in perl there are many ways to write code for this - and they all differ in both readability and beauty, and in speed. I wondered what these differences were - and I did a little research.

Suppose we have a $hash - a link to a hash with several fields, and we want to update three of them. A commonplace way to write this is:
  <font color = "gray"> $ hash -> {foo} = 456;
 $ hash -> {bar} = $ bar;
 $ hash -> {baz} = 'baz';

 </ font> 

')
You can use hash slice - and instead of three scalar assignment operators, you can achieve the same effect with one list assignment operator:
  <font color = "gray"> @ $ hash {qw (foo bar baz)} = (456, $ bar, 'baz');

 </ font> 

You can simply assign hashes (specifying inside %$hash to save other fields):
  <font color = "gray">% $ hash = (% $ hash, foo => 456, bar => $ bar, baz => 'baz');

 </ font> 

Finally, you can use the cycle (it is often convenient):
  <font color = "gray"> my% h = (foo => 456, bar => $ bar, baz => 'baz');
 $ hash -> {$ _} = $ h {$ _} for qw (foo bar baz);

 </ font> 

Or write the same thing in the form of a map construct:
  <font color = "gray"> map {$ hash -> {$ _} = $ h {$ _}} qw (foo bar baz);

 </ font> 


What do you think is the fastest way? Run a test that compares the speed of execution of different ways to update the hash fields:
  <font color = "gray"> #! / usr / bin / perl

 use strict;
 use Benchmark qw (cmpthese);

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

 my $ bar = 'bar';

 cmpthese (
	 -ten,
	 {
		 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> 


Here are the results:
               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% -



As you can see, the fastest way is direct assignment. Hash slice is two times slower, designs with a cycle are another 2-3 times slower. The map cycle works slightly faster than the similar for.
However, data can be stored not only in hashes, but also in arrays. A similar test for an array gives similar results:
  <font color = "gray"> #! / usr / bin / perl

 use strict;
 use Benchmark qw (cmpthese);

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

 my $ bar = 'bar';

 cmpthese (
	 -ten,
	 {
		 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% -



The comparison was done for perl 5.8.8 on three machines (a desktop under Windows XP and two servers under FreeBSD). Specific figures vary, but the ratio is about the same everywhere.

PS Somehow ugly here looks like a code wrapped in <pre>. Is there a way to make it prettier?

Source: https://habr.com/ru/post/10817/


All Articles