App-Greple
view release on metacpan or search on metacpan
lib/App/Greple/Grep.pm view on Meta::CPAN
package App::Greple::Grep::Block {
use strict;
sub min :lvalue { $_[0]->[0] }
sub max :lvalue { $_[0]->[1] }
sub number :lvalue { $_[0]->[2] }
}
package App::Greple::Grep::Match {
use strict;
sub min :lvalue { $_[0]->[0] }
sub max :lvalue { $_[0]->[1] }
sub index :lvalue { $_[0]->[2] }
sub callback :lvalue { $_[0]->[3] }
}
package App::Greple::Grep::Result {
use strict;
sub block { $_[0]->[0] }
sub matched { $_[0]->@[ 1 .. $#{$_[0]} ] }
sub min { $_[0]->block->min }
sub max { $_[0]->block->max }
sub number { $_[0]->block->number }
}
##
## Match multiple patterns in parallel using child processes.
## Each child scans $_ (shared by copy-on-write) with a single
## pattern and returns [from, to, index] triplets in packed binary
## format. Returns a list indexed by pattern number; undef elements
## mean the pattern was not processed and should be matched in the
## calling process.
##
use constant NO_INDEX => ~0;
our $default_threshold = 1024 * 1024;
sub parallel_match {
my $self = shift;
my $patlist = shift;
my $max = $self->{parallel} // 0;
return () if $max < 2;
my $threshold = $ENV{GREPLE_PARALLEL_THRESHOLD} // $default_threshold;
return () if length() < $threshold;
my @eligible = grep { not $patlist->[$_]->is_function } keys @$patlist;
return () if @eligible < 2;
warn sprintf("parallel_match: %d patterns, max %d processes\n",
scalar @eligible, $max) if $debug{m};
my @result;
while (my @chunk = splice @eligible, 0, $max) {
my @child;
for my $i (@chunk) {
my $pat = $patlist->[$i];
pipe(my $r, my $w) or last;
my $pid = fork;
if (not defined $pid) { # fall back to sequential
close $r; close $w;
last;
}
if ($pid == 0) {
close $r;
binmode $w;
my @p = match_regions(pattern => $pat->regex,
group => $self->{group_index},
index => $self->{group_index} >= 2);
my $data = pack 'J*',
map { ($_->[0], $_->[1], $_->[2] // NO_INDEX) } @p;
syswrite $w, $data if length $data;
close $w;
POSIX::_exit(0);
}
close $w;
binmode $r;
push @child, [ $i, $pid, $r ];
}
for (@child) {
my($i, $pid, $r) = @$_;
my $data = do { local $/; <$r> };
close $r;
waitpid $pid, 0;
next if $? != 0; # fall back to sequential
my @v = unpack 'J*', $data // '';
my @p;
for (my $j = 0; $j < @v; $j += 3) {
push @p, $v[$j+2] == NO_INDEX
? [ @v[$j, $j+1] ] : [ @v[$j .. $j+2] ];
}
$result[$i] = \@p;
}
}
@result;
}
##
## Compute line borders in a child process, overlapping with pattern
## matching in the calling process. Border positions are independent
## from the match result and required only after the search.
##
sub start_borders {
my $self = shift;
return if $self->{BORDERS_CHILD}; # already started
return if $self->{block}->@*; # borders not used for --block
my $threshold = $ENV{GREPLE_PARALLEL_THRESHOLD} // $default_threshold;
return if length() < $threshold;
pipe(my $r, my $w) or return;
my $pid = fork;
if (not defined $pid) { # fall back to sequential
close $r; close $w;
return;
}
if ($pid == 0) {
close $r;
binmode $w;
my $data = pack 'J*', match_borders $self->{border};
syswrite $w, $data if length $data;
close $w;
POSIX::_exit(0);
}
close $w;
binmode $r;
$self->{BORDERS_CHILD} = [ $pid, $r ];
warn "started borders child process\n" if $debug{m};
}
sub read_borders {
my $self = shift;
my($pid, $r) = @{delete $self->{BORDERS_CHILD}};
my $data = do { local $/; <$r> };
close $r;
waitpid $pid, 0;
return if $? != 0; # fall back to sequential
unpack 'J*', $data // '';
}
sub discard_borders {
my $self = shift;
if (my $child = delete $self->{BORDERS_CHILD}) {
my($pid, $r) = @$child;
kill 'TERM', $pid;
close $r;
waitpid $pid, 0;
}
$self;
}
sub prepare {
my $self = shift;
local *_ = $self->{text};
my $pat_holder = $self->{pattern};
my @blocks;
$self->{RESULT} = [];
$self->{BLOCKS} = [];
##
## build match result list
##
my @result;
my $positive_count = 0;
my $group_index_offset = 0;
my @patlist = $pat_holder->patterns;
my @parallel = $self->parallel_match(\@patlist);
while (my($i, $pat) = each @patlist) {
my($func, @args) = do {
if ($pat->is_function) {
$pat->function;
} else {
Getopt::EX::Func->new(\&match_regions,
pattern => $pat->regex,
group => $self->{group_index},
index => $self->{group_index} >= 2,
);
}
};
my @p = $parallel[$i]
? @{$parallel[$i]}
: $func->call(@args, &FILELABEL => $self->{filename});
if (@p == 0) {
##
( run in 0.332 second using v1.01-cache-2.11-cpan-9581c071862 )