How to speed up parallel parsing using Raku grammars?

Viewed 167

Parsing a few hundreds files using my grammar using a plain

for @files -> $file {
    my $input = $file.IO.slurp;
    my $output = parse-and-convert($input);
    $out-dir.IO.add($file ~ '.out').spurt: $output;
}

loop is relatively slow and takes ~20 seconds on my machine, so I've decided to speed this up by doing this instead:

my @promises;
for @files -> $file {
    my $input = $file.IO.slurp;
    @promises.append: start parse-and-convert($input);
}

for await @promises -> $output {
    $out-dir.IO.add($file ~ '.out').spurt: $output;
}

This works (at least in my real code, i.e. modulo any typos in this illustrative example), but the speedup is much less than I hoped for: it now takes ~11s, i.e. I've gained a factor of only two. This is appreciable, of course, but it looks like there is a lot of contention because the program uses less than 6 CPUs (on a system with 16 of them) and quite a bit of overhead (because I don't get a factor of 6 speedup neither).

I've confirmed (by inserting some say now - INIT.now) that almost all the running time is really spent inside await, as expected, but I have no idea how could I debug/profile it further. I'm doing this under Linux, so I could use perf, but I am not sure how would it help me at Raku level.

Would there be some simple way to improve the degree of parallelism here?

Edit: Just to make it clear, I can live with 20s (well, 30s by now, as I've added more things) running time, I'm really mostly curious about whether the degree of parallelism could be somehow improved here, without rewriting the grammar (unless there is something very specific, like e.g. use of dynamic variables, that should be avoided when using multiple threads).

2 Answers

A question, and a suggestion:

Does your Grammar parse entire documents, or only portions of those documents (sections, paragraphs, lines, etc.)?

If your Grammar only parses at the paragraph or lines level, then you might be spending a lot of time slurping your files in. The advantage of Raku's lines routine is that it reads lazily. To replicate and replace slurp for your second line of code you could try something like:

my $input = $file.IO.lines.join("\n");

Otherwise, if your Grammar parses on the paragraph level, you use the power of arrays in Raku (note below the assignment to @input instead of $input). You can also >> (hyper) process array elements to provide speedup, because as the docs say, "...all hyper operators are candidates for parallelism...":

my @input = $file.IO.split("\n\n");

If you have complex Paragraph (pre)-parsing to do, check out the Text::Paragraph submodule of @Codesections' _ "lowbar" module:

https://github.com/codesections/_/blob/main/lib/Text/Paragraphs/README.md

In any case, it seems your best opportunity for speedup is reducing 'impedence-mismatch', i.e. making sure that the chunk-size you're feeding to your Grammar matches the size that the Grammar expects (rather than creating a file-read bottleneck prior to the Grammar being executed).

HTH.

If you don't care about the order in which things occur, you can use race on any Iterable (in this case, your @files). This will by default create work for CPU-cores - 1 threads, and create work loads of 64 items to be processed per thread at a time.

Since grammar parsing is a notoriously expensive process, it's probably wise to let each thread handle 1 file at a time. You can specify that with the batch argument.

Relatedly, Intel processors typically claim to have 2x more CPUs than is available for typical workloads. So you might want to play with the degree argument (which indicates the maximum number of threads to be used) as well, because parsing a grammar creates similar types of workload.

So your code:

for @files.race(batch => 1, degree => 8) -> $file {
    my $input = $file.IO.slurp;
    my $output = parse-and-convert($input);
    $out-dir.IO.add($file ~ '.out').spurt: $output;
}

Note the only thing you needed to add to your original code was: .race(batch => 1, degree => 8)

Related