File Coverage

blib/lib/Parallel/Loops.pm
Criterion Covered Total %
statement 226 244 92.6
branch 35 46 76.0
condition 2 3 66.6
subroutine 34 42 80.9
pod 3 8 37.5
total 300 343 87.4


line stmt bran cond sub pod time code
1             package Parallel::Loops;
2              
3             our $VERSION='0.09';
4              
5             # For Tie::ExtraHash - This was the earliest perl version in which I found this
6             # class
7 16     16   850688 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   80 use strict;
  16         32  
  16         400  
368 16     16   80 use warnings;
  16         32  
  16         896  
369              
370 16     16   96 use Carp;
  16         16  
  16         800  
371 16     16   6960 use IO::Handle;
  16         78160  
  16         576  
372 16     16   5952 use IO::Select;
  16         20576  
  16         624  
373 16     16   9280 use File::Temp qw(tempfile);
  16         183648  
  16         800  
374 16     16   7984 use Storable;
  16         40048  
  16         736  
375 16     16   7488 use Parallel::ForkManager;
  16         716800  
  16         21472  
376              
377             sub new {
378 16     16 0 1792 my ($class, $maxProcs, %options) = @_;
379 16         64 my $self = {
380             maxProcs => $maxProcs,
381             shareNr => 0,
382             workingSelect => testWorkingSelect(),
383             };
384 16         96 return bless $self, $class;
385             }
386              
387             sub testWorkingSelect {
388 16     16 0 96 my $reader = IO::Handle->new();
389 16         368 my $writer = IO::Handle->new();
390 16 50       1248 pipe( $reader, $writer )
391             or die "Couldn't open a pipe";
392 16         192 $writer->autoflush(1);
393 16         848 my $select = IO::Select->new();
394 16         192 $select->add($reader);
395 16         960 print $writer "test\n";
396              
397             # There should be data right away, so lets not risk blocking if it is
398             # unreliable
399 16         112 my @handles = $select->can_read(0);
400 16         640 my $working = (scalar(@handles) == 1);
401              
402 16         144 close $reader;
403 16         192 close $writer;
404              
405 16         208 return $working;
406             }
407              
408             sub share {
409 64     64 1 23152 my ($self, @tieRefs) = @_;
410 64         112 foreach my $ref (@tieRefs) {
411 64 100       208 if (ref $ref eq 'HASH') {
    100          
412 16         32 my %initialContents = %$ref;
413             # $storage will point to the Parallel::Loops::TiedHash object
414 16         32 my $storage;
415 16         96 tie %$ref, 'Parallel::Loops::TiedHash', $self, \$storage;
416 16         144 %$ref = %initialContents;
417 16         128 push @{$$self{tieObjects}}, $storage;
  16         48  
418 16         32 push @{$$self{tieHashes}}, [$$self{shareNr}, $ref];
  16         64  
419             } elsif (ref $ref eq 'ARRAY') {
420 16         48 my @initialContents = @$ref;
421             # $storage will point to the Parallel::Loops::TiedArray object
422 16         16 my $storage;
423 16         80 tie @$ref, 'Parallel::Loops::TiedArray', $self, \$storage;
424 16         64 @$ref = @initialContents;
425 16         32 push @{$$self{tieObjects}}, $storage;
  16         32  
426 16         16 push @{$$self{tieArrays}}, [$$self{shareNr}, $ref];
  16         48  
427             } else {
428 32         2880 croak "Only unblessed hash and array refs are supported by share";
429             }
430 32         64 $$self{shareNr}++;
431             }
432             }
433              
434             sub in_child {
435 254     254 0 572 my ($self) = @_;
436 254   66     2238 return $$self{forkManager} && $$self{forkManager}->is_child;
437             }
438              
439             sub readChangesFromChild {
440 112     112 0 1182 my ($self, $childRdr, $childFinishSub) = @_;
441              
442 112         467 my $childOutput;
443              
444 112 100       654 if ($$self{workingSelect}) {
445 99         816 local $/;
446 99         4226 $childOutput = <$childRdr>;
447             } else {
448 13         578 my $filename = <$childRdr>;
449 13 50       800 open my $in, $filename
450             or die "Couldn't open $filename";
451 13         105 binmode $in;
452             {
453 13         45 local $/;
  13         143  
454 13         407 $childOutput = <$in>;
455             }
456 13         177 close $in;
457 13         967 unlink $filename;
458              
459             }
460 112 50       666 die "Error getting result contents from child"
461             if $childOutput eq '';
462              
463 112         204 my @output;
464 112         411 eval {
465 112         172 @output = @{ Storable::thaw($childOutput) };
  112         1631  
466             };
467 112 50       7506 if ($@) {
468 0         0 die "Error interpreting result from child: $@";
469             }
470 112         364 my $error = shift @output;
471 112         185 my $retval = shift @output;
472              
473 112         167 foreach my $set (@{$$self{tieHashes}}) {
  112         390  
474 112         555 my ($outputNr, $h) = @$set;
475 112         207 foreach my $k (keys %{$output[$outputNr]}) {
  112         406  
476 112         1736 $$h{$k} = $output[$outputNr]{$k};
477             }
478             }
479 112         193 foreach my $set (@{$$self{tieArrays}}) {
  112         293  
480 112         518 my ($outputNr, $a) = @$set;
481 112         230 foreach my $v (@{$output[$outputNr]}) {
  112         235  
482 112         1334 push @$a, $v;
483             }
484             }
485 112 50       250 if ($error) {
486 0         0 die "Error from child: $error";
487             }
488 112 50       244 $childFinishSub->()
489             if $childFinishSub;
490 112         620 return @$retval;
491             }
492              
493             sub printChangesToParent {
494 15     15 0 163 my ($self, $error, $retval, $parentWtr) = @_;
495 15         112 my $outputNr = 0;
496 15         118 my @childInfo = ($error, $retval);
497 15         35 foreach (@{$$self{tieObjects}}) {
  15         166  
498 30         444 push @childInfo, $_->getChildInfo();
499             }
500             {
501 15         44 local $SIG{PIPE} = sub {
502 0     0   0 die "Couldn't print to pipe";
503 15         1672 };
504 15 100       181 if ($$self{workingSelect}) {
505 10         191 print $parentWtr Storable::freeze(\@childInfo);
506             } else {
507 5         133 my ($fh, $filename) = tempfile();
508 5         4761 binmode $fh;
509 5         120 print $fh Storable::freeze(\@childInfo);
510 5         813 close $fh;
511 5         166 print $parentWtr $filename;
512             }
513             }
514             }
515              
516             sub while {
517 33     33 1 566 my ($self, $continueSub, $bodySub, $finishSub) = @_;
518 33         200 my @retvals;
519              
520             # This is used if $$self{workingSelect}
521 33         66 my $childCounter = 0;
522 33         66 my $nrRunningChildren = 0;
523 33         393 my $select = IO::Select->new();
524              
525             # Else this is used
526 33         456 my %childHandles;
527              
528 33         628 my $fm = Parallel::ForkManager->new($$self{maxProcs});
529 33         68685 $$self{forkManager} = $fm;
530 33         77 my %childFinishSubs;
531             $fm->run_on_finish( sub {
532 114     114   66106607 my ($pid) = @_;
533 114 100       648 if ($$self{workingSelect}) {
534 101         532 $nrRunningChildren--;
535             } else {
536 13         106 my $childRdr = $childHandles{$pid};
537             push @retvals, $self->readChangesFromChild(
538 13         226 $childRdr, $childFinishSubs{$childRdr}
539             );
540 13         237 close $childRdr;
541             }
542 33         287 });
543 33         353 while (my $childData = $continueSub->()) {
544             # Setup pipes so the child can send info back to the parent about
545             # output data.
546 135         5016 my $parentWtr = IO::Handle->new();
547 135         8427 my $childRdr = IO::Handle->new();
548 135 50       9414 pipe( $childRdr, $parentWtr )
549             or die "Couldn't open a pipe";
550 135         1111 binmode $parentWtr;
551 135         255 binmode $childRdr;
552 135         2836 $parentWtr->autoflush(1);
553              
554 135 50       16275 if ($finishSub) {
555             $childFinishSubs{$childRdr} = sub {
556 0     0   0 $finishSub->($childData);
557 0         0 };
558             }
559              
560 135 100       447 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       1055 $nrRunningChildren >= $$self{maxProcs} ? undef : 0
565             );
566 115         215386 for my $fh ( @ready ) {
567             push @retvals, $self->readChangesFromChild(
568 76         1881 $fh, $childFinishSubs{$fh}
569             );
570 76         443 $select->remove($fh);
571 76         4679 close $fh;
572             }
573             }
574              
575 135         788 my $pid = $fm->start( ++$childCounter );
576 135 100       152731 if ($pid) {
577             # We're running in the parent...
578 120         3398 close $parentWtr;
579 120 100       892 if ($$self{workingSelect}) {
580 105         881 $nrRunningChildren++;
581 105         2615 $select->add($childRdr);
582             } else {
583 15         420 $childHandles{$pid} = $childRdr;
584             }
585 120         20792 next;
586             }
587              
588             # We're running in the child
589 15         522 close $childRdr;
590              
591 15         70 my @retval;
592 15         154 eval {
593 15         452 @retval = $bodySub->();
594             };
595 15         43 my $error = $@;
596              
597 15 50       159 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         53 @retval = ();
601             }
602              
603 15         686 $self->printChangesToParent($error, \@retval, $parentWtr);
604 15         1988 close $parentWtr;
605              
606 15         307 $fm->finish($childCounter); # pass an exit code to finish
607             }
608              
609 18 100       579 if ($$self{workingSelect}) {
610 17         457 while (my @ready = $select->can_read()) {
611 23         34803 for my $fh (@ready) {
612             push @retvals, $self->readChangesFromChild(
613 23         496 $fh, $childFinishSubs{$fh}
614             );
615 23         681 $select->remove($fh);
616 23         1535 close $fh;
617             }
618             }
619             }
620              
621 18         729 $fm->wait_all_children;
622 18         387 delete $$self{forkManager};
623 18         1094 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         32 my $i = -1;
643 81     81   487 $self->while( sub { ++$i <= $#{$arrayRef} }, sub {
  81         968  
644             # Setup either $varRef or $_, if no such given before calling $sub->()
645 5 50   5   390 if ($varRef) {
646 0         0 $$varRef = $arrayRef->[$i];
647             } else {
648 5         22 $_ = $arrayRef->[$i];
649             }
650 5         140 $sub->();
651 16         112 });
652             }
653              
654             package Parallel::Loops::TiedHash;
655 16     16   256 use Tie::Hash;
  16         48  
  16         544  
