How can I make my program that calculates π faster? For my first Raku program, I thought it might be a fun challange to port over a program I'd written in C# about two years ago, and I was right: it was.
There's just one problem: while the C# program runs in 5 seconds, the Raku program takes nearly 5 minutes. I think the Raku program is doing more or less the same as the C# code, and so it should be equally fast (or equally slow, depending on how you look at it).
Here's my code:
use v6;

class Point {
    has Num $.x;
    has Num $.y;

    method is-inside-unit-circle(--&gt; Bool) {
        $!x²+$!y² &lt;= 1;
    }
}

sub random-points(Int $count --&gt; Seq) {
    gather for 0..^$count {
        take Point.new(x =&gt; 1.rand.Num, y =&gt; 1.rand.Num);
    }
}

sub compute-pi(Int $batch --&gt; Seq) {
    my Num $total = 0e0;
    my Num $count = 0e0;

    gather for 0..^∞ {
        my Seq $pointsInside = random-points($batch).grep: *.is-inside-unit-circle;

        $total += $batch;
        $count += $pointsInside.elems;
        my Num $ratio = $count / $total;

        take $ratio * 4;
    }
}

{ say &quot;π ≈ $_&quot; } for compute-pi(100_000).head(500);

And here's part of the output:
% time raku estimation.raku
π ≈ 3.12912
π ≈ 3.12874
π ≈ 3.1326533333333333
…
π ≈ 3.1419357429718877
π ≈ 3.1419323446893785
π ≈ 3.14193696
raku estimation.raku  272,98s user 1,78s system 100% cpu 4:34,76 total

My two questions are:

How can I make it faster?
Is my code idiomatic, and how could I make it more idiomatic?


For completeness' sake, here's the C# code:
using System;
using System.Collections.Generic;
using System.Linq;

internal class Program
{
    internal readonly struct Point
    {
        private readonly double _x;
        private readonly double _y;

        internal Point(double x, double y)
        {
            _x = x;
            _y = y;
        }

        internal bool IsInsideUnitCircle()
        {
            return Math.Pow(_x, 2) + Math.Pow(_y, 2) &lt;= 1;
        }
    }

    internal class PointGenerator
    {
        private readonly Random _random;

        public PointGenerator() =&gt; _random = new Random();

        public IEnumerable&lt;Point&gt; GeneratePoints(int count)
        {
            for (var i = 0; i &lt; count; i++)
            {
                yield return new Point(_random.NextDouble(), _random.NextDouble());
            }
        }
    }
    
    private static IEnumerable&lt;double&gt; ComputePi(int batch)
    {
        var pointGenerator = new PointGenerator();

        var total = 0.0;
        var count = 0.0;

        while (true)
        {
            var pointsInside = pointGenerator.GeneratePoints(batch)
                .Where(point =&gt; point.IsInsideUnitCircle());

            total += batch;
            count += pointsInside.Count();
            var ratio = count / total;

            yield return ratio * 4;
        }
    }

    private static void Main()
    {
        foreach (var estimate in ComputePi(100_000).Take(500))
        {
            Console.WriteLine($&quot;π ≈ {estimate}&quot;);
        }
    }
}

And part of its output:
% time dotnet run -c Release
π ≈ 3,13948
π ≈ 3,13904
π ≈ 3,138786666666667
…
π ≈ 3,1415373493975904
π ≈ 3,14154501002004
π ≈ 3,14155416
dotnet run -c Release  5,43s user 0,30s system 103% cpu 5,555 total
You random-points sub has a few things which can be removed or simplified.

1.rand returns a Num, so there is no reason to call the method .Num on its result.
Rather than gather for take you can use the xx operator.
You only want a positive number for the count, so declare that with UInt


sub random-points(UInt $count --&gt; Seq) {
    Point.new(x =&gt; 1.rand, y =&gt; 1.rand) xx $count
}

