该页面是关于在 Raku 上下文中 计算机性能 的。

首先,剖析你的代码

确保你没有在错误的代码上浪费时间: 通过剖析你的代码的性能以从识别你的 “临界 3%” 开始。本文档的其余部分将向您展示如何执行此操作。

Time with now - INIT now

对于 now - INIT now 形式的表达式, 其中 INIT 是一个 Raku 程序中运行的 phase, 为计时代码片段提供了一个很好的习惯用法。

使用 m: your code goes here raku 频道 evalbot 来写出这样的行:

  1. m: say now - INIT now
  2. rakudo-moar abc1234: OUTPUT«0.0018558
  3. »

INIT 左边的 nowINIT 右边的 now *晚*运行了 0.0018558 秒, 因为后者在INIT phase 期间出现。

本地剖析

当使用 MoarVM 后端时, Rakudo 编译器的 --profile 命令行选项将剖析数据写到一个 HTML 文件中。

此文件将打开“概述”部分,该部分提供有关程序如何运行的一些总体数据,例如总运行时间,执行垃圾回收所花费的时间。您将获得的一个重要信息是被解释的总调用帧(即,块)的百分比(最慢,红色),拼写(更快,橙色)和 jitted(最快,绿色)。

下一节“常规”可能是您花费最多时间的地方。它有一个可排序和可过滤的例程(或块)名称+文件+行的表,它运行的次数,包含时间(在该例程中花费的时间+从它调用的所有例程中花费的时间),独占时间(仅在该例程中花费的时间),以及它是否被解释,拼写或jitted(与“概述”页面相同的颜色代码)。按专属时间排序是了解从哪里开始优化的好方法。文件名从 SETTING::src/core/gen/moar/ 开始的例程来自编译器,从您自己的代码中看到的东西的一个好方法是将您描述的脚本的文件名放在“名称”中“ 搜索框。

“调用图”部分给出了与“例程”部分大致相同信息的火焰图表示。

“分配”部分为您提供有关分配的不同类型的数量以及分配的例程的信息。

“GC”部分为您提供有关所发生的所有垃圾收集的详细信息。

“OSR/Deopt”部分为您提供有关堆栈替换(OSR)的信息,这是在将例程从“已解释”升级为“拼写”或“jitted”时。当拼写或jitted代码必须被“降级”为被解释时,De是相反的。

如果配置文件数据太大,浏览器可能需要很长时间才能打开该文件。在这种情况下,使用 --profile-filename 选项输出到扩展名为 .json 的文件,然后使用 Qt 查看器打开该文件。

要处理更大的配置文件,请输出到扩展名为 .sql 的文件。这将把配置文件数据写成一系列SQL语句,适合在 SQLite 中打开。

  1. # create a profile
  2. raku --profile --profile-filename=demo.sql -e 'say (^20).combinations(3).elems'
  3. # create a SQLite database
  4. sqlite3 demo.sqlite
  5. # load the profile data
  6. sqlite> .read demo.sql
  7. # the query below is equivalent to the default view of the "Routines" tab in the HTML profile
  8. sqlite> select
  9. case when r.name = "" then "<anon>" else r.name end as name,
  10. r.file,
  11. r.line,
  12. sum(entries) as entries,
  13. sum(case when rec_depth = 0 then inclusive_time else 0 end) as inclusive_time,
  14. sum(exclusive_time) as exclusive_time
  15. from
  16. calls c,
  17. routines r
  18. where
  19. c.id = r.id
  20. group by
  21. c.id
  22. order by
  23. inclusive_time desc
  24. limit 30;

要了解如何解释配置文件信息,请使用 evalbot(如上所述)并在 IRC 频道上提问。

Profile 编译

If you want to profile the time and memory it takes to compile your code, use Rakudo’s --profile-compile or --profile-stage`options. 如果要分析编译代码所需的时间和内存,请使用 Rakudo 的 `--profile-compile--profile-stage 选项。

创建或查看基准

使用 raku-bench

如果您为多个编译器(通常是 Perl 5,Raku 或 NQP 的版本)运行 raku-bench,则每个编译器的结果将在视觉上覆盖在相同的图形上,以便快速轻松地进行比较。

Share problems

Once you’ve used the above techniques to identify the code to improve, you can then begin to address (and share) the problem with others:

  • 对于每个问题,将其提取到单行或 gist,并提供性能数字或使片段足够小,以便可以使用 prof-m: your code or gist URL goes here 进行分析。

  • 考虑你需要/想要的最低速度增加(或减少或减少什么),并考虑与实现该目标相关的成本。在人们的时间和精力方面,改进的价值是什么?

  • 让其他人知道您的 Raku 用例是在生产环境中还是仅仅是为了好玩。

