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.10';
4              
5             # For Tie::ExtraHash - This was the earliest perl version in which I found this
6             # class
7 16     16   1080064 use 5.008;
  16         64  
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   112 use strict;
  16         32  
  16         544  
368 16     16   112 use warnings;
  16         48  
  16         688  
369              
370 16     16   96 use Carp;
  16         32  
  16         1040  
371 16     16   9024 use IO::Handle;
  16         98592  
  16         768  
372 16     16   7632 use IO::Select;
  16         26128  
  16         800  
373 16     16   11808 use File::Temp qw(tempfile);
  16         234784  
  16         1120  
374 16     16   10288 use Storable;
  16         52016  
  16         992  
375 16     16   9648 use Parallel::ForkManager;
  16         924224  
  16         25904  
376              
377             sub new {
378 16     16 0 2304 my ($class, $maxProcs, %options) = @_;
379 16         80 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 128 my $reader = IO::Handle->new();
389 16         512 my $writer = IO::Handle->new();
390 16 50       1104 pipe( $reader, $writer )
391             or die "Couldn't open a pipe";
392 16         208 $writer->autoflush(1);
393 16         1088 my $select = IO::Select->new();
394 16         272 $select->add($reader);
395 16         1296 print $writer "test\n";
396              
397             # There should be data right away, so lets not risk blocking if it is
398             # unreliable
399 16         128 my @handles = $select->can_read(0);
400 16         880 my $working = (scalar(@handles) == 1);
401              
402 16         176 close $reader;
403 16         240 close $writer;
404              
405 16         256 return $working;
406             }
407              
408             sub share {
409 64     64 1 29472 my ($self, @tieRefs) = @_;
410 64         160 foreach my $ref (@tieRefs) {
411 64 100       240 if (ref $ref eq 'HASH') {
    100          
412 16         64 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         176 %$ref = %initialContents;
417 16         160 push @{$$self{tieObjects}}, $storage;
  16         80  
418 16         32 push @{$$self{tieHashes}}, [$$self{shareNr}, $ref];
  16         80  
419             } elsif (ref $ref eq 'ARRAY') {
420 16         48 my @initialContents = @$ref;
421             # $storage will point to the Parallel::Loops::TiedArray object
422 16         32 my $storage;
423 16         112 tie @$ref, 'Parallel::Loops::TiedArray', $self, \$storage;
424 16         80 @$ref = @initialContents;
425 16         32 push @{$$self{tieObjects}}, $storage;
  16         48  
426 16         32 push @{$$self{tieArrays}}, [$$self{shareNr}, $ref];
  16         64  
427             } else {
428 32         4432 croak "Only unblessed hash and array refs are supported by share";
429             }
430 32         112 $$self{shareNr}++;
431             }
432             }
433              
434             sub in_child {
435 250     250 0 601 my ($self) = @_;
436 250   66     2718 return $$self{forkManager} && $$self{forkManager}->is_child;
437             }
438              
439             sub readChangesFromChild {
440 110     110 0 1426 my ($self, $childRdr, $childFinishSub) = @_;
441              
442 110         480 my $childOutput;
443              
444 110 100       917 if ($$self{workingSelect}) {
445 97         894 local $/;
446 97         4129 $childOutput = <$childRdr>;
447             } else {
448 13         989 my $filename = <$childRdr>;
449 13 50       839 open my $in, $filename
450             or die "Couldn't open $filename";
451 13         78 binmode $in;
452             {
453 13         33 local $/;
  13         81  
454 13         402 $childOutput = <$in>;
455             }
456 13         178 close $in;
457 13         973 unlink $filename;
458              
459             }
460 110 50       594 die "Error getting result contents from child"
461             if $childOutput eq '';
462              
463 110         309 my @output;
464 110         416 eval {
465 110         251 @output = @{ Storable::thaw($childOutput) };
  110         3454  
466             };
467 110 50       8946 if ($@) {
468 0         0 die "Error interpreting result from child: $@";
469             }
470 110         227 my $error = shift @output;
471 110         216 my $retval = shift @output;
472              
473 110         321 foreach my $set (@{$$self{tieHashes}}) {
  110         541  
474 110         545 my ($outputNr, $h) = @$set;
475 110         211 foreach my $k (keys %{$output[$outputNr]}) {
  110         434  
476 110         2484 $$h{$k} = $output[$outputNr]{$k};
477             }
478             }
479 110         201 foreach my $set (@{$$self{tieArrays}}) {
  110         279  
480 110         714 my ($outputNr, $a) = @$set;
481 110         304 foreach my $v (@{$output[$outputNr]}) {
  110         278  
482 110         1839 push @$a, $v;
483             }
484             }
485 110 50       387 if ($error) {
486 0         0 die "Error from child: $error";
487             }
488 110 50       242 $childFinishSub->()
489             if $childFinishSub;
490 110         482 return @$retval;
491             }
492              
493             sub printChangesToParent {
494 15     15 0 74 my ($self, $error, $retval, $parentWtr) = @_;
495 15         45 my $outputNr = 0;
496 15         78 my @childInfo = ($error, $retval);
497 15         38 foreach (@{$$self{tieObjects}}) {
  15         75  
498 30         451 push @childInfo, $_->getChildInfo();
499             }
500             {
501 15         49 local $SIG{PIPE} = sub {
502 0     0   0 die "Couldn't print to pipe";
503 15         1991 };
504 15 100       211 if ($$self{workingSelect}) {
505 10         236 print $parentWtr Storable::freeze(\@childInfo);
506             } else {
507 5         164 my ($fh, $filename) = tempfile();
508 5         5037 binmode $fh;
509 5         114 print $fh Storable::freeze(\@childInfo);
510 5         925 close $fh;
511 5         161 print $parentWtr $filename;
512             }
513             }
514             }
515              
516             sub while {
517 33     33 1 633 my ($self, $continueSub, $bodySub, $finishSub) = @_;
518 33         226 my @retvals;
519              
520             # This is used if $$self{workingSelect}
521 33         99 my $childCounter = 0;
522 33         72 my $nrRunningChildren = 0;
523 33         464 my $select = IO::Select->new();
524              
525             # Else this is used
526 33         516 my %childHandles;
527              
528 33         756 my $fm = Parallel::ForkManager->new($$self{maxProcs});
529 33         86298 $$self{forkManager} = $fm;
530 33         82 my %childFinishSubs;
531             $fm->run_on_finish( sub {
532 114     114   66102930 my ($pid) = @_;
533 114 100       644 if ($$self{workingSelect}) {
534 101         430 $nrRunningChildren--;
535             } else {
536 13         83 my $childRdr = $childHandles{$pid};
537             push @retvals, $self->readChangesFromChild(
538 13         224 $childRdr, $childFinishSubs{$childRdr}
539             );
540 13         296 close $childRdr;
541             }
542 33         368 });
543 33         386 while (my $childData = $continueSub->()) {
544             # Setup pipes so the child can send info back to the parent about
545             # output data.
546 135         5116 my $parentWtr = IO::Handle->new();
547 135         10705 my $childRdr = IO::Handle->new();
548 135 50       13043 pipe( $childRdr, $parentWtr )
549             or die "Couldn't open a pipe";
550 135         1780 binmode $parentWtr;
551 135         327 binmode $childRdr;
552 135         2448 $parentWtr->autoflush(1);
553              
554 135 50       18843 if ($finishSub) {
555             $childFinishSubs{$childRdr} = sub {
556 0     0   0 $finishSub->($childData);
557 0         0 };
558             }
559              
560 135 100       485 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       774 $nrRunningChildren >= $$self{maxProcs} ? undef : 0
565             );
566 115         22103 for my $fh ( @ready ) {
567             push @retvals, $self->readChangesFromChild(
568 63         2255 $fh, $childFinishSubs{$fh}
569             );
570 63         324 $select->remove($fh);
571 63         3703 close $fh;
572             }
573             }
574              
575 135         864 my $pid = $fm->start( ++$childCounter );
576 135 100       175865 if ($pid) {
577             # We're running in the parent...
578 120         3259 close $parentWtr;
579 120 100       882 if ($$self{workingSelect}) {
580 105         413 $nrRunningChildren++;
581 105         2935 $select->add($childRdr);
582             } else {
583 15         408 $childHandles{$pid} = $childRdr;
584             }
585 120         22332 next;
586             }
587              
588             # We're running in the child
589 15         755 close $childRdr;
590              
591 15         158 my @retval;
592 15         46 eval {
593 15         614 @retval = $bodySub->();
594             };
595 15         186 my $error = $@;
596              
597 15 50       120 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         76 @retval = ();
601             }
602              
603 15         598 $self->printChangesToParent($error, \@retval, $parentWtr);
604 15         2073 close $parentWtr;
605              
606 15         216 $fm->finish($childCounter); # pass an exit code to finish
607             }
608              
609 18 100       646 if ($$self{workingSelect}) {
610 17         332 while (my @ready = $select->can_read()) {
611 34         33817 for my $fh (@ready) {
612             push @retvals, $self->readChangesFromChild(
613 34         720 $fh, $childFinishSubs{$fh}
614             );
615 34         1798 $select->remove($fh);
616 34         2275 close $fh;
617             }
618             }
619             }
620              
621 18         773 $fm->wait_all_children;
622 18         369 delete $$self{forkManager};
623 18         1345 return @retvals;
624             }
625              
626             # foreach is implemented via while above
627             sub foreach {
628 16     16 1 160 my ($self, $varRef, $arrayRef, $sub);
629 16 50       64 if (ref $_[1] eq 'ARRAY') {
630 16         48 ($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         48 my $i = -1;
643 81     81   658 $self->while( sub { ++$i <= $#{$arrayRef} }, sub {
  81         1195  
644             # Setup either $varRef or $_, if no such given before calling $sub->()
645 5 50   5   595 if ($varRef) {
646 0         0 $$varRef = $arrayRef->[$i];
647             } else {
648 5         22 $_ = $arrayRef->[$i];
649             }
650 5         160 $sub->();
651 16         160 });
652             }
653              
654             package Parallel::Loops::TiedHash;
655 16     16   400 use Tie::Hash;
  16         64  
  16         704  
656 16     16   112 use base 'Tie::ExtraHash';
  16         48  
  16         10736  
657              
658             sub TIEHASH {
659 16     16   64 my ( $class, $loops, $storageRef ) = @_;
660 16         96 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   6893 my ( $data, $key, $value ) = @_;
667              
668 125         1037 my $hash = $$data[0];
669 125         869 my $extra = $$data[1];
670 125         920 my $loops = $$extra{loops};
671              
672 125 100       642 if ( $loops->in_child() ) {
673 15         313 $$extra{childKeys}{$key} = $value;
674             }
675              
676             # warn sprintf "Setting $key to $value";
677 125         1728 $$hash{$key} = $value;
678             }
679              
680             sub getChildInfo {
681 15     15   73 my ($self, $outputNr) = @_;
682 15         64 my $extra = $$self[1];
683 15         58 return $extra->{childKeys};
684             }
685              
686             package Parallel::Loops::TiedArray;
687 16     16   8896 use Tie::Array;
  16         19824  
  16         496  
688 16     16   128 use base 'Tie::Array';
  16         16  
  16         7952  
689              
690             sub TIEARRAY {
691 16     16   48 my ( $class, $loops, $storageRef ) = @_;
692 16         80 my $storage = bless { arr => [], loops => $loops, childArr => [] }, $class;
693 16         48 $$storageRef = $storage;
694 16         32 return $storage;
695             }
696              
697 159     159   143250 sub FETCHSIZE { scalar @{ $_[0]->{arr} } }
  159         620  
698 0     0   0 sub STORESIZE { $#{ $_[0]->{arr} } = $_[1] - 1 }
  0         0  
699 90     90   454 sub STORE { $_[0]->{arr}->[ $_[1] ] = $_[2] }
700 180     180   700 sub FETCH { $_[0]->{arr}->[ $_[1] ] }
701 51     51   5898 sub CLEAR { @{ $_[0]->{arr} } = () }
  51         669  
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   651 my $self = shift;
710              
711 125 100       373 if ( $$self{loops}->in_child() ) {
712 15         114 push( @{ $self->{childArr} }, @_ );
  15         160  
713             }
714              
715 125         1646 push( @{ $self->{arr} }, @_ );
  125         839  
716             }
717              
718             sub getChildInfo {
719 15     15   96 my ($self) = @_;
720 15         73 return $self->{childArr};
721             }
722              
723             1;