xx seems to be on the order of 10× faster than gather for take.
The reason is mostly that take works by throwing a CONTROL and gather captures that message.
On my computer that change alone takes it from nearly 9 minutes (8m49.881s) to around 2 and a half minutes (2m27.582s). (The other changes have no meaningful effect on the run time.)
The sweet spot for the gather take feature is where there isn't already a built-in for what you are doing, and the speed doesn't matter as much as the ease of writing it.
You could go to the work of creating your own Iterator class which could speed it up. That is why xx is so much faster, there is already an Iterator class that was created specifically for it.
(Note that I did actually test to see if writing a new Iterator class would help. It didn't help, in fact it was slower.)

There are similar issues in compute-pi.
To start off, there are improvements to be made to gather for 0..^∞.

Don't mix Int and Num endpoints for Range objects if you can help it. 0e0..∞
Don't use for for infinite loops, just use loop


gather loop {

    ...

    take $ratio * 4;
}

I would not store the filtered random-points Seq in a variable, because the only thing you need from it is the count.
my Int $points-inside = +random-points($batch).grep: *.is-inside-unit-circle;

The prefix + operator just coerces to a Numeric value. Obviously the only number that makes sense for a Seq is the same as .elems, the count of elements.
.elems could potentially return a Num, specifically Inf. Considering that would break the rest of the code anyway, I think it's fine to store it in an Int scalar.

Ok now that we are done with the more obvious cleanups we can try to make it faster.
Thankfully changing from gather for take in random-points to xx helped a lot. (No other change had any meaningful change on the amount of time taken.)
The first thing I did at this point was to run the profiler
raku --profile test.raku

There were no obvious slow parts of our code. In fact a good ⅔ of the run time was in the Raku runtime, not our code.
So let's add parallelism to see if we can reduce the time by using more CPU cores.
We could just make the sequence returned from random-points a race sequence to see if it helps.
my Int $pointsInside = +random-points($batch).race.grep(*.is-inside-unit-circle);

After that change it now takes 4 minutes, almost twice the time it took from before that change.
I did mess around with the batch and degree arguments to race, but no combination seemed to improve it meaningfully. At best I made it match the original timing.
So now we know that was a dead-end. random-points.grep was already about as fast as possible.

So let's add the asynchrony to compute-pi instead.
One obvious thing we could do is have a bunch of threads creating the sequences of random-points.grep.
Of course we don't know how many we need to create. So let's give that as an argument.
sub compute-pi(UInt $batch-size, UInt $batch-count --&gt; Seq) {

Now we want to have an existing sequence that we can call race on.
my \batches = ^$batch-count;

Add race to it.
(We don't care about the order of random-points.grep values we get.)
my \batches = (^$batch-count).race(batch =&gt; 1);

The reason for the batch argument of 1 is that we know each random-points.grep takes a long time, so we don't want it to batch them together. We also don't want to wait for 64 of them to be calculated before we get any results.
Now to turn that into the sequence of values that we want by using map.
my \batches = (^$batch-count).race(batch =&gt; 1).map: {
    +random-points($batch-size).grep(*.is-inside-unit-circle)
}

Rather than have an infinite loop, we will loop over those batches
gather for batches -&gt; Int $pointsInside {

So the last part of the file now looks like this:
sub compute-pi(UInt $batch-size, UInt $batch-count --&gt; Seq) {

    my \batches = (^$batch-count).race(batch =&gt; 1).map: {
        +random-points($batch-size).grep(*.is-inside-unit-circle)
    }

    my Num $total = 0e0;
    my Num $count = 0e0;

    gather for batches -&gt; Int $pointsInside {

        $total += $batch;
        $count += $pointsInside;
        my Num $ratio = $count / $total;

        take $ratio * 4;
    }
}

{ say &quot;π ≈ $_&quot; } for compute-pi(100_000, 500);

It now takes just over a minute (1m13.905s) with a cpu time of over 5 minutes (5m19.237s).
Which means we have more than 400% utilization. Which is fairly good on a 4 core CPU, with hyperthreading.

We can gain a bit more if we use native num variables instead of boxed Num variables.
You have to be careful with this as you can actually make it take more time if the value ends up needing to be boxed for an operation.
class Point {
    has num $.x;
    has num $.y;
    ...
}

...

sub compute-pi(UInt $batch-size, UInt $batch-count --&gt; Seq) {

    my \batches = (^$batch-count).race(batch =&gt; 1).map: {
        random-points($batch-size).grep(*.is-inside-unit-circle).Num
    }

    my num $total = 0e0;
    my num $count = 0e0;

    gather for batches -&gt; num $pointsInside {

        $total += $batch;
        $count += $pointsInside;
        my num $ratio = $count / $total;

        take $ratio * 4;
    }
}

That final change brings it down to about 42 seconds (0m42.771s) with 4 minutes of CPU time (4m0.929s).
Which also seems to increase the CPU utilization up over 500%.

The biggest improvement was going from gather for take to just xx. Which I'd argue was warranted just for how much it improved the clarity of the code. The other changes were of minimal benefit.
I think to get any significant improvement would require improving the compiler, runtime, or the VM.
Really what we ended up with is fairly fast considering how much of higher level language Raku is than C#, and how few people are working on it.