Navigation Menu

Skip to content

Commit

Permalink
Make IO::Path.dir between 1.5x and 2.2x as fast
Browse files Browse the repository at this point in the history
- 2.2x without explicit condition, 1.5x with explicit condition
- replaced the gather / take logic by true iterators in Rakudo::Iterator
- introduced "cloned-from-path" method for faster similar IO::Path creation
  which is marked as an implementation detail
- abstracted prefix logic in "prefix-for-dir" method
  which is marked as an implementation detail
  • Loading branch information
lizmat committed May 21, 2020
1 parent 8e4e23a commit b63976a
Show file tree
Hide file tree
Showing 2 changed files with 193 additions and 52 deletions.
82 changes: 32 additions & 50 deletions src/core.c/IO/Path.pm6
Expand Up @@ -519,62 +519,44 @@ my class IO::Path is Cool does IO {
}}
}


# Call with cloned object, update path, keep "is-absolute" setting
# and reset the rest. If the source object was an absolute path,
# then the given path should also be an absolute path, and vice-versa.
method cloned-with-path(Str:D $path) is implementation-detail {
$!path := $path;
$!os-path := $!parts := nqp::null;
self
}

# create prefix to be added to each directory entry
method prefix-for-dir() is implementation-detail {
my str $dir-sep = $!SPEC.dir-sep;
nqp::iseq_s($!path,'.') || nqp::iseq_s($!path,$dir-sep)
?? ''
!! $!path.ends-with($dir-sep)
?? $!path
!! nqp::concat($!path,$dir-sep)
}

