Tải trước trong PHP 7.4 – Stitcher.io

  • Post category:lập trình


Với PHP 7.4, tính năng hỗ trợ tải trước đã được thêm vào, một tính năng có thể cải thiện đáng kể hiệu suất mã của bạn.

Tóm lại, đây là cách nó hoạt động:

  • Để tải trước các tệp, bạn cần viết một tập lệnh PHP tùy chỉnh
  • Tập lệnh này được thực thi một lần khi khởi động máy chủ
  • Tất cả các tệp được tải sẵn đều có sẵn trong bộ nhớ cho mọi yêu cầu
  • Những thay đổi được thực hiện đối với các tệp được tải sẵn sẽ không có bất kỳ ảnh hưởng nào cho đến khi máy chủ được khởi động lại

Chúng ta hãy nhìn vào nó một cách sâu sắc.

# Opcache, nhưng hơn thế nữa

Mặc dù tải trước được xây dựng dựa trên opcache nhưng nó không hoàn toàn giống nhau. Opcache sẽ lấy các tệp nguồn PHP của bạn, biên dịch nó thành “opcodes” và lưu trữ các tệp đã biên dịch đó trên đĩa.

Bạn có thể coi opcode như một cách thể hiện mã cấp thấp, có thể dễ dàng diễn giải trong thời gian chạy. Vì vậy, opcache bỏ qua bước dịch giữa các tệp nguồn của bạn và những gì trình thông dịch PHP thực sự cần khi chạy. Một chiến thắng lớn!

Nhưng còn nhiều thứ có thể đạt được. Các tập tin bị xóa không biết về các tập tin khác. Nếu bạn có một lớp học A kéo dài từ lớp học B, bạn vẫn cần liên kết chúng với nhau trong thời gian chạy. Hơn nữa, opcache thực hiện kiểm tra xem liệu các tệp nguồn có bị sửa đổi hay không và sẽ làm mất hiệu lực bộ đệm của nó dựa trên đó.

Vì vậy, đây là lúc việc tải trước phát huy tác dụng: nó sẽ không chỉ biên dịch các tệp nguồn thành opcode mà còn liên kết các lớp, đặc điểm và giao diện liên quan với nhau. Sau đó, nó sẽ giữ khối mã có thể chạy được “đã biên dịch” này – nghĩa là: mã mà trình thông dịch PHP có thể sử dụng được – trong bộ nhớ.

Khi một yêu cầu đến máy chủ, giờ đây nó có thể sử dụng các phần của cơ sở mã đã được tải vào bộ nhớ mà không cần bất kỳ chi phí nào.

Vậy chúng ta đang nói về “những phần nào của cơ sở mã”?

# Tải trước trong thực tế

Để quá trình tải trước hoạt động, bạn – nhà phát triển – phải cho máy chủ biết cần tải tệp nào. Việc này được thực hiện bằng một tập lệnh PHP đơn giản, thực sự không có gì khó khăn cả.

Các quy tắc rất đơn giản:

  • Bạn cung cấp tập lệnh tải trước và liên kết tới tập lệnh đó trong tệp php.ini của mình bằng cách sử dụng opcache.preload
  • Mọi tệp PHP bạn muốn tải trước phải được chuyển tới opcache_compile_file() hoặc được yêu cầu một lần, từ trong tập lệnh tải trước

Giả sử bạn muốn tải trước một framework, Laravel chẳng hạn. Tập lệnh của bạn sẽ phải lặp qua tất cả các tệp PHP trong vendor/laravel thư mục và bao gồm từng cái một.

Đây là cách bạn liên kết tới tập lệnh này trong php.ini:

opcache.preload=/path/to/project/preload.php

Và đây là một triển khai giả:

$files = ;

foreach ($files as $file) {
    opcache_compile_file($file);
}

# Cảnh báo: Không thể tải trước lớp chưa liên kết

Tuy nhiên, hãy chờ đợi, có một lời cảnh báo! Để các tệp được tải trước, các phần phụ thuộc của chúng — giao diện, đặc điểm và lớp cha — cũng phải được tải trước.

Nếu có bất kỳ vấn đề nào với các phần phụ thuộc của lớp, bạn sẽ được thông báo về vấn đề đó khi máy chủ khởi động:

Can't preload unlinked class 
Illuminate\Database\Query\JoinClause: 
Unknown parent 
Illuminate\Database\Query\Builder

