`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 快,但比我们开始时要好一个数量级。