656 16     16   80 use base 'Tie::ExtraHash';
  16         32  
  16         8096  
657              
658             sub TIEHASH {
659 16     16   48 my ( $class, $loops, $storageRef ) = @_;
660 16         80 my $storage = bless [ {}, { loops => $loops, childKeys => {} } ], $class;
661 16         32 $$storageRef = $storage;
662 16         48 return $storage;
663             }
664              
665             sub STORE {
666 127     127   2215 my ( $data, $key, $value ) = @_;
667              
668 127         3950 my $hash = $$data[0];
669 127         649 my $extra = $$data[1];
670 127         1019 my $loops = $$extra{loops};
671              
672 127 100       877 if ( $loops->in_child() ) {
673 15         252 $$extra{childKeys}{$key} = $value;
674             }
675              
676             # warn sprintf "Setting $key to $value";
677 127         1463 $$hash{$key} = $value;
678             }
679              
680             sub getChildInfo {
681 15     15   47 my ($self, $outputNr) = @_;
682 15         43 my $extra = $$self[1];
683 15         47 return $extra->{childKeys};
684             }
685              
686             package Parallel::Loops::TiedArray;
687 16     16   6368 use Tie::Array;
  16         14624  
  16         400  
688 16     16   80 use base 'Tie::Array';
  16         32  
  16         6128  
