source: main/trunk/package-kits/scripts/perllib/Hash/Ordered/Benchmarks.pod@ 29679

Last change on this file since 29679 was 29679, checked in by Jeremy Symon, 9 years ago

Switch to an Ordered Hash to stop the XML files from being scrambled

File size: 13.2 KB
Line 
1# PODNAME: Hash::Ordered::Benchmarks
2# ABSTRACT: Ordered hash benchmarking
3
4__END__
5
6=pod
7
8=encoding UTF-8
9
10=head1 NAME
11
12Hash::Ordered::Benchmarks - Ordered hash benchmarking
13
14=head1 VERSION
15
16version 0.002
17
18=head1 INTRODUCTION
19
20The L<Hash::Ordered> internals are simple: a hash of data and an array of
21ordered keys. I thought this would perform well for common tasks and
22likely outperform more complicated ordered hash implementations, so I
23decided to do some benchmarking to test it.
24
25In my review of alternatives to C<Hash::Ordered>, six seemed sufficiently
26general-purpose to be worth benchmarking. The modules tested are listed in
27the benchmark output in shorthand:
28
29=over 4
30
31=item *
32
33L<Array::AsHash> — denoted C<a:ah>
34
35=item *
36
37L<Array::OrdHash> — denoted C<a:oh>
38
39=item *
40
41L<Data::XHash> — denoted C<d:xh>
42
43=item *
44
45L<Hash::Ordered> — denoted C<h:o>
46
47=item *
48
49L<Tie::Hash::Indexed> — denoted C<t:h:i>
50
51=item *
52
53L<Tie::IxHash> — denoted C<t:ix>
54
55=item *
56
57L<Tie::LLHash> — denoted C<t:llh>
58
59=back
60
61Note that L<Tie::Hash::Indexed> is written in XS and also may require
62forced installation as its tests often fail for Perl 5.18+ due to the
63hash randomization change.
64
65If there are different methods of doing something with a module, the
66variations are described in each section below.
67
68=head1 BENCHMARKS
69
70I conducted benchmarking with the L<Benchmark> module. The test script is
71in the C<devel> directory of the distribution. Tests were run on Perl
725.21.0 (essentially the same as 5.20.0) on a Mac Book Pro. Each benchmark
73ran for 5 CPU seconds.
74
75Benchmarks were run at several different scales to reveal differences in
76efficiency as hash size grows. The details are described in each section
77below.
78
79A seed list of keys and values was generated from random integers
80using L<Math::Random::MT::Auto>. The same seed list was used for
81all benchmarks unless otherwise noted.
82
83I did not test advanced features of these modules, as apples-to-apples
84comparison is difficult. Still, the performance on common, simple measures
85could suggest how features that combine these operations might perform.
86
87=head2 Ordered hash creation
88
89I tested hash creation for 10, 100 and 1000 elements. For some modules
90there were different options for creating a hash:
91
92=over 4
93
94=item *
95
96C<Array::AsHash> takes an array-reference with an option to use it directly or to clone it. In one case I provided the seed list array reference with the clone option to true ("a:ah_cp"). In another case I created a new array reference from the seed list and provided it directly ("a:ah_rf").
97
98=item *
99
100C<Tie::IxHash> can be initialized either with C<new> ("t:ix_oo") or via C<tie> ("t:ix_th").
101
102=item *
103
104C<Data::XHash> can be created with a list ("t:xh_ls") or an array reference ("t:xh_rf").
105
106=back
107
108As expected, when C<Array::AsHash> gets an array reference, it's very fast.
109C<Tie::Hash::Indexed> does well here, also. Of the non-XS, more hash-like
110choices, C<Hash::Ordered> does well.
111
112 Results for ordered hash creation for 10 elements
113 t:h:i 129713/s
114 a:ah_rf 104034/s
115 h:o 94121/s
116 a:ah_cp 62539/s
117 t:ix_th 60136/s
118 t:ix_oo 59895/s
119 a:oh 49399/s
120 t:llh 32122/s
121 d:xh_rf 13288/s
122 d:xh_ls 13223/s
123
124 Results for ordered hash creation for 100 elements
125 t:h:i 15026/s
126 a:ah_rf 14304/s
127 h:o 10931/s
128 a:ah_cp 7512/s
129 t:ix_oo 7368/s
130 t:ix_th 7161/s
131 a:oh 6572/s
132 t:llh 3306/s
133 d:xh_ls 1498/s
134 d:xh_rf 1491/s
135
136 Results for ordered hash creation for 1000 elements
137 a:ah_rf 1410/s
138 t:h:i 1285/s
139 h:o 1022/s
140 a:ah_cp 763/s
141 t:ix_oo 703/s
142 t:ix_th 697/s
143 a:oh 694/s
144 t:llh 290/s
145 d:xh_rf 147/s
146 d:xh_ls 146/s
147
148=head2 Getting hash elements
149
150I tested retrieving values for 10% of the keys, randomly selected, from
151hashes of 10, 100 and 1000 elements. The hash was created beforehand so
152the benchmarks reflect only element access.
153
154Some modules had choices for how to retrieve an value, usually between a
155method (denoted with "_oo"), tied hash access ("_th") or with a dereference
156("_rf").
157
158Generally, method calls turned out faster than other approaches for a given
159module, demonstrating the inefficiency of tied objects.
160
161 Results for fetching ~10% of 10 elements
162 h:o 1417712/s
163 d:xh_oo 1231973/s
164 t:ix_oo 1120271/s
165 t:h:i 792250/s
166 d:xh_rf 722683/s
167 t:ix_th 624603/s
168 a:oh 553755/s
169 t:llh 504533/s
170 a:ah 246063/s
171
172 Results for fetching ~10% of 100 elements
173 h:o 244800/s
174 d:xh_oo 181520/s
175 t:ix_oo 175981/s
176 t:h:i 132963/s
177 d:xh_rf 93519/s
178 t:ix_th 82154/s
179 a:oh 68270/s
180 t:llh 57013/s
181 a:ah 28280/s
182
183 Results for fetching ~10% of 1000 elements
184 h:o 24871/s
185 d:xh_oo 19125/s
186 t:ix_oo 17655/s
187 t:h:i 13407/s
188 d:xh_rf 9590/s
189 t:ix_th 8455/s
190 a:oh 6995/s
191 t:llh 5781/s
192 a:ah 2219/s
193
194=head2 Setting hash elements
195
196I tested changing values for 10% of the keys, randomly selected, from
197hashes of 10, 100 and 1000 elements. The hash was created beforehand so
198the benchmarks reflect only element mutation. No new keys were added.
199
200Some modules had choices for how to modify a value, usually between a
201method (denoted with "_oo"), tied hash access ("_th") or with a dereference
202("_rf").
203
204Again, methods outperformed.
205
206 Results for replacing ~10% of 10 elements
207 h:o 1353795/s
208 d:xh_oo 952485/s
209 t:h:i 943983/s
210 t:ix_oo 923874/s
211 t:llh 600717/s
212 d:xh_rf 568693/s
213 a:oh 547233/s
214 t:ix_th 519939/s
215 a:ah 164170/s
216
217 Results for replacing ~10% of 100 elements
218 h:o 197232/s
219 t:h:i 131238/s
220 d:xh_oo 121692/s
221 t:ix_oo 114869/s
222 t:llh 71720/s
223 d:xh_rf 67130/s
224 a:oh 63634/s
225 t:ix_th 59784/s
226 a:ah 16843/s
227
228 Results for replacing ~10% of 1000 elements
229 h:o 20364/s
230 t:h:i 13254/s
231 d:xh_oo 12512/s
232 t:ix_oo 11542/s
233 t:llh 7295/s
234 d:xh_rf 7004/s
235 a:oh 6376/s
236 t:ix_th 6175/s
237 a:ah 1635/s
238
239=head2 Adding hash elements
240
241I tested adding 10, 100 and 1000 elements to an empty hash.
242
243Some modules had choices for how to append a value, usually between a
244method (denoted with "_oo"), tied hash access ("_th") or with a dereference
245("_rf").
246
247For C<Tie::LLHash>, I did not use the "lazy" option, but did the equivalent
248using C<tied> and a method call:
249
250 tied(%tllh)->last( irand(), 42 ) for 1 .. $n;
251
252Generally, it seemed like the differences were smaller than for other
253benchmarks. Methods still outperformed.
254
255 Results for adding 10 elements to empty hash
256 h:o 367588/s
257 t:h:i 300357/s
258 t:ix_oo 263158/s
259 t:ix_th 214085/s
260 t:llh 187981/s
261 a:oh 141308/s
262 a:ah 96523/s
263 d:xh_oo 87498/s
264 d:xh_rf 84316/s
265
266 Results for adding 100 elements to empty hash
267 h:o 66495/s
268 t:h:i 57307/s
269 t:ix_oo 49676/s
270 t:ix_th 38222/s
271 a:oh 35476/s
272 t:llh 27998/s
273 d:xh_oo 24371/s
274 d:xh_rf 22326/s
275 a:ah 14114/s
276
277 Results for adding 1000 elements to empty hash
278 h:o 7217/s
279 t:h:i 6244/s
280 t:ix_oo 5671/s
281 a:oh 4335/s
282 t:ix_th 4313/s
283 d:xh_oo 2977/s
284 t:llh 2899/s
285 d:xh_rf 2683/s
286 a:ah 1466/s
287
288=head2 Deleting hash elements
289
290I tested creating hashes of 10, 100 and 1000 elements and then deleting
29110% of the keys, chosen randomly. I would have liked to have isolated
292creation from deletion, but I couldn't figure out a way to do that given
293how C<Benchmark> runs the same tests over and over.
294
295Some modules had choices for how to delete a value, usually between a
296method (denoted with "_oo"), tied hash access ("_th") or with a dereference
297("_rf").
298
299Interestingly, the performance of different modules changes a lot at the
300three different sizes, revealing implementation differences. (Though
301recall that some of this is the creation performance difference as well as
302deletion difference.)
303
304For example, C<Tie::Hash::Indexed> XS does very well, which could be its
305good creation performance, but could also be good deletion.
306C<Hash::Ordered> does linear search deleting a key, which is slow at larger
307sizes. C<Tie::LLHash> really shines at larger sizes as deleting from a
308linked list is faster than splicing out an element of an array.
309
310 Results for creating 10 element hash then deleting ~10%
311 t:h:i 139517/s
312 h:o 95284/s
313 a:ah 66495/s
314 t:ix_oo 52892/s
315 t:ix_th 50254/s
316 a:oh 45609/s
317 t:llh 28599/s
318 d:xh_rf 13223/s
319 d:xh_oo 13173/s
320
321 Results for creating 100 element hash then deleting ~10%
322 t:h:i 16745/s
323 h:o 6924/s
324 t:ix_oo 4063/s
325 a:oh 3963/s
326 t:ix_th 3590/s
327 a:ah 3014/s
328 t:llh 2459/s
329 d:xh_oo 1449/s
330 d:xh_rf 1434/s
331
332 Results for creating 1000 element hash then deleting ~10%
333 t:h:i 1604/s
334 t:llh 269/s
335 a:oh 171/s
336 d:xh_rf 146/s
337 h:o 144/s
338 d:xh_oo 130/s
339 t:ix_oo 85/s
340 t:ix_th 77/s
341 a:ah 36/s
342
343=head2 Extracting the hash as a list
344
345I tested getting an ordered list of pairs from hashes of 10, 100 and 1000
346elements. The hash was created beforehand so the benchmarks reflect only
347conversion to a list.
348
349Oddly, modules that usually have more than one way to do this don't for
350this. Even C<Tie::IxHash> doesn't really have an OO way to do it, so I did
351it longhand:
352
353 @list = map { $_ => $tix_oo->FETCH($_) } $tix_oo->Keys;
354
355Because C<Array::AsHash> keeps its internal representation as an ordered
356list of pairs, it outperforms the rest handily.
357
358 Results for listing pairs of 10 element hash
359 a:ah 290725/s
360 h:o 170187/s
361 t:ix_oo 92118/s
362 t:h:i 80408/s
363 t:ix_th 48756/s
364 t:llh 38509/s
365 a:oh 36126/s
366 d:xh 35766/s
367
368 Results for listing pairs of 100 element hash
369 a:ah 39222/s
370 h:o 18839/s
371 t:ix_oo 9525/s
372 t:h:i 7742/s
373 a:oh 5081/s
374 t:ix_th 5014/s
375 d:xh 4160/s
376 t:llh 3841/s
377
378 Results for listing pairs of 1000 element hash
379 a:ah 3703/s
380 h:o 1877/s
381 t:ix_oo 961/s
382 t:h:i 768/s
383 a:oh 508/s
384 t:ix_th 505/s
385 d:xh 413/s
386 t:llh 385/s
387
388=head1 CONCLUSION
389
390With the exception of hash creation and element deletion, C<Hash::Ordered>
391consistently outperformed the other ordered hash implementations. Even for
392creation, it was the fastest of the pure-Perl, hash-based implementations,
393often by a large margin.
394
395However, C<Hash::Ordered> deletion gets worse as the hash size grows large.
396This was an intentional trade-off, as keeping an index of keys to allow
397faster deletion would slow down other operations. I think large-scale key
398deletion is probably rare in practice, but frequently using the C<push> or
399C<unshift> methods on existing keys to move them to the end/start of the
400list will have similar performance problems.
401
402C<Array::AsHash>, with the opposite internal implementation compared to
403C<Hash::Ordered>, performs best at creation and listing pairs, but is dead
404last at element access and modification. I believe the poor performance is
405mostly due to extra indirection (e.g. an extra function call) and logic in
406the element access methods. For uses that don't require much element
407access and have lots of creation/serialization, it could still be a useful
408choice.
409
410Generally, every module that depends on C<tie> for some portion of its
411implementation pays a substantial performance penalty.
412C<Tie::Hash::Indexed> — likely because of its XS implementation — performs
413decently, but not well enough in my opinion to justify its use.
414
415As the author of C<Hash::Ordered>, I'm clearly biased, but unless you have
416very large hashes with lots of deletes, I think these benchmarks make a
417very good case for it being the "go to" module for general-purpose ordered
418hashes.
419
420=head1 AUTHOR
421
422David Golden <[email protected]>
423
424=head1 COPYRIGHT AND LICENSE
425
426This software is Copyright (c) 2014 by David Golden.
427
428This is free software, licensed under:
429
430 The Apache License, Version 2.0, January 2004
431
432=cut
Note: See TracBrowser for help on using the repository browser.