File Coverage

blib/lib/Parallel/Loops.pm
Criterion Covered Total %
statement 226 244 92.6
branch 35 46 76.0
condition 1 3 33.3
subroutine 34 42 80.9
pod 3 8 37.5
total 299 343 87.1


line stmt bran cond sub pod time code
1             package Parallel::Loops;
2              
3             our $VERSION='0.08';
4              
5             # For Tie::ExtraHash - This was the earliest perl version in which I found this
6             # class
7 16     16   222880 use 5.008;
  16         48  
8              
9             =head1 NAME
10              
11             Parallel::Loops - Execute loops using parallel forked subprocesses
12              
13             =encoding utf-8
14              
15             =head1 SYNOPSIS
16              
17             use Parallel::Loops;
18              
19             my $maxProcs = 5;
20             my $pl = Parallel::Loops->new($maxProcs);
21              
22             my @parameters = ( 0 .. 9 );
23              
24             # We want to perform some hefty calculation for each @input and
25             # store each calculation's result in %output. For that reason, we
26             # "tie" %output, so that changes to %output in any child process
27             # (see below) are automatically transfered and updated in the
28             # parent also.
29              
30             my %returnValues;
31             $pl->share( \%returnValues );
32              
33             $pl->foreach( \@parameters, sub {
34             # This sub "magically" executed in parallel forked child
35             # processes
36              
37             # Lets just create a simple example, but this could be a
38             # massive calculation that will be parallelized, so that
39             # $maxProcs different processes are calculating sqrt
40             # simultaneously for different values of $_ on different CPUs
41             # (Do see 'Performance' / 'Properties of the loop body' below)
42              
43             $returnValues{$_} = sqrt($_);
44             });
45             foreach (@parameters) {
46             printf "i: %d sqrt(i): %f\n", $_, $returnValues{$_};
47             }
48              
49             You can also use @arrays instead of %hashes, and/or while loops
50             instead of foreach:
51              
52             my @returnValues;
53             $pl->share(\@returnValues);
54              
55             my $i = 0;
56             $pl->while ( sub { $i++ < 10 }, sub {
57             # This sub "magically" executed in parallel forked
58             # child processes
59              
60             push @returnValues, [ $i, sqrt($i) ];
61             });
62              
63             And you can have both foreach and while return values so that $pl->share()
64             isn't required at all:
65              
66             my $maxProcs = 5;
67             my $pl = Parallel::Loops->new($maxProcs);
68             my %returnValues = $pl->foreach( [ 0..9 ], sub {
69             # Again, this is executed in a forked child
70             $_ => sqrt($_);
71             });
72              
73             =head1 DESCRIPTION
74              
75             Often a loop performs calculations where each iteration of the loop
76             does not depend on the previous iteration, and the iterations really
77             could be carried out in any order.
78              
79             This module allows you to run such loops in parallel using all the
80             CPUs at your disposal.
81              
82             Return values are automatically transfered from children to parents via
83             %hashes or @arrays, that have explicitly been configured for that sort
84             of sharing via $pl->share(). Hashes will transfer keys that are
85             set in children (but not cleared or unset), and elements that are
86             pushed to @arrays in children are pushed to the parent @array too (but
87             note that the order is not guaranteed to be the same as it would have
88             been if done all in one process, since there is no way of knowing
89             which child would finish first!)
90              
91             If you can see past the slightly awkward syntax, you're basically
92             getting foreach and while loops that can run in parallel without
93             having to bother with fork, pipes, signals etc. This is all handled
94             for you by this module.
95              
96             =head2 foreach loop
97              
98             $pl->foreach($arrayRef, $childBodySub)
99              
100             Runs $childBodySub->() with $_ set foreach element in @$arrayRef, except that
101             $childBodySub is run in a forked child process to obtain parallelism.
102             Essentially, this does something conceptually similar to:
103              
104             foreach(@$arrayRef) {
105             $childBodySub->();
106             }
107              
108             Any setting of hash keys or pushing to arrays that have been set with
109             $pl->share() will automagically appear in the hash or array in the parent
110             process.
111              
112             If you like loop variables, you can run it like so:
113              
114             $pl->foreach( \@input, sub {
115             my $i = $_;
116             .. bla, bla, bla ... $output{$i} = sqrt($i);
117             }
118             );
119              
120             =head2 while loop
121              
122             $pl->while($conditionSub, $childBodySub [,$finishSub])
123              
124             Essentially, this does something conceptually similar to:
125              
126             while($conditionSub->()) {
127             $childBodySub->();
128             }
129              
130             except that $childBodySub->() is executed in a forked child process.
131             Return values are transfered via share() like in L above.
132              
133             =head3 While loops must affect condition outside $childBodySub
134              
135             Note that incrementing $i in the $childBodySub like in this example
136             B:
137              
138             $pl->while( sub { $i < 5 },
139             sub {
140             $output{$i} = sqrt($i);
141             # Won't work!
142             $i++
143             }
144             );
145              
146             Because $childBodySub is executed in a child, and so while $i would
147             be incremented in the child, that change would not make it to the
148             parent, where $conditionSub is evaluated. The changes that make
149             $conditionSub return false eventually I take place outside
150             the $childBodySub so it is executed in the parent. (Adhering to
151             the parallel principle that one iteration may not affect any other
152             iterations - including whether to run them or not)
153              
154             =head3 Optional $finishSub parameter
155              
156             In order to track progress, an optional C<$finishSub> can be provided. It will
157             be called whenever a child finishes. The return value from the C<$conditionSub>
158             is remembered and provided to the C<$finishSub> as a reference:
159              
160             my $i = 0;
161             my %returnValues = $pl->while (
162             sub { $i++ < 10 ? $i : 0 },
163             sub {
164             return ($i, sqrt($i));
165             },
166             sub {
167             my ($i) = @_;
168             printf "Child %d has finished\n", $i;
169             }
170             );
171              
172             =head2 share
173              
174             $pl->share(\%output, \@output, ...)
175              
176             Each of the arguments to share() are instrumented, so that when a
177             hash key is set or array element pushed in a child, this is transfered
178             to the parent's hash or array automatically when a child is finished.
179              
180             B Only keys being set like C<$hash{'key'} = 'value'> and
181             arrays elements being pushed like C will be transfered to
182             the parent. Unsetting keys, or setting particluar array elements with
183             $array[3]='value' will be lost if done in the children. Also, if two different
184             children set a value for the same key, a random one of them will be seen by the
185             parent.
186              
187             In the parent process all the %hashes and @arrays are full-fledged, and you can
188             use all operations. But only these mentioned operations in the child processes
189             make it back to the parent.
190              
191             =head3 Array element sequence not defined
192              
193             Note that when using share() for @returnValue arrays, the sequence of elements
194             in @returnValue is not guaranteed to be the same as you'd see with a normal
195             sequential while or foreach loop, since the calculations are done in parallel
196             and the children may end in an unexpected sequence. But if you don't really
197             care about the order of elements in the @returnValue array then share-ing an
198             array can be useful and fine.
199              
200             If you need to be able to determine which iteration generated what output, use
201             a hash instead.
202              
203             =head2 Recursive forking is possible
204              
205             Note that no check is performed for recursive forking: If the main
206             process encouters a loop that it executes in parallel, and the
207             execution of the loop in child processes also encounters a parallel
208             loop, these will also be forked, and you'll essentially have
209             $maxProcs^2 running processes. It wouldn't be too hard to implement
210             such a check (either inside or outside this package).
211              
212             =head1 Exception/Error Handling / Dying
213              
214             If you want some measure of exception handling you can use eval in the child
215             like this:
216              
217             my %errors;
218             $pl->share( \%errors );
219             my %returnValues = $pl->foreach( [ 0..9 ], sub {
220             # Again, this is executed in a forked child
221             eval {
222             die "Bogus error"
223             if $_ == 3;
224             $_ => sqrt($_);
225             };
226             if ($@) {
227             $errors{$_} = $@;
228             }
229             });
230              
231             # Now test %errors. $errors{3} should exist as the only element
232              
233             Also, be sure not to call exit() in the child. That will just exit the child
234             and that doesn't work. Right now, exit just makes the parent fail no-so-nicely.
235             Patches to this that handle exit somehow are welcome.
236              
237             =head1 Performance
238              
239             =head2 Properties of the loop body
240              
241             Keep in mind that a child process is forked every time while or foreach calls
242             the provided sub. For use of Parallel::Loops to make sense, each invocation
243             needs to actually do some serious work for the performance gain of parallel
244             execution to outweigh the overhead of forking and communicating between the
245             processes. So while sqrt() in the example above is simple, it will actually be
246             slower than just running it in a standard foreach loop because of the overhead.
247              
248             Also, if each loop sub returns a massive amount of data, this needs to be
249             communicated back to the parent process, and again that could outweigh parallel
250             performance gains unless the loop body does some heavy work too.
251              
252             =head2 Linux and Windows Comparison
253              
254             On the same VMware host, I ran this script in Debian Linux and Windows XP
255             virtual machines respectively. The script runs a "no-op" sub in 1000 child
256             processes two in parallel at a time
257              
258             my $pl = Parallel::Loops->new(2);
259             $pl->foreach( [1..1000], sub {} );
260              
261             For comparison, that took:
262              
263             7.3 seconds on Linux
264             43 seconds on Strawberry Perl for Windows
265             240 seconds on Cygwin for Windows
266              
267             =head2 fork() e.g. on Windows
268              
269             On some platforms the fork() is emulated. Be sure to read perlfork.
270              
271             =head2 Temporary files unless select() works - e.g. on Windows
272              
273             E.g. on Windows, select is only supported for sockets, and not for pipes. So we
274             use temporary files to store the information sent from the child to the parent.
275             This adds a little extra overhead. See perlport for other platforms where there
276             are problems with select. Parallel::Loops tests for a working select() and uses
277             temporary files otherwise.
278              
279             =head1 SEE ALSO
280              
281             This module uses fork(). ithreads could have been possible too, but was not
282             chosen. You may want to check out:
283              
284             When to use forks, when to use threads ...?
285             L
286              
287             The forks module (not used here)
288             L
289              
290             threads in perlthrtut
291             L
292              
293             =head1 DEPENDENCIES
294              
295             I believe this is the only dependency that isn't part of core perl:
296              
297             use Parallel::ForkManager;
298              
299             These should all be in perl's core:
300              
301             use Storable;
302             use IO::Handle;
303             use Tie::Array;
304             use Tie::Hash;
305              
306             =head1 BUGS / ENHANCEMENTS
307              
308             No bugs are known at the moment. Send any reports to peter@morch.com.
309              
310             Enhancements:
311              
312             Optionally prevent recursive forking: If a forked child encounters a
313             Parallel::Loop it should be possible to prevent that Parallel::Loop instance to
314             also create forks.
315              
316             Determine the number of CPUs so that new()'s $maxProcs parameter can be
317             optional. Could use e.g. Sys::Sysconf, UNIX::Processors or Sys::CPU.
318              
319             Maybe use function prototypes (see Prototypes under perldoc perlsub).
320              
321             Then we could do something like
322              
323             pl_foreach @input {
324             yada($_);
325             };
326             or
327              
328             pl_foreach $pl @input {
329             yada($_);
330             };
331              
332             instead of
333              
334             $pl->foreach(\@input, sub {
335             yada($_);
336             });
337              
338             and so on, where the first suggestion above means global variables (yikes!).
339             Unfortunately, methods aren't supported by prototypes, so this will never be
340             posssible:
341              
342             $pl->foreach @input {
343             yada($_);
344             };
345              
346             An alternative pointed out by the perlmonks chatterbox could be to use
347             L "if I can stand
348             pain".
349              
350             =head1 SOURCE REPOSITORY
351              
352             See the git source on github L
353              
354             =head1 COPYRIGHT
355              
356             Copyright (c) 2008 Peter Valdemar Mørch
357              
358             All right reserved. This program is free software; you can redistribute it
359             and/or modify it under the same terms as Perl itself.
360              
361             =head1 AUTHOR
362              
363             Peter Valdemar Mørch
364              
365             =cut
366              
367 16     16   64 use strict;
  16         16  
  16         320  
