<jupyter_start><jupyter_text>HHVM 背景介绍HHVM 是 Facebook (现 Meta) 开发的高性能 PHP 虚拟机，宣称达到了官方解释器的 9x 性能 为什么会有 HHVM 脚本语言  Pros一般我们使用脚本语言 （Perl，Python，PHP，JavaScript）是为了以下几个目的1. 大部分的脚本语言都拥有较为完备的外部库，能够帮助开发者快速的开发/测试   - 使用 Python 作为 ebt 的技术栈也是因为 `numpy`, `pandas` 等数据科学库的支持比别的编程语言更加的完备2. 动态语言的特性使得开发过程变得异常轻松，可以最大程度的实现可复用性和多态性，打个比方    - Python   ```python   def evaluate(model_impl, params):       return model_impl.calculate(params)   class Model(object):       def calculate(params):           sum_val = 0           for param in params:               sum_val += param           return sum_val   ```    - C++   ```cpp   class IModel {       public:           virtual double calculate(const vector &params) = 0;           virtual int calculate(const vector &params) = 0;   }   class Model : public IModel {       public:           double calculate(const vector &params) {               double sum_val = 0;               for (vector::iterator it = params.begin(); it != params.end(); ++it) {                   sum_val += *it;               }               return sum_val;           }           int calculate(const vector &params) {               int sum_val = 0;               for (vector::iterator it = params.begin(); it != params.end(); ++it) {                   sum_val += *it;               }               return sum_val;           }   }   double evaluate(IModel* model_impl, const vector &params) {       return model_impl->calculate(params);   }   int evaluate(IModel* model_impl, const vector &params) {       return model_impl->calculate(params);   }   ```   - 模版   ```cpp   // This is ok but template is not a general feature for all static typed language   template    T evaluate(IModel* model_impl, const vector &params) {       return model_impl->calculate(params);   }   template     T::value_type evaluate(IModel* model_impl, const T& params) {        return model_impl->calculate(params);    }   ```3. 动态语言完全是解释执行，调试成本较低。每当改动源码有所改动后，程序重新运行更加直接，只需要解释器重新读取源码即可。编译性语言需要更多的步骤与时间，例如 C++，为了从源码生成可执行程序需要 链接静态库 -> .obj -> 链接动态库 -> 可执行程序。如果是大型项目开发的话这一步骤甚至会花费几个小时。而解释执行的程序可以不需要这些步骤直接重新运行  Cons但是对于有较高性能需求的 situation，编译执行反而会成为拖累。> Although easy to implement, interpreters are generally slow, which makes scripting language prohibitive for implementing large, CPU-intensive applications. (Zhao, 2021)Debian 有一个 [benchmark game](https://benchmarksgame-team.pages.debian.net/benchmarksgame/index.html)，比较了目前比较常见的几种编程语言的运行速度/内存占用/源码大小<jupyter_code>import pandas as pd
import matplotlib.pyplot as plt
benchmarks = pd.read_csv('./data/programming_language_benchmarks_game_all_measurements.csv')

benchmarks.head(10)
compile_lang_lst = ['clang', 'csharpcore', 'csharppgo', 'gcc', 'gfortran', 'go', 'gpp', 'java', 'rust', 'swift']
interpreter_lang_lst = ['node', 'perl', 'php', 'python3']

def boxplot_by_lang(data: pd.DataFrame, colname: str) -> None:
    fig, ax = plt.subplots()
    index = 1
    for lang in compile_lang_lst:
        ax.boxplot(data[data['lang'] == lang][colname],
                   positions=[index],
                   labels=[lang],
                   boxprops=dict(color='blue'))
        index += 1

    for lang in interpreter_lang_lst:
        ax.boxplot(data[data['lang'] == lang][colname],
                   positions=[index],
                   labels=[lang],
                   boxprops=dict(color='green'))
        index += 1

    ax.set_title(colname, fontsize=15)
    ax.tick_params(axis='x', labelrotation=45)
    fig.set_size_inches(10, 6)
filtered = benchmarks[(benchmarks['status'] == 0) & (benchmarks['name'] == 'binarytrees') & (benchmarks['n'] == 21)].reset_index()
boxplot_by_lang(data=filtered, colname='elapsed(s)')
boxplot_by_lang(data=filtered, colname='mem(KB)')

boxplot_by_lang(data=filtered, colname='size(B)')<jupyter_output><empty_output><jupyter_text>通过以上数据可以显然看出与编译执行语言相比，解释执行的语言在 CPU 的处理性能有明显的优势，部分编译执行的语言在内存处理（申请与回收）上也有着异常优秀的表现。像 Meta 这样的巨型公司需要 host 的服务器是也是巨型的上图是 Meta 在 Oregon 的数据中心，据说这两个 building 的造价就高达了 *$750M* (约合 *¥47.78 亿*)，并且在 2020 年中 Meta 又在边上造了两个如此巨型的数据中心的一大作用就是用来做 Facebook 的服务器主机，所以为了优化服务器的物理成本，从代码上优化服务器性能是必然的。我们都知道 Facebook 是用 PHP 实现的，据 HHVM 的项目主持者之一的 <NAME> 所说，Facebook 有约莫 $2 \times 10^7$ 行 PHP 代码 （2012年）。Facebook 的开发者在检查之后发现自己服务器的性能问题很大一部分就是资源的消耗就在 PHP 的解释器本身上，所以需要考虑的就是如何优化 PHP 的性能  如何优化 PHP 的性能1. 使用性能更好的语言重写服务端，如 C++，GO，Java    - 重构2千万行代码？算了算了2. 使用 RPC 将部分业务功能独立，减少 PHP 的处理，比如 Twitter 就将自己的很多业务逻辑从 Ruby on Rails 转为了 Java 和 Scala (前端由 node + react 独立实现)   - RPC 框架              但是不解决问题3. 以动态扩展的形式优化 PHP 的性能瓶颈，用 PHP 加载 C++ 实现的方式绕开性能瓶颈    - ebt 目前的解决方案，但是对 Facebook 这样历史包袱过重的源码仓库来说，性能瓶颈并不是 1-2 处小地方，而是不断累积的后果，并且 PHP 的扩展并不像 pybind 一样有比较成熟的加载方式4. 优化 PHP 的解释器本身 如何优化 PHP 的解释器1. 改进自己的源码   - 用 PHP 写的 PHP 性能分析工具 [XHProf](https://github.com/phacility/xhprof)   - 定位性能瓶颈处，优化代码逻辑，就像 leetcode 去做到 >99%   - 优化的不够2. 优化 PHP 的解释器实现   - [Zend Engine](https://github.com/php/php-src/tree/master/Zend)   - 将 PHP 编译为 `opcode` 然后执行 `opcode`   - 优化 Zend 的性能代价太大，并且还要做到版本的向下兼容3. 将 PHP 转为 C/C++，然后编译生成   - Hiphop Compiler for PHP (Zhao, 2012)       被认为是一种 Ahead of Time 的方式，能够完成非常多的代码优化（就像 LLVM 一样），但是一个问题就是无法正确支持 PHP 中的部分特性，如 `eval()`, `create_function()` 等    > Support for eval is theoretically possible in HipHop by invoking an interpreter. However, we opted not to implement that because the use of eval exposes serious security problems and is therefore discouraged. (Zhao, 2012)    就像是在 C++ 里面 `Py_Initialize()` 启动了 Python 环境，对内存消耗和代码优化都不是很友好4. 实现一个虚拟机，将 PHP 源码转为当前平台的机器码执行 （JVM）   - 如今的 HHVM HipHop Compiler for PHP （HPHPc） C++ 与 PHP 的特性区别    HPHPc 的编译设计   ```php<?phpdefine("confName", "OOPSLA");define("firstYear", 1986);function year($edition) {   return firstYear - 1 + $edition;}echo "Hello " . confName . "'" . year(27);``` 1. 生成 AST读取 PHP 的源码并生成对应的 AST    2. 预分析遍历 AST 并且记录所有符号的信息（类，函数，变量，全局 const），判断哪些符号是存在同名覆盖的情况需要考虑代码上下文。并且建立符号之间的依赖图，为后续的并行优化提供前置准备 3. 预优化处理不需要类型就能完成的油画，比如将函数转为内联函数，优化不必要的逻辑判断，移除不可能到达的代码片段等 4. 判断类型核心部分，基于 Damas-Milner constraint-based 算法判断不同符号的类型`variant` 是 `any` 类型，所有符号在类型推断出类型前都是 `variant` 类型，在推断过程中如果成功判断为任何类型则覆盖 `variant` 5. 后优化在拥有类型之后，HipHop 编译器会根据类型信息优化包括简单的数值计算和逻辑判断在内的部分代码片段，然后再重新执行一次预优化阶段的优化逻辑 6. 生成最后编译器会遍历带有类型的，被优化后的 AST，并且生成对应的 C++ 代码，并且运行 gcc 编译 C++ 源码，生成的 C++ 部分包括1. 类的头文件：每个 PHP 的类实现会生成对应的 C++ 头文件与对应的类声明2. PHP 的头文件：每个 PHP 的文件（函数）会生成对应的 C++ 头文件与对应的声明3. 具体实现文件：包含了一个或者多个声明类/函数的具体实现4. 系统文件：不包含 PHP 的源码内容，但是会记录 Symbol Table 如何从 AST 生成 C++ 代码 鸭子类型除了正常标注类型的符号以外，仍然存在无法判断的 `variant` 类型，需要实现一个 `variant` 类型支持运行时处理的逻辑 动态符号指符号的具体指向需要运行时判断，如```pythonif SOME_CONDITION:   x = 10else:   x = 'a'```对于这种情况，HipHop 用一张 *global symbol table* 记录了：   - 全局变量   - 动态申明的 constant   - 函数体/类实现中的静态变量   - 重复定义的函数体/类所谓重复定义是指在不同文件中定义了同样的函数名/类名（在动态语言中这样做是合法的），GST 会在符号名后添加唯一后缀，然后根据具体 `include` 语句引用的文件名导向不同后缀的同名函数/类实现。对于静态编译过程中出现的无法处理的**动态符号**，HipHop 会通过类似的逻辑生成临时的 *local symbol table*。在具体处理实际的 variable 时，编译器会通过当前所处的上下文获取 LST 和 GST，从 table 中获取实际的指向。同时，HipHop 还支持了动态添加实例属性，如<jupyter_code>class SomeClass:

    def __init__(self):
        pass


some_instance = SomeClass()
some_instance.a = 10
print(some_instance.a)<jupyter_output>10<jupyter_text>为了实现这样的 feature，HipHop 还实现了一个 *property symbol table*，用于记录符号中的属性，当源码尝试访问实例/类属性时，会通过 PST 找到对应的符号。但是 HPHPc 的痛点就是- 无法支持 PHP 的动态方法 `eval()`, `create_function()` 等- 并且编译后部署上线的源码重量会比 PHP 要大很多，对于2千万行的源码，这种硬性的成本是毁灭性的  HHVM （HipHop Virtual Machine）会什么虚拟机能够解决传统解释器的问题呢？ JIT （Just In Time）JVM，Numba 和 HHVM 都是对解释性语言的 jit 优化，但是尝试过 Numba 的我们也知道，jit 有可能并没有想象中的那么美好，对于一些简单的方法，jit 甚至还会拖累性能<jupyter_code>def make_it_double(a: int) -> int:
    return a * 2
%%timeit
make_it_double(10)
import numba

make_it_double_numba = numba.jit(make_it_double)
%%timeit
make_it_double_numba(10)<jupyter_output>90.7 ns ± 0.405 ns per loop (mean ± std. dev. of 7 runs, 10,000,000 loops each)