>我开发了一个用于处理文档集合的模块。
该软件的一次运行会收集有关它们的信息。数据存储在两个结构中,称为%processed
和%symbols
.需要缓存数据,以便在同一组文档上后续运行软件,其中一些文档可能会更改。(文档本身使用 CompUnit 模块进行缓存)。
目前,数据结构的存储/恢复如下:
# storing
'processed.raku`.IO.spurt: %processed.raku;
'symbols.raku`.IO.spurt: %symbol.raku;
# restoring
my %processed = EVALFILE 'processed.raku';
my %symbols = EVALFILE 'symbols.raku';
将这些结构输出到文件中(可能非常大)可能会很慢,因为哈希被解析以创建字符串化表单,并且输入速度很慢,因为它们正在重新编译。
它不用于检查缓存文件,仅用于保存软件运行之间的状态。
此外,虽然这对我的用例来说不是问题,但据我所知,这种技术通常不能使用,因为字符串化(序列化)不适用于 Raku 闭包。
我想知道是否可以使用 CompUnit 模块,因为它们用于存储模块的编译版本。因此,也许它们可以用来存储数据结构的"编译"或"内部"版本?
已经有办法做到这一点了吗?
如果没有,是否有任何技术原因可能是不可能的?
(很有可能你已经尝试过这个和/或它不适合你的用例,但我想我会提到它,以防万一它对你或其他发现这个问题的人有帮助。
您是否考虑过使用 JSON::Fast 将数据序列化到 JSON 或从 JSON 序列化数据? 它已针对(反)序列化速度进行了优化,这是基本字符串化没有/不可能的方式。 这不允许存储Block
或其他闭包——正如你提到的,Raku 目前没有很好的方法来序列化它们。 但是,由于您提到这不是问题,因此 JSON 可能适合您的用例。
[编辑:正如您在下面指出的,这可能会使对某些 Raku 数据结构的支持更加困难。 通常(但并非总是)可以通过将数据类型指定为序列化步骤的一部分来解决此问题:
use JSON::Fast;
my $a = <a a a b>.BagHash;
my $json = $a.&to-json;
my BagHash() $b = from-json($json);
say $a eqv $b # OUTPUT: «True»
对于难以在 JSON 中表示的数据结构(例如具有非字符串键的数据结构),这会变得更加复杂。 JSON::Class 模块也可能有所帮助,但我还没有测试它的速度。
在查看了其他答案并查看了预编译的代码后,我意识到我最初的问题是基于误解。
Rakudo 编译器生成一个中间"字节码",然后在运行时使用该字节码。由于模块是用于编译目的的独立单元,因此可以对其进行预编译。可以缓存此中间结果,从而显着加快 Raku 程序的速度。
当 Raku 程序use
已经编译的代码时,编译器不会再次编译它。
我曾认为预编译缓存是程序内部状态的一种存储,但事实并非如此。这就是为什么 - 我认为 - @ralph对这个问题感到困惑,因为我没有问正确的问题。
我的问题是关于数据的存储(和恢复)。JSON::Fast,正如@codesections所讨论的那样,非常快,因为它被 Rakudo 编译器在低级别使用,因此进行了高度优化。因此,在还原时重组数据将比还原本机数据类型更快,因为缓慢的速率确定步骤是从"磁盘"存储和还原,JSON 可以非常快地执行此操作。
有趣的是,我提到的 CompUnit 模块使用低级 JSON 函数,使 JSON::Fast 如此之快。
我现在正在考虑使用优化例程存储数据的其他方法,也许使用压缩/存档模块。这将归结为测试哪个最快。可能是 JSON 路由是最快的。
所以这个问题没有明确的答案,因为问题本身是"不正确的"。
更新正如@RichardHainsworth所指出的,我对他们的问题感到困惑,尽管觉得像我一样回答应该会有所帮助。根据他的反应,以及他不接受@codesection的回答的决定,当时这是唯一的另一个答案,我得出结论,最好删除这个答案,以鼓励其他人回答。但是现在理查德提供了一个提供了良好解决方案的答案,我正在取消删除它,希望现在更有用。
<小时 />里拉;DR不使用EVALFILE
,将数据存储在一个模块中,然后use
。有一些简单的方法可以做到这一点,这些方法对EVALFILE
进行了最小但有用的改进。有更复杂的方法可能会更好。
比EVALFILE
略有改进
我决定首先提出一个小的改进,这样你就可以巩固你从EVALFILE
思维的转变。它在两个方面很小:
-
实施只需几分钟。
-
它只给你一个小小的改进
EVALFILE
.
我建议您在实际实现我在第一部分中描述的内容之前,正确考虑此答案的其余部分(它描述了更复杂的改进,可能会带来更大的回报,而不是这个小的回报)。我认为这个小小的改进可能会被证明是多余的,而不是作为通往后面部分的心理桥梁。
编写一个程序,比如store.raku
,创建一个模块,比如data.rakumod
:
use lib '.';
my %hash-to-store = :a, :b;
my $hash-as-raku-code = %hash-to-store .raku;
my $raku-code-to-store = "unit module data; our %hash = $hash-as-raku-code";
spurt 'data.rakumod', $raku-code-to-store;
(我对.raku
的使用当然过于简单化。以上只是一个概念证明。
这种写入数据的形式将具有与当前解决方案基本相同的性能,因此在这方面没有任何好处。
接下来,编写另一个程序,例如using.raku
,使用它:
use lib '.';
use data;
say %data::hash; # {a => True, b => True}
use
模块将需要编译它。因此,第一次使用这种方法而不是EVALFILE
读取数据时,它不会更快,就像写入数据一样。但是对于后续读取来说,它应该要快得多。(直到您下次更改数据并且必须重新生成数据模块。
本节也不涉及闭包字符串化,这意味着您仍在执行可能不必要的数据写入阶段。
串化闭包;一个黑客
可以扩展上一节的方法,以包括闭包的字符串化。
你"只需要"访问包含闭包的源代码;使用正则表达式/解析来提取闭包;然后将匹配项写入数据模块。容易!;)
现在,我将跳过填写细节,部分原因是我再次认为这只是一个心理桥梁,并建议您继续阅读,而不是尝试按照我刚才描述的那样做。
使用复合单位
现在我们到达:
我想知道是否可以使用 CompUnit 模块,因为它们用于存储模块的编译版本。因此,也许它们可以用来存储数据结构的"编译"或"内部"版本?
我对你在这里问的问题有点困惑,原因有两个。首先,我认为您指的是文档("文档本身是使用 CompUnit 模块缓存的"),并且文档存储为模块。其次,如果您确实意味着文档存储为模块,那么为什么您不能存储您想要存储在其中的数据呢?您是否担心隐藏数据?
无论如何,我假设您询问的是将数据存储在文档模块中,并且您对"隐藏"该数据的方法感兴趣。
一个简单的选择是像我在第一部分中所做的那样编写数据,但在实际文档之后的末尾插入our %hash = $hash-as-raku-code";
etc 代码,而不是在开头。
但也许这太丑/不够"隐藏"?
另一种选择可能是在文档模块的末尾添加带有 Pod 块配置数据的 Pod 块。
例如,将所有代码放入文档模块并放入say
作为概念验证:
# ...
# Document goes here
# ...
# At end of document:
=begin data :array<foo bar> :hash{k1=>v1, k2=>v2} :baz :qux(3.14)
=end data
say $=pod[0].config<array>; # foo bar
也就是说,这只是在模块中执行的代码;我不知道模块的编译形式是否保留了配置数据。此外,您需要使用"Pod 加载器"(参见另一个 Raku 文件中的 Access pod)。但我的猜测是你对这些事情了如指掌。
同样,这可能不够隐藏,并且存在约束:
数据只能是类型为
Str
、Int
、Num
或Bool
的文字标量,或以Array
秒或Hash
秒为单位的聚合。数据中不能有实际的换行符。(您可能有双引号字符串,其中有
n
s。
修改乐道
Aiui,假设 RakuAST 落地,编写可以使用 Raku 模块进行任意工作的 Rakudo 插件相对容易。这似乎是从 RakuAST 宏到基本is parsed
宏的短跳,而这反过来又看起来像是提取源代码(例如闭包源)的短跳,因为它通过编译器,然后将其作为数据吐回编译的代码中,可能附加到 Pod 声明器块,而这些块又作为代码附加到代码。
所以,也许只是等待一两年,看看 RakuAST 是否登陆并获得您需要通过乐道做你需要做的事情的钩子?