这很慢,因为您正在尝试物化大量物品。作为替代方案,您可以构建一个序列:(5, 10 … 1000000000).sum,但这仍然会具体化并保持大量元素,所以它仍然很慢。您可以创建一个 C 风格的循环并为每个增量添加总和,但这并不有趣(对于足够大的数字仍然很慢)。
你可以用数学来解决这个问题:能被 N 整除的数字是 N 的倍数,如果我们从该序列中分解出 N,我们会发现你要查找的所有数字的总和是 N * (1 + 2 + ... + floor 1000000000/N) .由于这是一个连续的范围,我们可以使用它的Range.sum(它知道如何快速完成)来计算该部分。因此我们得到:
sub sum-of-stuff (\n, \d) { d * sum 1..n div d; }
say sum-of-stuff 1000000000, 5 # OUTPUT: 100000000500000000
所以这是计算问题的最快和最明智的方法,但这并不是最有趣的!
您提到了并发性,所以让我们试一试这种方法。我们的问题是物化东西,所以我们需要找到一种方法,通过我们可用的核心数量将我们的原始数字范围分块,然后开始物化和寻找每个单独核心的乘数的工作。然后我们将对每个核心中每个块中的内容进行汇总,然后返回主线程并汇总块的总和以获得最终答案。在代码中,它看起来像这样:
constant N = 10000;
constant DIV = 5;
constant CORES = 32;
constant batch = N div CORES;
(0, batch … N, N).rotor(2 => -1).flat.map({$^a^..$^b})
.race(:batch :degree(CORES)).map(*.grep(* %% DIV).sum).sum.say;
batch 是每个核心需要处理的块的大小,并且
这是对每一位分解所有工作的单线的解释:
我们使用序列运算符创建0, batch, 2*batch, 3*batch 等序列,直到N。由于我们希望 N 成为其中的一部分,因此我们第二次包含它:
(0, batch … N, N)
我们现在想要的是使用该序列来创建一堆Range 对象,我们想重用序列中的元素,所以我们使用.rotor,批处理为 2,backstep 为 1,然后展平子列表并使用 .map 创建 Range 对象(.map: *^..* 看起来好多了,但可惜的是,Whatever Stars 不想在这种安排中使用咖喱):
.rotor(2 => -1).flat.map({$^a^..$^b})
现在有趣的是,我们使用.race 方法创建一个HyperSeq,以便使用我们所有的内核对其进行评估。它的:batch 命名参数允许您指定每批要处理多少个元素,它的:degree 指定要使用多少个线程。我们已经对我们的数据进行了批处理,所以对于:batch,我们使用1。对于:degree,我们使用我们的核心数量。为什么我们不告诉它批量处理我们的东西?因为物化是我们的敌人,我们想在不同的线程中物化。告诉它批处理将在一个线程中具体化所有内容:
.race(:batch :degree(CORES))
所以现在我们已经掌握了 HyperSeq,我们可以对其进行映射。每个项目都是我们的Range 对象,大小适合一个批次,回想一下。所以我们会在上面调用.grep,寻找能被我们想要的除数整除的数字,然后我们会调用.sum:
.map(*.grep(* %% DIV).sum)
顶部的最后一颗樱桃,我们想把每个块的总和相加并打印结果,所以我们调用
.sum.say;
多田!
你也可以用这种方式重写有趣的部分,并使用 Promises 代替 .race:
say sum await (0, batch … N, N).rotor(2 => -1).flat.map:
{ start sum grep * %% DIV, $^a^..$^b }
类似但稍短一些。曾经为我们制作 Ranges 的地图现在也触发了一个 Promise(使用 start 关键字),它对块进行 greps 和求和。在行首,我们添加了await,等待所有promise的结果,然后将结果汇总。
它仍然很慢,并且无法解决您最初的问题,因此为什么您应该使用一个好的算法;)
干杯。