368 16     16   64 use warnings;
  16         80  
  16         400  
369              
370 16     16   48 use Carp;
  16         16  
  16         960  
371 16     16   7968 use IO::Handle;
  16         79136  
  16         656  
372 16     16   7600 use IO::Select;
  16         19264  
  16         640  
373 16     16   10144 use File::Temp qw(tempfile);
  16         199008  
  16         880  
374 16     16   8848 use Storable;
  16         39120  
  16         928  
375 16     16   8272 use Parallel::ForkManager;
  16         120320  
  16         20016  
376              
377             sub new {
378 16     16 0 1248 my ($class, $maxProcs, %options) = @_;
379 16         80 my $self = {
380             maxProcs => $maxProcs,
381             shareNr => 0,
382             workingSelect => testWorkingSelect(),
383             };
384 16         64 return bless $self, $class;
385             }
386              
387             sub testWorkingSelect {
388 16     16 0 144 my $reader = IO::Handle->new();
389 16         384 my $writer = IO::Handle->new();
390 16 50       592 pipe( $reader, $writer )
391             or die "Couldn't open a pipe";
392 16         80 $writer->autoflush(1);
393 16         768 my $select = IO::Select->new();
394 16         1280 $select->add($reader);
395 16         1248 print $writer "test\n";
396              
397             # There should be data right away, so lets not risk blocking if it is
398             # unreliable
399 16         80 my @handles = $select->can_read(0);
400 16         688 my $working = (scalar(@handles) == 1);
401              
402 16         80 close $reader;
403 16         144 close $writer;
404              
405 16         224 return $working;
406             }
407              
408             sub share {
409 64     64 1 19424 my ($self, @tieRefs) = @_;
410 64         144 foreach my $ref (@tieRefs) {
411 64 100       224 if (ref $ref eq 'HASH') {
    100          
412 16         48 my %initialContents = %$ref;
413             # $storage will point to the Parallel::Loops::TiedHash object
414 16         32 my $storage;
415 16         128 tie %$ref, 'Parallel::Loops::TiedHash', $self, \$storage;
416 16         224 %$ref = %initialContents;
417 16         128 push @{$$self{tieObjects}}, $storage;
  16         64  
418 16         256 push @{$$self{tieHashes}}, [$$self{shareNr}, $ref];
  16         64  
419             } elsif (ref $ref eq 'ARRAY') {
420 16         32 my @initialContents = @$ref;
421             # $storage will point to the Parallel::Loops::TiedArray object
422 16         16 my $storage;
423 16         96 tie @$ref, 'Parallel::Loops::TiedArray', $self, \$storage;
424 16         80 @$ref = @initialContents;
425 16         32 push @{$$self{tieObjects}}, $storage;
  16         32  
426 16         16 push @{$$self{tieArrays}}, [$$self{shareNr}, $ref];
  16         32  
427             } else {
428 32         4304 croak "Only unblessed hash and array refs are supported by share";
429             }
430 32         80 $$self{shareNr}++;
431             }
432             }
433              
434             sub in_child {
435 250     250 0 427 my ($self) = @_;
436 250   33     1280 return $$self{forkManager} && $$self{forkManager}{in_child};
437             }
438              
439             sub readChangesFromChild {
440 110     110 0 393 my ($self, $childRdr, $childFinishSub) = @_;
441              
442 110         167 my $childOutput;
443              
444 110 100       327 if ($$self{workingSelect}) {
445 97         674 local $/;
446 97         3358 $childOutput = <$childRdr>;
447             } else {
448 13         331 my $filename = <$childRdr>;
449 13 50       643 open my $in, $filename
450             or die "Couldn't open $filename";
451 13         42 binmode $in;
452             {
453 13         30 local $/;
  13         71  
454 13         252 $childOutput = <$in>;
455             }
456 13         117 close $in;
457 13         1304 unlink $filename;
458              
459             }
460 110 50       395 die "Error getting result contents from child"
461             if $childOutput eq '';
462              
463 110         190 my @output;
464 110         374 eval {
465 110         161 @output = @{ Storable::thaw($childOutput) };
  110         1504  
466             };
467 110 50       5125 if ($@) {
468 0         0 die "Error interpreting result from child: $@";
469             }
470 110         185 my $error = shift @output;
471 110         225 my $retval = shift @output;
472              
473 110         138 foreach my $set (@{$$self{tieHashes}}) {
  110         711  
474 110         322 my ($outputNr, $h) = @$set;
475 110         102 foreach my $k (keys %{$output[$outputNr]}) {
  110         416  
476 110         1373 $$h{$k} = $output[$outputNr]{$k};
477             }
478             }
479 110         182 foreach my $set (@{$$self{tieArrays}}) {
  110         221  
480 110         296 my ($outputNr, $a) = @$set;
481 110         98 foreach my $v (@{$output[$outputNr]}) {
  110         196  
482 110         726 push @$a, $v;
483             }
484             }
485 110 50       239 if ($error) {
486 0         0 die "Error from child: $error";
487             }
488 110 50       190 $childFinishSub->()
489             if $childFinishSub;
490 110         416 return @$retval;
491             }
492              
493             sub printChangesToParent {
494 15     15 0 76 my ($self, $error, $retval, $parentWtr) = @_;
495 15         66 my $outputNr = 0;
496 15         40 my @childInfo = ($error, $retval);
497 15         98 foreach (@{$$self{tieObjects}}) {
  15         138  
498 30         323 push @childInfo, $_->getChildInfo();
499             }
500             {
501 15         27 local $SIG{PIPE} = sub {
502 0     0   0 die "Couldn't print to pipe";
503 15         1265 };
504 15 100       101 if ($$self{workingSelect}) {
505 10         404 print $parentWtr Storable::freeze(\@childInfo);
506             } else {
507 5         79 my ($fh, $filename) = tempfile();
508 5         3337 binmode $fh;
509 5         94 print $fh Storable::freeze(\@childInfo);
510 5         4928 close $fh;
511 5         164 print $parentWtr $filename;
512             }
513             }
514             }
515              
516             sub while {
517 33     33 1 308 my ($self, $continueSub, $bodySub, $finishSub) = @_;
518 33         44 my @retvals;
519              
520             # This is used if $$self{workingSelect}
521 33         56 my $childCounter = 0;
522 33         44 my $nrRunningChildren = 0;
523 33         256 my $select = IO::Select->new();
524              
525             # Else this is used
526 33         293 my %childHandles;
527              
528 33         374 my $fm = Parallel::ForkManager->new($$self{maxProcs});
529 33         17077 $$self{forkManager} = $fm;
530 33         60 my %childFinishSubs;
531             $fm->run_on_finish( sub {
532 114     114   66104566 my ($pid) = @_;
533 114 100       669 if ($$self{workingSelect}) {
534 101         463 $nrRunningChildren--;
535             } else {
536 13         41 my $childRdr = $childHandles{$pid};
537             push @retvals, $self->readChangesFromChild(
538 13         149 $childRdr, $childFinishSubs{$childRdr}
539             );
540 13         168 close $childRdr;
541             }
542 33         302 });
543 33         329 while (my $childData = $continueSub->()) {
544             # Setup pipes so the child can send info back to the parent about
545             # output data.
546 135         3413 my $parentWtr = IO::Handle->new();
547 135         7683 my $childRdr = IO::Handle->new();
548 135 50       5824 pipe( $childRdr, $parentWtr )
549             or die "Couldn't open a pipe";
550 135         330 binmode $parentWtr;
551 135         179 binmode $childRdr;
552 135         1247 $parentWtr->autoflush(1);
553              
554 135 50       8832 if ($finishSub) {
555             $childFinishSubs{$childRdr} = sub {
556 0     0   0 $finishSub->($childData);
557 0         0 };
558             }
559              
560 135 100       450 if ($$self{workingSelect}) {
561             # Read data from children that are ready. Block if maxProcs has
562             # been reached, so that we are sure to close some file handle(s).
563             my @ready = $select->can_read(
564 115 100       1307 $nrRunningChildren >= $$self{maxProcs} ? undef : 0
565             );
566 115         4159 for my $fh ( @ready ) {
567             push @retvals, $self->readChangesFromChild(
568 63         687 $fh, $childFinishSubs{$fh}
569             );
570 63         343 $select->remove($fh);
571 63         2915 close $fh;
572             }
573             }
574              
575 135         578 my $pid = $fm->start( ++$childCounter );
576 135 100       125110 if ($pid) {
577             # We're running in the parent...
578 120         2563 close $parentWtr;
579 120 100       461 if ($$self{workingSelect}) {
580 105         542 $nrRunningChildren++;
581 105         2286 $select->add($childRdr);
582             } else {
583 15         191 $childHandles{$pid} = $childRdr;
584             }
585 120         14256 next;
586             }
587              
588             # We're running in the child
589 15         725 close $childRdr;
590              
591 15         225 my @retval;
592 15         230 eval {
593 15         234 @retval = $bodySub->();
594             };
595 15         102 my $error = $@;
596              
597 15 50       146 if (! defined wantarray) {
598             # Lets not waste any energy printing stuff to the parent, if the
599             # parent isn't going to use the return values anyway
600 15         55 @retval = ();
601             }
602              
603 15         151 $self->printChangesToParent($error, \@retval, $parentWtr);
604 15         1794 close $parentWtr;
605              
606 15         582 $fm->finish($childCounter); # pass an exit code to finish
607             }
608              
609 18 100       210 if ($$self{workingSelect}) {
610 17         257 while (my @ready = $select->can_read()) {
611 34         3916 for my $fh (@ready) {
612             push @retvals, $self->readChangesFromChild(
613 34         314 $fh, $childFinishSubs{$fh}
614             );
615 34         168 $select->remove($fh);
616 34         1466 close $fh;
617             }
618             }
619             }
620              
621 18         452 $fm->wait_all_children;
622 18         298 delete $$self{forkManager};
623 18         202 return @retvals;
624             }
625              
626             # foreach is implemented via while above
627             sub foreach {
628 16     16 1 112 my ($self, $varRef, $arrayRef, $sub);
629 16 50       48 if (ref $_[1] eq 'ARRAY') {
630 16         32 ($self, $arrayRef, $sub) = @_;
631             } else {
632             # Note that this second usage is not documented (and hence not
633             # supported). It isn't really useful, but this is how to use it just in
634             # case:
635             #
636             # my $foo;
637             # my %returnValues = $pl->foreach( \$foo, [ 0..9 ], sub {
638             # $foo => sqrt($foo);
639             # });
640 0         0 ($self, $varRef, $arrayRef, $sub) = @_;
641             }
642 16         16 my $i = -1;
643 81     81   105 $self->while( sub { ++$i <= $#{$arrayRef} }, sub {
  81         583  
644             # Setup either $varRef or $_, if no such given before calling $sub->()
645 5 50   5   67 if ($varRef) {
646 0         0 $$varRef = $arrayRef->[$i];
647             } else {
648 5         47 $_ = $arrayRef->[$i];
649             }
650 5         110 $sub->();
651 16         208 });
652             }
653              
654             package Parallel::Loops::TiedHash;
655 16     16   160 use Tie::Hash;
  16         16  
  16         352  
