【问题标题】:HHVM staticly typing lookup tables and keeping them fully cached in RAMHHVM 静态键入查找表并将它们完全缓存在 RAM 中
【发布时间】:2015-05-11 00:20:14
【问题描述】:

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

为了能够回答这个问题,您需要具备以下所有知识/经验

  • HHVM 如何在请求之间将数据结构缓存到 RAM 中
  • 如何判断 HHVM 数据结构将保持不变
  • 如何声明数组索引和值类型

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

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

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

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

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

Lookuptable35543.php

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

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

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

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

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

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

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

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

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

我不希望使用 OO 或创建类。如何声明类型、程序风格? 如果一个类是绝对必要的,你能给出适合我上面要求的示例代码吗?

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

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

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

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

这是我目前发现的。

  • const 数组在 HHVM 中还不能工作。 const foo = ['uuid1',43,43]; 抛出关于 HHVM 仅支持具有标量值的常量的错误。

  • 带有数组值的向量:我还不知道它会如何执行......我希望它会比普通数组更好。这是有效的 HH 代码。

这是进步,因为 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> 找到了对此的参考,很遗憾它可能还不能使用。

【问题讨论】:

    标签: hhvm hacklang


    【解决方案1】:

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

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

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

    • 测量每个性能差异。不要过早地优化——使用分析来指导。过早的优化阻碍了开发人员的工作效率,这可能是致命的。
    • 尽可能从顶层(“伪域”)中获取信息。返回 static 或常量数组的函数应该没问题,通常会帮助 HHVM 更好地优化代码。
    • 尽可能避免引用,尤其是在你非常关心性能的情况下在这个数组中。
    • 您可能应该研究一下repo authoritiative mode,它可以帮助 HHVM 进一步优化很多东西 - 但特别是在这种情况下,repo auth 模式可以做的更积极的内联可能是一个胜利。

    编辑,一边

    因为那么整个数据结构必须被解析为字符串,并为每个请求转换为内存中的数据结构。据我所知,APC 也有同样的问题

    这正是我所说的过早优化的意思:你甚至没有尝试就拒绝 APC,即使它可能是一种更清洁的方式来做你想做的事。事实证明,在大多数情况下,HHVM 实际上可以优化 APC 中存储数组的序列化/反序列化,尤其是当它们是从未修改过的常量数组时。如上所述,HHVM 非常擅长优化许多常见模式。只需编写干净的代码,对其进行分析,然后修复热点。

    【讨论】:

    • 你认为我正在做过早的优化真是太荒谬了。我什至明确表示我已经测量了性能差异。你在浪费我们俩的时间。我只需要知道如何定义 HHVM 可以缓存在 RAM 中的不可变数据结构。你最后一次将大约 100MB 的数组加载到内存中以服务每个请求是什么时候?我已经在这个代码库上工作了超过 6 个月,并且不断地对其进行分析和改进。在 hack 规范发布之前,我停止了它的工作。现在恢复工作。
    • 你认为我做错了,而不是回答我的问题,即使没有理由相信我过早地优化了。我说我测量,你忽略了这一点。我已经尝试过反序列化(哈哈,我主要在我当前的方法之前使用它)并且我已经尝试过 APC(也许不是所有可能的 APC 方式)。如果您阅读了我的问题,那应该很明显,必须有比在每个请求上解析 100mb 的序列化数组更好的方法。我正在做科学研究,处理大量数据。由于请求模型,PHP 并不理想,但使用 HHVM 可以克服这一点
    • 过去我不知道有一种方法可以告诉 HHVM 某个 APC 缓存是恒定的。如果我能够并且可以优化反序列化,那么我就有责任管理何时缓存和过期数据。目前,HHVM 为我管理得很好。这仍然没有解决我关于告诉 HHVM 事物是什么类型的问题。
    • 那么回答问题怎么样?
    • 我确实回答了你的问题。正如我在上面所说的,HHVM 已经非常擅长优化静态数组,并且很可能会比你更好地找出键入和缓存数组的正确方法。事实上,它不会在每个请求上重新解析和重新生成数组——它实际上足够聪明,在整个过程中只有一个数组,在每个请求之间共享。回购权威模式是我唯一的改进建议。
    【解决方案2】:

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

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

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

    【讨论】:

    • 在浪费了我所有这些时间之后,HHVM 家伙回来说 ImmVectors 目前比数组慢。虽然理论上它们应该更快,但并不是因为它们还没有被优化。它们可能被实现为 PHP 类。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2019-03-17
    • 2013-01-02
    • 1970-01-01
    • 2019-06-16
    相关资源
    最近更新 更多