proto method dir(|) {*} # make it possible to augment with multies from modulespace
multi method dir(IO::Path:D: Mu :$test = $*SPEC.curupdir) {
multi method dir(IO::Path:D: Mu :$test!) {
CATCH { default {
X::IO::Dir.new(:path($.absolute), :os-error(.Str)).throw
X::IO::Dir.new(:path(self.absolute), :os-error(.Str)).throw
} }

my str $dir-sep = $!SPEC.dir-sep;
my int $absolute = $.is-absolute;
Seq.new: Rakudo::Iterator.Dir(self, $test)
}

my str $abspath;
$absolute && nqp::unless( # calculate $abspath only when we'll need it
nqp::eqat(($abspath = $.absolute), $dir-sep,
nqp::sub_i(nqp::chars($abspath), 1)),
($abspath = nqp::concat($abspath, $dir-sep)));
multi method dir(IO::Path:D:) {
CATCH { default {
X::IO::Dir.new(:path(self.absolute), :os-error(.Str)).throw
} }

my str $path = nqp::iseq_s($!path, '.') || nqp::iseq_s($!path, $dir-sep)
?? ''
!! nqp::eqat($!path, $dir-sep, nqp::sub_i(nqp::chars($!path), 1))
?? $!path
!! nqp::concat($!path, $dir-sep);

my Mu $dirh := nqp::opendir(nqp::unbox_s($.absolute));
gather {
# set $*CWD inside gather for $test.ACCEPTS to use correct
# $*CWD the user gave us, instead of whatever $*CWD is
# when the gather is actually evaluated. We use a temp var
# so that .IO coercer doesn't use the nulled `$*CWD` for
# $!CWD attribute and we don't use `temp` for this, because
# it's about 2x slower than using a temp var.
my $cwd = $!CWD.IO;
{ my $*CWD = $cwd;
#?if jvm
for <. ..> -> $elem {
$test.ACCEPTS($elem) && (
$absolute
?? take nqp::create(self)!SET-SELF(
$abspath ~ $elem, $!SPEC, $!CWD, True)
!! take nqp::create(self)!SET-SELF(
$path ~ $elem, $!SPEC, $!CWD, False)
);
}
#?endif
nqp::until(
nqp::isnull_s(my str $str-elem = nqp::nextfiledir($dirh))
|| nqp::iseq_i(nqp::chars($str-elem),0),
nqp::if(
$test.ACCEPTS($str-elem),
nqp::if(
$absolute,
(take nqp::create(self)!SET-SELF(
nqp::concat($abspath,$str-elem), $!SPEC, $!CWD, True)),
(take nqp::create(self)!SET-SELF(
nqp::concat($path,$str-elem), $!SPEC, $!CWD, False)),)));
nqp::closedir($dirh);
}
}
# if default tester is system default, use implicit no . .. iterator
Seq.new: nqp::eqaddr($!SPEC.curupdir,IO::Spec::Unix.curupdir)
?? Rakudo::Iterator.Dir(self)
!! Rakudo::Iterator.Dir(self, $!SPEC.curupdir)
}

# slurp STDIN in binary mode
Expand Down
163 changes: 161 additions & 2 deletions src/core.c/Rakudo/Iterator.pm6
Expand Up @@ -1481,13 +1481,172 @@ class Rakudo::Iterator {
CStyleLoop.new(&body, &cond, &afterwards, $label)
}

my role DelegateCountOnly[\iter] does PredictiveIterator {
method count-only(--> Int:D) { iter.count-only }
# Returns an iterator for iterating file system directories, producing
# IO::Path objects for a directory entry if it smart matches the given
# tester. Takes an IO::Path object and a prefix to be added to each
# director entry produced.
class DirTest does Iterator {
has Mu $!path; # IO::Path object to do dir for
has str $!prefix; # string to prefix to elements found
has Mu $!CWD; # IO::Path object for $*CWD when testing
has Mu $!tester; # object to call .ACCEPTS on to accept entry
has Mu $!dirhandle; # low level directory handle
#?if jvm
has $!dots; # JVM doesnt produce "." and "..", so we need to fake them
#?endif

method !SET-SELF(\path, \tester) {
$!path := path;
$!prefix = path.prefix-for-dir;
$!CWD := path.CWD.IO;
$!tester := tester;
$!dirhandle := nqp::opendir(path.absolute);
#?if jvm
$!dots := nqp::list_s(".","..");
#?endif

self
}

method new(\path, \tester) { nqp::create(self)!SET-SELF(path, tester) }

method pull-one() {
my str $entry;
my $*CWD := $!CWD;

nqp::while(
#?if jvm
($entry = nqp::if(
nqp::elems($!dots),
nqp::shift($!dots),
nqp::nextfiledir($!dirhandle)
)),
#?endif
#?if !jvm
($entry = nqp::nextfiledir($!dirhandle)),
#?endif
nqp::if(
$!tester.ACCEPTS($entry),
return nqp::clone(
$!path
).cloned-with-path(nqp::concat($!prefix,$entry))
)
);

IterationEnd
}

method push-all(\target --> IterationEnd) {
#?if jvm
my $dots := $!dots;
#?endif
my $path := $!path;
my str $prefix = $!prefix;
my $tester := $!tester;
my $dirhandle := $!dirhandle;
my str $entry;

my $*CWD := $!CWD;
nqp::while(
#?if jvm
($entry = nqp::if(
nqp::elems($dots),
nqp::shift($dots),
nqp::nextfiledir($dirhandle)
)),
#?endif
#?if !jvm
($entry = nqp::nextfiledir($dirhandle)),
#?endif
nqp::if(
$tester.ACCEPTS($entry),
target.push: nqp::clone(
$path
).cloned-with-path(nqp::concat($prefix,$entry))
)
);

nqp::closedir($dirhandle);
}
}

# Returns an iterator for iterating file system directories, producing
# IO::Path objects for *each* directory entry found (except "." and "..").
# Takes an IO::Path and a prefix to be added to each directory entry
# produced.
class Dir does Iterator {
has Mu $!path; # IO::Path object to do dir for
has str $!prefix; # string to prefix to elements found
has Mu $!dirhandle; # low level directory handle

method !SET-SELF(\path) {
$!path := path;
$!prefix = path.prefix-for-dir;

#?if !jvm
my $dirhandle := nqp::opendir(path.absolute);
# skipping . and .. worked, JVM never produces them
if nqp::iseq_s(nqp::nextfiledir($dirhandle),'.')
&& nqp::iseq_s(nqp::nextfiledir($dirhandle),'..') {
$!dirhandle := $dirhandle;
#?endif
#?if jvm
$!dirhandle := nqp::opendir(path.absolute);
#?endif

self
#?if !jvm
}

# strange, no '.' or '..' at start, run with tester
else {
DirTest.new(
path,
-> str $d { nqp::isne_s($d,'.') && nqp::isne_s($d,'..') },
)
}
#?endif
}

method new(\path) { nqp::create(self)!SET-SELF(path) }

method pull-one() {
nqp::if(
(my str $entry = nqp::nextfiledir($!dirhandle)),
nqp::clone($!path).cloned-with-path(nqp::concat($!prefix,$entry)),
nqp::stmts(
nqp::closedir($!dirhandle),
IterationEnd
)
)
}

method push-all(\target --> IterationEnd) {
my $path := $!path;
my str $prefix = $!prefix;
my $dirhandle := $!dirhandle;
my str $entry;

nqp::while(
($entry = nqp::nextfiledir($dirhandle)),
target.push:
nqp::clone($path).cloned-with-path(nqp::concat($prefix,$entry))
);

nqp::closedir($dirhandle);
}
}

proto method Dir(|) {*}
multi method Dir(IO::Path:D \path) { Dir.new(path) }
multi method Dir(IO::Path:D \path, \tester) { DirTest.new(path, tester) }

# Takes two iterators and mixes in a role into the second iterator that
# delegates .count-only method to the first iterator if the first is a
# PredictiveIterator
my role DelegateCountOnly[\iter] does PredictiveIterator {
method count-only(--> Int:D) { iter.count-only }
}
method delegate-iterator-opt-methods (Iterator:D \a, Iterator:D \b) {
nqp::istype(a,PredictiveIterator)
?? b.^mixin(DelegateCountOnly[a])
Expand Down

0 comments on commit b63976a

Please sign in to comment.