656 16     16   112 use base 'Tie::ExtraHash';
  16         16  
  16         7952  
657              
658             sub TIEHASH {
659 16     16   32 my ( $class, $loops, $storageRef ) = @_;
660 16         80 my $storage = bless [ {}, { loops => $loops, childKeys => {} } ], $class;
661 16         48 $$storageRef = $storage;
662 16         48 return $storage;
663             }
664              
665             sub STORE {
666 125     125   1512 my ( $data, $key, $value ) = @_;
667              
668 125         170 my $hash = $$data[0];
669 125         139 my $extra = $$data[1];
670 125         260 my $loops = $$extra{loops};
671              
672 125 100       416 if ( $loops->in_child() ) {
673 15         123 $$extra{childKeys}{$key} = $value;
674             }
675              
676             # warn sprintf "Setting $key to $value";
677 125         804 $$hash{$key} = $value;
678             }
679              
680             sub getChildInfo {
681 15     15   591 my ($self, $outputNr) = @_;
682 15         29 my $extra = $$self[1];
683 15         86 return $extra->{childKeys};
684             }
685              
686             package Parallel::Loops::TiedArray;
687 16     16   7440 use Tie::Array;
  16         12992  
  16         368  
688 16     16   96 use base 'Tie::Array';
  16         16  
  16         5104  