689              
690             sub TIEARRAY {
691 16     16   32 my ( $class, $loops, $storageRef ) = @_;
692 16         64 my $storage = bless { arr => [], loops => $loops, childArr => [] }, $class;
693 16         32 $$storageRef = $storage;
694 16         32 return $storage;
695             }
696              
697 159     159   136097 sub FETCHSIZE { scalar @{ $_[0]->{arr} } }
  159         1101  
698 0     0   0 sub STORESIZE { $#{ $_[0]->{arr} } = $_[1] - 1 }
  0         0  
699 90     90   546 sub STORE { $_[0]->{arr}->[ $_[1] ] = $_[2] }
700 180     180   680 sub FETCH { $_[0]->{arr}->[ $_[1] ] }
701 51     51   5788 sub CLEAR { @{ $_[0]->{arr} } = () }
  51         568  
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 127     127   753 my $self = shift;
710              
711 127 100       391 if ( $$self{loops}->in_child() ) {
712 15         138 push( @{ $self->{childArr} }, @_ );
  15         85  
713             }
714              
715 127         538 push( @{ $self->{arr} }, @_ );
  127         782  
716             }
717              
718             sub getChildInfo {
719 15     15   100 my ($self) = @_;
720 15         65 return $self->{childArr};
721             }
722              
723             1;