解决问题

这需要重复:确保你没有浪费时间在错误的代码上。首先确定代码的“关键3%”。

逐行

尝试逐行改进代码的快速,有趣和高效的方法是使用 raku evalbot camelia 与其他人协作。

逐个例程

使用 multidispatch,您可以在现有的例程“旁边”添加新的例程变体:

  1. # existing code generically matches a two arg foo call:
  2. multi sub foo(Any $a, Any $b) { ... }
  3. # new variant takes over for a foo("quux", 42) call:
  4. multi sub foo("quux", Int $b) { ... }

拥有多个 foo 定义的调用开销通常是微不足道的(虽然请参见下面的讨论),因此如果您的新定义比以前存在的定义集更有效地处理其特定情况,那么您可能只是使您的代码更有效率对于那种情况。

加速类型检测和调用解析

大多数 where 子句 - 以及大多数子集 - 强制动态(运行时)类型检查和调用解析它可能匹配的任何调用。这比编译时更慢,或者至少晚一些。

方法调用通常尽可能晚地解析(在运行时动态),而 sub 调用通常在编译时静态解析。

选择更好的算法

无论语言或编译器如何,提高性能的最可靠技术之一是选择更合适的算法。

一个典型的例子是 Boyer-Moore。要匹配大字符串中的小字符串,一个明显的方法是比较两个字符串的第一个字符然后,如果它们匹配,则比较第二个字符,或者,如果它们不匹配,则比较第一个字符大字符串中第二个字符的小字符串的字符,依此类推。相反,Boyer-Moore 算法首先将小字符串的 last 字符与大字符串中相应定位的字符进行比较。对于大多数字符串,Boyer-Moore 算法在算法上接近 N 倍,其中 N 是小字符串的长度。

接下来的几节讨论了算法改进的两大类,这些类别在 Raku 中特别容易实现。有关这个一般主题的更多信息,请阅读有关算法效率的维基百科页面,尤其是接近结尾的“另请参阅”部分。

将顺序/阻塞代码更改为并行/非阻塞

这是另一个非常重要的算法改进类。

查看幻灯片 Raku 中的并行、并发和异步对应的视频

使用已有的高性能代码

您可以在 Raku 中使用大量高性能 C 库,而 NativeCall 可以轻松地为它们创建包装器。还有对 C++ 库的实验性支持。

如果要在 Raku 中使用 Perl 5 模块,请混合使用 Raku 类型和元对象协议

更一般地说,Raku 旨在与其他语言平滑地互操作,并且有许多模块旨在促进使用来自其他语言的库

让 Rakudo 编译器生成更快的代码

到目前为止,编译器的重点是正确性,而不是它生成代码的速度有多快,或者生成的代码运行速度有多快。但是预计会发生变化,最终…​…​你可以在 freenode IRC 频道#raku 和 #moarvm 上与编译器开发人员讨论预期的内容。更好的是,你可以自己贡献代码:

  • Rakudo 主要用 Raku 编写。因此,如果您可以编写 Raku,那么您可以破解编译器,包括优化任何影响代码速度的大量现有高级代码(以及其他所有代码)。

  • 大多数编译器的其余部分都是用一种名为 NQP 的小语言编写的,它基本上是 Raku 的一个子集。如果你可以编写 Raku,你也可以很容易地学会使用和改进中级 NQP 代码,至少从一种纯粹的语言观点。要深入了解 NQP 和 Rakudo 的内涵,请从 NQP 和内部课程开始。

  • 如果低级别的 C 黑客是你的乐趣,请查看 MoarVM 并访问 freenode IRC 频道 #moarvm(日志)。

仍然需要更多想法?

此页面中尚未涵盖的一些已知当前 Rakudo 性能缺陷包括使用 gather/takejunctions,正则表达式和字符串处理。

如果您认为某个主题需要在此页面上进行更多报道,请提交 PR 或告诉某人您的想法。谢谢。 :)

没有得到你需要/想要的结果?

如果您已尝试此页面上的所有内容无效,请考虑使用 #raku 上的编译器开发人员进行讨论,以便我们可以从您的用例中了解到目前为止您已经发现的内容。

一旦开发人员知道您的困境,请留出足够的时间做出明智的回应(几天或几周,具体取决于问题的确切性质和潜在的解决方案)。

如果还没有成功,请考虑在继续之前提交有关您在我们的用户体验仓库中的体验的问题。

谢谢。 :)