Nhìn thấy, opcache_compile_file() sẽ phân tích một tập tin, nhưng không thực thi nó. Điều này có nghĩa là nếu một lớp có các phần phụ thuộc chưa được tải trước thì chính lớp đó cũng không thể được tải trước.

Đây không phải là vấn đề nghiêm trọng, máy chủ của bạn sẽ hoạt động tốt; nhưng bạn sẽ không có tất cả các tệp được tải sẵn mà bạn thực sự muốn.

May mắn thay, có một cách để đảm bảo các tệp được liên kết cũng được tải: thay vì sử dụng opcache_compile_file bạn có thể dùng require_oncevà để trình tải tự động đã đăng ký (có thể là của nhà soạn nhạc) lo phần còn lại.

$files = ;

foreach ($files as $file) {
    require_once($file);
}

Vẫn còn một số lưu ý. Ví dụ: nếu bạn đang cố tải trước Laravel, thì có một số lớp trong khung có phần phụ thuộc vào các lớp khác chưa tồn tại. Ví dụ: lớp bộ đệm của hệ thống tập tin \Illuminate\Filesystem\Cache có sự phụ thuộc vào \League\Flysystem\Cached\Storage\AbstractCachecó thể không được cài đặt trong dự án của bạn nếu bạn chưa bao giờ sử dụng bộ đệm hệ thống tệp.

Bạn có thể gặp phải lỗi “không tìm thấy lớp” khi cố tải trước mọi thứ. May mắn thay, trong bản cài đặt Laravel mặc định, chỉ có một số ít các lớp này có thể dễ dàng bỏ qua. Để thuận tiện, tôi đã viết một lớp trình tải trước nhỏ để giúp việc bỏ qua các tệp dễ dàng hơn, nó trông như thế này:

class Preloader
{
    private array $ignores = ();

    private static int $count = 0;

    private array $paths;

    private array $fileMap;

    public function __construct(string ...$paths)
    {
        $this->paths = $paths;

        
        
        
        $classMap = require __DIR__ . '/vendor/composer/autoload_classmap.php';

        $this->fileMap = array_flip($classMap);
    }
    
    public function paths(string ...$paths): Preloader
    {
        $this->paths = array_merge(
            $this->paths,
            $paths
        );

        return $this;
    }

    public function ignore(string ...$names): Preloader
    {
        $this->ignores = array_merge(
            $this->ignores,
            $names
        );

        return $this;
    }

    public function load(): void
    {
        
        
        foreach ($this->paths as $path) {
            $this->loadPath(rtrim($path, "https://stitcher.io/"));
        }

        $count = self::$count;

        echo "(Preloader) Preloaded {$count} classes" . PHP_EOL;
    }

    private function loadPath(string $path): void
    {
        
        
        if (is_dir($path)) {
            $this->loadDir($path);

            return;
        }

        
        $this->loadFile($path);
    }

    private function loadDir(string $path): void
    {
        $handle = opendir($path);

        
        
        
        while ($file = readdir($handle)) {
            if (in_array($file, ('.', '..'))) {
                continue;
            }

            $this->loadPath("{$path}/{$file}");
        }

        closedir($handle);
    }

    private function loadFile(string $path): void
    {
        
        $class = $this->fileMap($path) ?? null;

        
        if ($this->shouldIgnore($class)) {
            return;
        }

        
        
        require_once($path);

        self::$count++;

        echo "(Preloader) Preloaded `{$class}`" . PHP_EOL;
    }

    private function shouldIgnore(?string $name): bool
    {
        if ($name === null) {
            return true;
        }

        foreach ($this->ignores as $ignore) {
            if (strpos($name, $ignore) === 0) {
                return true;
            }
        }

        return false;
    }
}

Bằng cách thêm lớp này vào cùng một tập lệnh tải trước, giờ đây chúng ta có thể tải toàn bộ khung công tác Laravel như sau:



(new Preloader())
    ->paths(__DIR__ . '/vendor/laravel')
    ->ignore(
        \Illuminate\Filesystem\Cache::class,
        \Illuminate\Log\LogManager::class,
        \Illuminate\Http\Testing\File::class,
        \Illuminate\Http\UploadedFile::class,
        \Illuminate\Support\Carbon::class,
    )
    ->load();

# Nó có hoạt động không?

