post-image

JIT Compiler trong PHP 8

Tổng quan

Trong bài viết này chúng ta hãy cùng nhau tìm hiểu xem JIT Complier trong PHP 8 là gì nhé!

JIT là gì?

Có thể bạn cũng chưa biết về JIT nên trước hết hãy giải thích nó là gì đã nhé. Chắc bạn cũng đã biết PHP là một ngôn ngữ thông dịch, nghĩa là code của bạn không cần phải được compile trước khi chạy (giống như C/C++). Thay vào đó thì PHP engine sẽ đọc code của bạn và chạy. Nói cách khác là bạn không code để compile thành machine code cho máy tính chạy mà là code một cái kịch bản và đưa cho PHP để nó chạy.

PHP có một cái virtual machine gọi là Zend VM. Tại sao gọi nó là virtual machine, bởi vì nó đóng vai trò giống như máy tính của bạn trong việc chạy code, như vừa giải thích ở trên ấy. Nó có nhiệm vụ đọc và chạy code PHP của bạn. Nhưng mà trước đó thì code của bạn sẽ được PHP đọc và dịch thành opcode, là ngôn ngữ mà Zend VM hiểu được đã. Sau đó thì Zend VM mới chạy opcode đó được. Đây là một cái hình minh họa cho dễ hiểu.

php-lifecycle

Vậy là chúng ta cần một bước compile trước rồi mới đến bước thông dịch. Để tiết kiệm thời gian thì chúng ta có một cái gọi là OPCache (Opcode Cache) để lưu lại kết quả của bước compile để lần sau không cần phải compile nữa.

Đó là cách PHP hoạt động cho tới bây giờ. Vậy bây giờ nói về JIT compiler nhé. Ngay tên gọi của nó đã có từ compiler rồi. Nghĩa là chúng ta sẽ compile code thành machine code để chạy. JIT là viết tắt của “Just-In-Time”, nghĩa là lúc nào cần đến thì mới compile thay vì compile trước rồi mới chạy. Khi nào chạy mới compile.

Đối với PHP thì JIT compiler sẽ compile opcode thành machine code và chạy luôn code đó thay vì phải đưa cho Zend VM để nó chạy. Vậy là chúng ta không cần bước thông dịch nữa và tất nhiên là code chạy nhanh hơn rồi 😃.

Thế JIT để làm gì

Kể từ PHP 7.0, vấn đề performance của PHP đã được quan tâm hơn bao giờ hết, một phần nhờ sự cạnh tranh đến từ HHVM của Facebook (cũng dùng JIT compiler). OPCache, cấu trúc dữ liệu, mọi thứ đều được tối ưu từng tí một để đạt hiệu năng cao nhất. Và rồi gần như chẳng còn chỗ nào để improve mà mang lại hiệu năng tăng đáng kể nữa cả.

Ngoài ra thì hiệu năng của PHP đối với một ngôn ngữ server đã có thể xem là khá tốt rồi, không còn là PHP chậm chạp ngày xưa nữa. Nên cũng đã đến lúc mở rộng khả năng của PHP một chút, đến những lĩnh vực như data analysis, 3D/2D rendering…

Trước đây những đoạn code đòi hỏi performance cao thường được viết dưới dạng C/C++ extension thay vì PHP package. Ví dụ như phpredis luôn nhanh hơn predis đến 6-7 lần. Nếu code PHP được compile thay vì interpret thì chúng ta sẽ có được những package PHP với hiệu năng không kém gì các extension viết bằng C/C++.

Vậy nên JIT compiler đã được chọn vì đây là hướng đi thú vị và tiềm năng hơn cả.

Dùng thử xem sao

Giới thiệu xong rồi thì bây giờ hãy thử dùng xem sao. Vì vẫn chưa có bản release cho PHP 8.0 nên mình sẽ phải compile từ source code. Source code của PHP thì ở đây. Dù version 7.4 vẫn đang alpha nhưng master đã là 8.0 rồi. Đầu tiên tải source code về đã

wget -O php.zip https://github.com/php/php-src/archive/master.zip
unzip php.zip
cd php-src-master

Sau đó cài dependencies. Mình dùng Ubuntu, nếu bạn dùng distro khác thì tự tìm package tương tự nhé.

apt-get install \
    autoconf \
    bison \
    dpkg-dev \
    file \
    g++ \
    gcc \
    libc-dev \
    make \
    pkg-config \
    re2c \
    libxml2-dev \
    libsqlite3-dev

Generate build files.

./buildconf

Sau đó là chạy ./configure để setup bản build. Lựa chọn các option để compile PHP. Bạn có thể chọn chỗ để cài bằng cách thêm --prefix=<install_dir>. Mặc định nó sẽ cài vào /usr/local/bin nên nếu bạn có cài sẵn 1 phiên bản PHP khác rồi thì nhớ set cái này. PHP sẽ được cài vào đường dẫn <install_dir>/bin/php. Thêm cả đường dẫn cho file config bằng --with-config-file-path và --with-config-file-scan-dir nếu bạn muốn nữa.

