HHVM 静态输入查找表并将它们完全缓存在 RAM 中

HHVM staticly typing lookup tables and keeping them fully cached in RAM

我正在做科学研究,通过数百万个数兆字节数组的组合进行处理

为了能够回答这个问题,您需要 knowledge/experience 以下所有内容

我需要处理整个数组,因此需要加载和处理大量数据。 (在 LAN 上几分钟内有数百万个请求)。我完成请求的速度越快,我完成工作的速度就越快。如果 HHVM 必须在每个请求上加载这些数据,那么它占了完成请求时间的很大一部分(有时超过一半,这取决于我当时正在做的分析的复杂性)。

我找到了一种方法,可以让我将这些数据结构缓存在 RAM 中(无需从文件加载、解释代码、无缘无故地向数组推送数十万次、无需无意义的重复反序列化等) ,因此我消除了这个巨大的可测量延迟。

我有 3 个关于如何使这个更快的问题:

  1. 我现在的做法是否造成了全局范围的惩罚?
  2. 如何将我的数组声明为常量并告诉 HHVM 需要什么数据类型? 如果我将我的数组声明为常量,是否有必要为 HHVM 声明类型?
  3. 如果我不使用嵌套数组,而是使用 3 个独立的数据结构 ImmVector、PackedArray,或者定义一个 class 会更快吗?

请记住,任何阻止 HHVM 在请求之间将数据结构缓存在 RAM 中的行为都应被视为 unacceptable。

Lookuptable35543.php

<?php
$data = [
    ["uuid (20 chars)", 5336, 7373],
    ["uuid (20 chars)", 5336, 7373],
    #more lines as above
];
?>

其中一些文件有很多 MB,而且数量很多 Main.php

<?php
function main() {
  require /path/to/Lookuptable35543.php;
  #(Do stuff with $data)
}
?>

这工作得很好,因为 Main.php 收到了数千个请求,在短时间内,HHVM 将 Lookuptable.php 的数据结构保存在内存中。避免无意义的处理和 IO,因为它只是位于 RAM 中,随时可以使用。 (我有足够的内存)

不幸的是,我知道如何让 HHVM 在 RAM 中保存查找 table 的唯一方法是,我在查找####.php 文件中的全局范围内设置 $data (然后要求查找文件变成数据处理文件中的一个函数:Main.php)?这样 HHVM 就不会费心加载文件或重新执行代码来创建 $data,因为它可以看到 $data 可以在编译时确定,并且在运行时永远不会改变。这可行,但我不知道在 lookup###.php 文件的全局范围中存在 $data 是否会受到惩罚。 (或者它可能根本不是全局的,因为它是 required 到 main.php 的函数中?)

  1. 如果我从 Lookup.php 中的函数 return $data 并像这样从 Main.php 调用该函数怎么办 Main.php

HHVM 会在 RAM 中 JIT getData() 的结果吗? 不知何故,我将函数与不可预测性联系在一起......但也许 HHVM 足够聪明,知道函数结果可以在编译时确定,并且永远不会改变?

我无法将查找 table 放在 Main.php 中,因为我 require 根据请求类型进行不同的查找 table。

  1. 有什么方法可以告诉 HHVM 我的外部数组将始终具有一个永不更改的整数索引,并且外部数组的值将始终是一个数组? 也许我需要使用 ImmVector? 那么有没有办法告诉 HHVM 我的内部数组将始终是一个固定长度的字符串,后跟 2 个整数,总是,没有额外的元素,内容永远不会改变?

我不想使用 OO 或创建 class。我如何声明类型、程序风格? 如果 class 是绝对必要的,请提供示例代码 suitable 以满足我的上述要求?

  1. 不嵌套数组会不会更快? 我刚刚意识到我可以拥有一个具有整数索引和固定长度字符串值的数组。然后是具有整数索引和整数值的第二个数组,以及具有整数索引和整数值的第三个数组。

如果您不熟悉这种 HHVM 缓存技术,请不要浪费相互的时间建议数据库、redis、APC、反序列化等。最快的方法是 HHVM 将我的各种 $data 变量保存在 RAM 中。即使从 ramdisk 文件中反序列化 $data 也很慢,因为整个数据结构必须被解析为字符串并为每个请求转换为内存中的数据结构。据我所知,APC 有同样的问题。我什至不想复制 $data。查找 table 是 immutable,只读。它们必须在 RAM 中完全结构化。我当前的缓存解决方案(在这个问题的顶部)已经给了我巨大的收益,但是根据我的 3 个问题,我认为可能会有更多的收益?

