此页面是关于 Raku 上下文中的 计算机性能。
首先,分析您的代码§
确保您没有浪费时间在错误的代码上:首先通过分析代码的性能来识别您的 "关键的 3%"。本文档的其余部分将向您展示如何做到这一点。
使用 now - INIT now
计时§
形如 now - INIT now
的表达式,其中 INIT
是 Raku 程序运行中的一个阶段,提供了一种计时代码片段的绝佳习惯用法。
使用 m: your code goes here
#raku channel evalbot 来编写类似以下的代码行:
m: say now - INIT now rakudo-moar abc1234: OUTPUT«0.0018558»
INIT
左侧的 now
比 INIT
右侧的 now
运行时间晚 0.0018558 秒,因为后者发生在 INIT 阶段。
本地分析§
当使用 MoarVM 后端时,Rakudo 编译器的 --profile
命令行选项会将分析数据写入 HTML 文件。
该文件将打开到 "Overview" 部分,其中提供了一些关于程序运行的总体数据,例如:总运行时间、垃圾回收所花费的时间。这里你会得到一个重要的信息,即总调用帧(即代码块)的百分比,这些调用帧被解释(最慢,红色)、speshed(更快,橙色)和 jitted(最快,绿色)。
"Routines" 部分可能是你花费时间最多的部分。它包含一个可排序和可过滤的表格,其中包含例程(或代码块)名称+文件+行号、运行次数、包含时间(在该例程中花费的时间 + 从该例程调用的所有例程中花费的时间)、排他时间(仅在该例程中花费的时间),以及它是否被解释、speshed 或 jitted(与 "Overview" 页面中的颜色代码相同)。按排他时间排序是了解从哪里开始优化的一个好方法。文件名以 SETTING::src/core/
或 gen/moar/
开头的例程来自编译器,要查看你自己的代码中的内容,可以在 "Name" 搜索框中输入你分析的脚本的文件名。
"Call Graph" 部分提供了与 "Routines" 部分中大部分信息相同的火焰图表示。
"Allocations" 部分提供有关分配的不同类型数量的信息,以及哪些例程进行了分配。
"GC" 部分提供有关所有发生的垃圾回收的详细信息。
"OSR / Deopt" 部分提供有关堆栈替换(OSR)的信息,即例程从解释升级到 speshed 或 jitted 的过程。Deopt 是相反的过程,即 speshed 或 jitted 代码必须降级为解释执行。
如果分析数据太大,浏览器打开文件可能需要很长时间。在这种情况下,使用 --profile=filename
选项将输出写入扩展名为 .json
的文件,然后使用 Qt 查看器 打开该文件。
为了处理更大的分析数据,将输出写入扩展名为 .sql
的文件。这将把分析数据写入一系列 SQL 语句,适合在 SQLite 中打开。
# create a profile
raku --profile=demo.sql -e 'say (^20).combinations(3).elems'
# create a SQLite database
sqlite3 demo.sqlite
# load the profile data
sqlite> .read demo.sql
# the query below is equivalent to the default view of the "Routines" tab in the HTML profile
sqlite> select
case when r.name = "" then "<anon>" else r.name end as name,
r.file,
r.line,
sum(entries) as entries,
sum(case when rec_depth = 0 then inclusive_time else 0 end) as inclusive_time,
sum(exclusive_time) as exclusive_time
from
calls c,
routines r
where
c.routine_id = r.id
group by
r.id
order by
inclusive_time desc
limit 30;
正在开发中的下一代分析器是 moarperf,它可以接受 .sql 或 SQLite 文件,并且与原始分析器相比,它具有许多新功能。但是,它比相对独立的原始分析器具有更多依赖项,因此你需要安装一些模块才能使用它。
要了解如何解释分析信息,请使用 prof-m: your code goes here
evalbot(如上所述)并在 IRC 频道上提问。
分析编译§
如果你想分析编译代码所需的时间和内存,请使用 Rakudo 的 --profile-compile
或 --profile-stage
选项。
创建或查看基准测试§
使用 perl6-bench。
如果你为多个编译器(通常是 Perl、Raku 或 NQP 的版本)运行 perl6-bench,每个编译器的结果都会在同一个图表上以视觉方式叠加,以便快速轻松地进行比较。
分享问题§
一旦你使用上述技术确定了需要改进的代码,就可以开始解决(并与他人分享)问题。
对于每个问题,将其提炼成一行代码或 gist,并提供性能数据或使代码片段足够小,以便可以使用
prof-m: your code or gist URL goes here
进行分析。考虑你需要的/想要的最小速度提升(或内存减少或其他),并考虑实现该目标的成本。在人们的时间和精力方面,这种改进值多少钱?
让其他人知道你的 Raku 使用场景是用于生产环境还是只是为了娱乐。
解决问题§
需要重复强调:**确保你没有浪费时间在错误的代码上**。首先要识别出代码中的"关键的 3%"。
逐行检查§
一个快速、有趣、高效的方式来逐行改进代码,是使用#raku evalbot camelia 与他人协作。
逐个例程§
使用多重分派,你可以将新的例程变体“并排”添加到现有的例程中。
# existing code generically matches a two arg foo call:multi foo(Any , Any )# new variant takes over for a foo("quux", 42) call:multi foo("quux", Int )
拥有多个 foo
定义的调用开销通常微不足道(尽管请参阅下面关于 where
的讨论),因此,如果你的新定义比之前存在的定义集更有效地处理了特定情况,那么你可能只是让代码在该情况下变得更加高效。
加速类型检查和调用解析§
大多数where
子句 - 以及大多数子集 - 会强制对任何可能匹配的调用进行动态(运行时)类型检查和调用解析。这比编译时更慢,或者至少更晚。
方法调用通常尽可能晚地解析(在运行时动态解析),而子调用通常在编译时静态解析。
选择更好的算法§
无论语言或编译器如何,提高性能最可靠的技术之一是选择更合适的算法。
一个经典的例子是Boyer-Moore。为了在一个大字符串中匹配一个小字符串,一个显而易见的方法是比较两个字符串的第一个字符,然后如果它们匹配,就比较第二个字符,或者如果它们不匹配,就比较小字符串的第一个字符和大字符串的第二个字符,依此类推。相比之下,Boyer-Moore 算法从比较小字符串的*最后一个*字符和大字符串中对应位置的字符开始。对于大多数字符串,Boyer-Moore 算法在算法上接近 N 倍的速度,其中 N 是小字符串的长度。
接下来的几个部分讨论了两个广泛的算法改进类别,它们在 Raku 中特别容易实现。有关此一般主题的更多信息,请阅读维基百科页面上的算法效率,尤其是结尾处的“另请参阅”部分。
将顺序/阻塞代码更改为并行/非阻塞代码§
这是另一类非常重要的算法改进。
请参阅Raku 中的并行性、并发性和异步性的幻灯片和/或相应的视频。
使用现有的高性能代码§
有很多高性能的 C 库,你可以在 Raku 中使用,而NativeCall 使得为它们创建包装器变得很容易。还有一些对 C++ 库的实验性支持。
如果你想在 Raku 中使用 Perl 模块,可以混合使用 Raku 类型和元对象协议。
更一般地说,Raku 被设计为与其他语言无缝互操作,并且有许多旨在促进使用其他语言库的模块。
让 Rakudo 编译器生成更快的代码§
到目前为止,编译器的重点一直是正确性,而不是它生成代码的速度,或者它生成的代码运行的速度或效率。但预计这种情况最终会改变……你可以在 libera.chat IRC 频道 #raku 和 #moarvm 与编译器开发者讨论预期情况。更棒的是,你可以自己贡献。
Rakudo 主要用 Raku 编写。所以如果你会写 Raku,那么你就可以修改编译器,包括优化任何影响代码速度(以及其他所有人代码速度)的大量现有高级代码。
编译器的其余部分大部分是用一种名为 NQP 的小型语言编写的,它基本上是 Raku 的一个子集。如果你会写 Raku,你就可以相当容易地学习使用和改进中级 NQP 代码,至少从纯语言的角度来看是这样。要深入了解 NQP 和 Rakudo 的内部结构,请从 NQP 和内部结构课程 开始。
如果低级 C 编程是你喜欢的,请查看 MoarVM 并访问 libera.chat IRC 频道 #moarvm (日志).
仍然需要更多想法吗?§
一些已知的当前 Rakudo 性能弱点,在本页中尚未涵盖,包括使用 gather/take、连接、正则表达式和一般的字符串处理。
如果你认为本页需要更多内容,请提交 PR 或告诉别人你的想法。谢谢。:)
没有得到你想要的结果吗?§
如果你已经尝试了本页上的所有方法,但仍然没有效果,请考虑在 #raku 上与编译器开发者讨论,以便我们从你的用例以及你迄今为止发现的有关它的信息中学习。
一旦开发者了解到你的困境,请留出足够的时间来获得知情的回复(几天或几周,具体取决于问题的性质和潜在的解决方案)。
如果这没有奏效,请考虑在继续之前,在 我们的用户体验仓库 中提交一个关于你体验的问题。
谢谢。:)