Ngoài ra cũng còn nhiều option khác, bạn có thể xem tất cả option bằng cách chạy

./configure --help

Ví dụ mình chạy ./configure như sau

./configure \
    --prefix=/opt \
    --with-config-file-path=/opt/php \
    --with-config-file-scan-dir=/opt/php/conf.d

Build.

make -j$(nproc)

Build xong thì cài. Có thể sẽ phải dùng sudo tùy prefix mà bạn chọn lúc trước nhé.

make install

Thử kiểm tra bằng php -v xem. À nếu bạn có set --prefix vừa nãy thì nhớ chạy PHP đúng đường dẫn nhé. Ví dụ /opt/bin/php -v.

PHP 8.0.0-dev (cli) (built: Jul 15 2019 02:22:59) ( NTS )
Copyright (c) The PHP Group
Zend Engine v4.0.0-dev, Copyright (c) Zend Technologies
    with Zend OPcache v8.0.0-dev, Copyright (c), by Zend Technologies

JIT compiler là một phần của OPCache extension nên để dùng được JIT thì bạn phải bật OPCache trước đã. Thêm mấy dòng này vào file php.ini ở trong config-file-path lúc nãy nhé.

zend_extension=opcache.so
opcache.enable=1
opcache.enable_cli=1

Để bật JIT compiler thì bạn thêm 2 dòng này. Dòng thứ 2 là optional nhé, nhưng mà có vẻ được khuyến khích nên dùng thay vì default. Chi tiết về các config bạn có thể xem ở đây.

opcache.jit_buffer_size=32M
opcache.jit=1235

Bây giờ bạn thử chạy một file PHP với option -dopcache.jit_debug=1 xem. Sẽ thấy được assembly code được compile ra. Ví dụ mình có file như này.

<?php

$a = 0;

for ($i = 0; $i < 10000; $i++) {
    $a += $i;
}

echo $a;

Thì nó sẽ compile thành assembly code như sau.

JIT$/php-src-master/hello.php: ; (/php-src-master/hello.php)
        sub $0x10, %rsp
        lea 0x50(%r14), %rdi
        cmp $0xa, 0x8(%rdi)
        jnz .L1
        mov (%rdi), %rdi
        cmp $0x0, 0x18(%rdi)
        jnz .L12
        add $0x8, %rdi
.L1:
        test $0x1, 0x9(%rdi)
        jnz .L13
.L2:
        mov $0x0, (%rdi)
        mov $0x4, 0x8(%rdi)

; ...
; dài lắm demo một đoạn thôi nhé

Bây giờ thử 1 chút benchmark xem nó có nhanh hơn không nhé. Trong source code của PHP có sẵn 1 file Zend/bench.php để bạn chạy thử. Trong file này đều là những đoạn code tính toán rất nhiều (hash, loop .etc). Đây là kết quả mình chạy ra (đã được format lại cho dễ so sánh).

                   No OPCache       OPCache        JIT
simple             0.021            0.006          0.001
simplecall         0.007            0.004          0.001
simpleucall        0.017            0.003          0.001
simpleudcall       0.019            0.003          0.001
mandel             0.079            0.022          0.007
mandel2            0.081            0.033          0.008
ackermann(7)       0.016            0.013          0.008
ary(50000)         0.008            0.004          0.003
ary2(50000)        0.003            0.003          0.002
ary3(2000)         0.033            0.028          0.011
fibo(30)           0.055            0.043          0.022
hash1(50000)       0.015            0.010          0.009
hash2(500)         0.006            0.007          0.005
heapsort(20000)    0.021            0.023          0.010
matrix(20)         0.020            0.017          0.010
nestedloop(12)     0.038            0.016          0.006
sieve(30)          0.012            0.008          0.003
strcat(200000)     0.004            0.004          0.002
--------------------------------------------------------
Total              0.456            0.247          0.112

Tất nhiên là nó chạy nhanh hơn rồi, không có gì lạ cả. Mình sẽ thử benchmark 1 cái web app nữa xem sao. Mình tạo 1 cái project Laravel mới tinh và benchmark bằng tool ApacheBench

ab -t10 -c10 http://test.localhost/

Kết quả thì đúng như chúng ta đã nói ở phần trước là chả khác gì cả 😄.

PHP 7.3: 131.37 req/s
PHP 8.0 + JIT: 133.57 req/s

Nguồn: https://viblo.asia/p/try-out-jit-compiler-with-php-80-RnB5pwMYlPG

Leave a Reply

Your email address will not be published. Required fields are marked *