如果您想知道,我已经测量了各种数据加载或缓存方法的延迟。 现在我基本上想保留我的缓存情况,但是让 HHVM JIT 对如何输入我的数据有最大的信心,这样它可以节省时间而不是 运行 类型甚至绑定(数组大小)检查。

编辑 好的,目前还没有人能够给我任何代码示例,所以我只是尝试一下。

这是我到目前为止所发现的。

这是进步,因为 HHVM 应该能够以相同的方式缓存它,HHVM 知道整个结构是常量,并且 HHVM 知道索引都是整数。

我对这个结构仍然不满意的是: 考虑这段代码

for ($n=0;$n<count($iv);++$n) if ($x > $iv[$n][1]) dosomething();

HHVM 会在每次循环迭代时对 $if[$n][2] 执行类型检查吗? 在我上面 $iv 的定义中,没有任何内容表明内部数组的第二个元素将是一个整数。 我该如何改进呢? disabling the type checker 有什么用吗?这只是对外部类型检查器隐藏错误,还是阻止 HHVM 不断进行类型检查? (我认为这是第一件事)

也许如果我可以创建自己的用户定义类型来解决问题?

<?hh
#I don't know what mechanisms for UDT's exist, so this code is made-up
CreateUDT foo = <string,int,int>;
$iv = ImmVector<foo> {
    ['uuid1',425,244],
    ['uuid2',658,836]
};
print_r($iv);

我在 Hack Collections Literal Syntax Vector<Foo> 找到了对此的引用 Vector<Foo> 不幸的是它可能还无法使用。

我是 Facebook 的一名软件工程师,从事 HHVM 方面的工作。

整个问题对我来说都是过早优化的味道。您是否完成了分析并确定加载此数组实际上是您应用程序的瓶颈? (不仅是微基准测试,还有它实际上如何影响 真实 页面加载的性能、延迟、RPS 等。)并且还与其他影响隔离,例如,如果此数组是缓存或某些某种预先计算的数据,您需要通过以各种不同的方式缓存它来将预先计算数据的好处与实际时间隔离开来加载它。

总的来说,HHVM 非常擅长处理数组,因为它们在几乎每个代码路径中都很热门——尤其是像这个常量数组。关于如何将数组中事物的形状和类型告知它的问题,HHVM 可以自己解决所有问题,并且非常擅长在完全组成的常量数组上这样做的常数。 (它考虑数组的方式并不完全是 考虑数组的方式,所以它可能会做得更好!)基本上,除非分析表明这实际上是一个热点- 我对此持怀疑态度 - 我不会太担心它。需要注意的几个一般注意事项:

  • 衡量每个性能差异。不要过早地优化——使用分析来指导。过早的优化阻碍了开发人员的生产力,这可能是致命的。
  • 尽可能多地从顶级 ("pseudomains") 中获取信息。 returns 一个 static 或常量数组的函数应该没问题,并且通常会帮助 HHVM 更好地优化代码。
  • 尽可能避免引用,尤其是 如果您非常关心性能,则在此数组中。
  • 您可能应该研究 repo authoritiative mode,它可以帮助 HHVM 进一步优化很多事情——但特别是对于这种情况,repo 身份验证模式可以执行的更积极的内联可能是一个胜利。

编辑,放在一边:

because then the entire data structure must be parsed as a string and converted into a data structure in memory for every request. APC has the same problem as far as i know

这正是我所说的过早优化的意思:你甚至没有尝试就拒绝了 APC,即使它可能是一种更干净的做你想做的事情的方法。事实证明,在大多数情况下,HHVM 实际上 可以 优化 APC 中存储数组的 serialization/deserialization,特别是如果它们是从未修改过的常量数组。如上所述,HHVM 非常擅长优化许多常见模式。只需编写干净的代码、分析它并修复热点。

好的,我已经解决了我的第一个问题。

  1. 我没有任何全局范围的问题。我的要求是从函数 main() 内部完成的,所以就好像来自 lookuptable####.php 的代码被插入到函数 main() 中。 HHVM docs: "If the include occurs inside a function..." 基本上,如果您要打开 lookuptable####.php,代码看起来像是在全局范围内,但这不是从 hhvm 请求的文件。 main.php 是被请求的,因此在全局范围内没有代码。

  2. 我想我已经回答了我的第二个问题,它目前位于我问题的底部。我不是 100% 相信,但我很高兴继续测试它。