`now`在1000万次迭代循环中变慢

我有一个Python的SnowFlake 脚本,我将其转换为Raku模块,并调用了 10,000,000 次,但速度非常慢(文件test.raku):

use IdWorker;

my $worker = IdWorker.new(worker_id => 10, sequence => 0);
my @ids = gather for (1...10000000) { take $worker.get_id() };

my $duration = now - INIT now;
say sprintf("%-8s %-8s %-20s", @ids.elems, Set(@ids).elems, $duration);

正如@codesections 的回答所说,这now需要很多时间。

Python 大约需要 12 秒,而 Raku 需要几分钟。我怎样才能解决这个问题?

这个空的for循环大约需要 0.12 秒:

for (1...10000000) {
    ;
}

和通话get_id()$worker花费几分钟

for (1...10000000) {
    $worker.get_id();
}

回答

我相信这里的问题不是来自构造数组而是来自now它本身——这似乎出奇地慢。

例如,这段代码:

no worries; # skip printing warning for useless `now`
for ^10_000_000 { now }
say now - INIT now;

也需要几分钟才能运行。这让我觉得是一个错误,我会打开一个问题 [编辑:我在这个问题上找到了rakudo/rakudo#3620。好消息是已经有修复计划。] 由于您的代码now在每次迭代中调用多次,因此此问题对您的循环的影响更大。

除此之外,还有一些其他方面可以加快此代码的运行速度:

首先,使用隐式返回(即更改return new_id;为 just new_id,并对您使用的其他地方进行类似更改return)通常会稍微快一点/让 JIT 优化得更好一些。

二、线路

my @ids = gather for (1...10000000) { take $worker.get_id() };

使用gather/ take(它增加了对惰性列表的支持并且只是一个更复杂的构造)有点浪费。您可以将其简化为

my @ids = (1...10000000).map: { $worker.get_id() };

(不过,这仍然构建了一个中间体Seq。)

第三-这是一个从性能的影响更重要,虽然从字面上小,因为它可能是从一个代码变化的角度-是改变(1...10000000)(1..10000000)。不同之处在于它...是序列运算符,而..是范围运算符。与 Ranges 相比,Sequences 有一些超能力(如果你很好奇,请参阅文档),但在这样的循环中迭代要慢得多。

不过,这些都是小问题。我相信性能now是最大的问题。

长期的解决方案now是缓慢的是,它是固定的(我们正在努力了!)作为临时解决办法,不过,如果你不介意浸入稍低比一般还是建议用户代码,您可以使用nqp::time_n获取当前时间的浮点秒数。使用它会使你的get_timestamp方法看起来像:

method get_timestamp() {
    use nqp;
    (nqp::time_n() * 1000).Int;
}

有了这个解决方法和我上面建议的其他重构,你的代码现在在我的机器上执行大约 55 秒——仍然没有我希望 Raku 快,但比我们开始时要好一个数量级。


以上是`now`在1000万次迭代循环中变慢的全部内容。
THE END
分享
二维码
< <上一篇
下一篇>>