Tất nhiên đó là câu hỏi quan trọng nhất: tất cả các tệp có được tải chính xác không? Bạn có thể chỉ cần kiểm tra nó bằng cách khởi động lại máy chủ và kết xuất đầu ra của opcache_get_status() trong một tập lệnh PHP. Bạn sẽ thấy nó có một khóa gọi là preload_statistics, sẽ liệt kê tất cả các hàm, lớp và tập lệnh được tải sẵn; cũng như bộ nhớ được sử dụng bởi các tệp được tải sẵn.

# Hỗ trợ nhà soạn nhạc

Một tính năng đầy hứa hẹn có lẽ là giải pháp tải trước tự động dựa trên trình soạn thảo, giải pháp này đã được sử dụng bởi hầu hết các dự án PHP hiện đại. Mọi người đang làm việc để thêm tùy chọn cấu hình tải trước vào composer.json, từ đó sẽ tạo tệp tải trước cho bạn! Hiện tại, tính năng này vẫn đang được hoàn thiện nhưng bạn có thể theo dõi tại đây.

Cập nhật 29-11-2019: hỗ trợ nhà soạn nhạc đã dừng lại, như có thể đọc được bằng câu trả lời của Jordi.

#Yêu cầu máy chủ

Có hai điều quan trọng hơn cần đề cập về phía nhà phát triển khi sử dụng tính năng tải trước.

Bạn đã biết rằng bạn cần chỉ định một mục trong php.ini để quá trình tải trước hoạt động. Điều này có nghĩa là nếu bạn đang sử dụng dịch vụ lưu trữ chia sẻ, bạn sẽ không thể tự do định cấu hình PHP theo cách bạn muốn. Trong thực tế, bạn sẽ cần một máy chủ chuyên dụng (ảo) để có thể tối ưu hóa các tệp được tải sẵn cho một dự án. Vì vậy, hãy ghi nhớ điều đó.

Cũng nên nhớ rằng bạn sẽ cần phải khởi động lại máy chủ (php-fpm là đủ nếu bạn đang sử dụng nó) mỗi khi bạn muốn tải lại các tệp trong bộ nhớ. Điều này có vẻ hiển nhiên đối với hầu hết mọi người, nhưng vẫn đáng được đề cập.

# Hiệu suất

Bây giờ đến câu hỏi quan trọng nhất: tải trước có thực sự cải thiện hiệu suất không?

Tất nhiên, câu trả lời là có: Ben Morel đã chia sẻ một số điểm chuẩn, có thể tìm thấy trong cùng một vấn đề về nhà soạn nhạc được liên kết trước đó. Tôi cũng đã tự đánh giá điểm chuẩn của mình trong một dự án Laravel ngoài đời thực. Bạn có thể đọc về họ ở đây.

Thật thú vị, bạn có thể quyết định chỉ tải trước “các lớp nóng” — các lớp được sử dụng thường xuyên trong cơ sở mã của bạn. Điểm chuẩn của Ben cho thấy rằng chỉ tải khoảng 100 lớp nóng thực sự mang lại hiệu suất tốt hơn so với việc tải trước mọi thứ. Đó là sự khác biệt giữa mức tăng hiệu suất 13% và 17%.

Tất nhiên, những lớp nào nên được tải trước sẽ phụ thuộc vào dự án cụ thể của bạn. Sẽ là khôn ngoan nếu chỉ cần tải trước càng nhiều càng tốt khi bắt đầu. Nếu bạn thực sự cần tăng một vài phần trăm, bạn sẽ phải theo dõi mã của mình trong khi chạy.

Tất nhiên tất cả những điều này cũng có thể được tự động hóa và có thể sẽ được thực hiện trong tương lai.

Hiện tại, điều quan trọng nhất cần nhớ là trình soạn thảo sẽ thêm hỗ trợ để bạn không phải tự tạo tệp tải trước và tính năng này rất dễ thiết lập trên máy chủ của bạn, vì bạn có toàn quyền kiểm soát nó .

Nhận thấy một tpyo? Bạn có thể gửi PR để sửa nó. Nếu bạn muốn cập nhật về những gì đang diễn ra trên blog này, bạn có thể Theo dõi danh sách gửi thư của tôi: gửi email đến [email protected] và tôi sẽ thêm bạn vào danh sách.

Bạn sẽ sử dụng tính năng tải trước khi PHP 7.4 xuất hiện chứ? Bạn có nhận xét hay suy nghĩ nào sau khi đọc bài viết này không? Hãy cho tôi biết qua Twitter hoặc e-mail.



Trả lời