689              
690             sub TIEARRAY {
691 16     16   32 my ( $class, $loops, $storageRef ) = @_;
692 16         192 my $storage = bless { arr => [], loops => $loops, childArr => [] }, $class;
693 16         32 $$storageRef = $storage;
694 16         32 return $storage;
695             }
696              
697 159     159   91078 sub FETCHSIZE { scalar @{ $_[0]->{arr} } }
  159         456  
698 0     0   0 sub STORESIZE { $#{ $_[0]->{arr} } = $_[1] - 1 }
  0         0  
699 90     90   424 sub STORE { $_[0]->{arr}->[ $_[1] ] = $_[2] }
700 180     180   689 sub FETCH { $_[0]->{arr}->[ $_[1] ] }
701 51     51   9727 sub CLEAR { @{ $_[0]->{arr} } = () }
  51         421  
702 0     0   0 sub POP { pop( @{ $_[0]->{arr} } ) }
  0         0  
703 0     0   0 sub SHIFT { shift( @{ $_[0]->{arr} } ) }
  0         0  
704 0     0   0 sub UNSHIFT { my $o = shift; unshift( @{ $o->{arr} }, @_ ) }
  0         0  
  0         0  
705 0     0   0 sub EXISTS { exists $_[0]->{arr}->[ $_[1] ] }
706 0     0   0 sub DELETE { delete $_[0]->{arr}->[ $_[1] ] }
707              
708             sub PUSH {
709 125     125   402 my $self = shift;
710              
711 125 100       253 if ( $$self{loops}->in_child() ) {
712 15         463 push( @{ $self->{childArr} }, @_ );
  15         122  
713             }
714              
715 125         151 push( @{ $self->{arr} }, @_ );
  125         815  
716             }
717              
718             sub getChildInfo {
719 15     15   29 my ($self) = @_;
720 15         43 return $self->{childArr};
721             }
722              
723             1;