Laravel优化

Contents

  • PHP

    • PHP7

    • OPcache

    • JIT

  • Laravel

    • Config Cache

    • Route Cache

    • Autoloader

  • Database

    • Cache

    • Eager Loading

    • Chunk

PHP

PHP7

PHP7相比PHP5.6有至少1倍以上的性能提升

  • 让PHP7达到最高性能的几个Tips

  • PHP的性能演进(从PHP5.0到PHP7.1的性能全评测)

  • The Definitive PHP 5.6, 7.0, 7.1, 7.2 & 7.3 Benchmarks (2019)

PHP7更多特性可以参考从PHP 5.6.x 移植到 PHP 7.0.x - 新特性

OPcache

OPcache将PHP代码预编译生成的脚本文件Opcode缓存在共享内存中供以后反复使用 从而避免了从磁盘读取代码再次编译的时间消耗

  1. Config OPcache
php -m | grep "Zend OPcache"
# Zend OPcache

sudo vim /etc/php/7.1/cli/php.ini
# opcache.validate_timestamps=0
vim cache.php
php -S 0.0.0.0:8088

curl http://192.168.56.201:8088/cache.php
# 此时修改cache.php 修改不会生效
  1. PHP info
vim info.php
curl http://192.168.56.201:8088/info.php
  1. OpCacheGUI
git clone https://github.com/PeeHaa/OpCacheGUI.git && cd OpCacheGUI

cp config.sample.php config.php

sudo vim /etc/nginx/sites-enabled/opcache.conf
server {
    listen      80;
    server_name 192.168.56.201.xip.io;

    root        /home/op/OpCacheGUI/public;

    try_files $uri $uri/ /index.php;

    location ~* \.php$ {
        include fastcgi_params;
        fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
        fastcgi_pass unix:/run/php/php7.1-fpm.sock;
    }
}
sudo nginx -t && sudo nginx -s reload

curl http://192.168.56.201.xip.io/index.php
  • Opcode是啥以及如何使用好Opcache

  • PHP7开启Opcode打造强悍性能

  • 让子弹飞~利用 OPcache 扩展提升 PHP7 性能 | Laravel 篇

JIT

JIT(Just-In-Time) Compiler 即时编译器

默认
PHP源代码 => 经过zend编译=> OPcode字节码 => PHP解释器 => 机器码

启用OPcache
PHP源代码 => 查找OPcache 没有则经过zend编译 => OPcode字节码 => PHP解释器 => 机器码

启用JIT
PHP源代码 =>编译 => 机器码
  • PHP 霸主地位被动摇,JIT 是穷途末路后的绝地反击?

  • HHVM 是如何提升 PHP 性能的?

  • php 的 opcache 和最近的 php jit 有什么区别?

JIT理论性能优于OPcache 因为JIT缓存机器码而OPcache缓存Opcode字节码

Laravel

Config Cache

php artisan config:cache
# bootstrap/cache/config.php

php artisan config:clear
# vendor/laravel/framework/src/Illuminate/Foundation/Bootstrap/LoadEnvironmentVariables.php
class LoadEnvironmentVariables
{
    /**
     * Bootstrap the given application.
     *
     * @param  \Illuminate\Contracts\Foundation\Application  $app
     * @return void
     */
    public function bootstrap(Application $app)
    {
        if ($app->configurationIsCached()) {
            return;
        }

        $this->checkForSpecificEnvironmentFile($app);

        try {
            (new Dotenv($app->environmentPath(), $app->environmentFile()))->load();
        } catch (InvalidPathException $e) {
            //
        }
    }
}
  • Configuration Caching

If you execute the config:cache command during your deployment process, you should be sure that you are only calling the env function from within your configuration files. Once the configuration has been cached, the .env file will not be loaded and all calls to the env function will return null.

Route Cache

In some cases, your route registration may even be up to 100x faster.

php artisan route:cache
# bootstrap/cache/routes.php

php artisan route:clear
# vendor/laravel/framework/src/Illuminate/Foundation/Support/Providers/RouteServiceProvider.php
class RouteServiceProvider extends ServiceProvider
{
    /**
     * The controller namespace for the application.
     *
     * @var string|null
     */
    protected $namespace;

    /**
     * Bootstrap any application services.
     *
     * @return void
     */
    public function boot()
    {
        $this->setRootControllerNamespace();

        if ($this->app->routesAreCached()) {
            $this->loadCachedRoutes();
        } else {
            $this->loadRoutes();

            $this->app->booted(function () {
                $this->app['router']->getRoutes()->refreshNameLookups();
                $this->app['router']->getRoutes()->refreshActionLookups();
            });
        }
    }
}

# vendor/laravel/framework/src/Illuminate/Foundation/Application.php
class Application extends Container implements ApplicationContract, HttpKernelInterface
{
    /**
     * Determine if the application routes are cached.
     *
     * @return bool
     */
    public function routesAreCached()
    {
        return $this['files']->exists($this->getCachedRoutesPath());
    }

    /**
     * Get the path to the routes cache file.
     *
     * @return string
     */
    public function getCachedRoutesPath()
    {
        return $this->bootstrapPath().'/cache/routes.php';
    }
}
  • Route Caching

Closure based routes cannot be cached. To use route caching, you must convert any Closure routes to controller classes.

Autoloader

composer install
# 包含composer dumpautoload -o
# bootstrap/cache/packages.php
# bootstrap/cache/services.php
# composer.json
{
    "scripts": {
        "post-root-package-install": [
            "@php -r \"file_exists('.env') || copy('.env.example', '.env');\""
        ],
        "post-create-project-cmd": [
            "@php artisan key:generate"
        ],
        "post-autoload-dump": [
            "Illuminate\\Foundation\\ComposerScripts::postAutoloadDump",
            "@php artisan package:discover"
        ]
    },
}
# vendor/laravel/framework/src/Illuminate/Foundation/ComposerScripts.php
class ComposerScripts
{
    /**
     * Handle the post-autoload-dump Composer event.
     *
     * @param  \Composer\Script\Event  $event
     * @return void
     */
    public static function postAutoloadDump(Event $event)
    {
        require_once $event->getComposer()->getConfig()->get('vendor-dir').'/autoload.php';

        static::clearCompiled();
    }
}

# vendor/laravel/framework/src/Illuminate/Foundation/Console/PackageDiscoverCommand.php
class PackageDiscoverCommand extends Command
{
    /**
     * The console command signature.
     *
     * @var string
     */
    protected $signature = 'package:discover';

    /**
     * The console command description.
     *
     * @var string
     */
    protected $description = 'Rebuild the cached package manifest';

    /**
     * Execute the console command.
     *
     * @param  \Illuminate\Foundation\PackageManifest  $manifest
     * @return void
     */
    public function handle(PackageManifest $manifest)
    {
        $manifest->build();

        foreach (array_keys($manifest->manifest) as $package) {
            $this->line("Discovered Package: {$package}");
        }

        $this->info('Package manifest generated successfully.');
    }
}
# vendor/laravel/framework/src/Illuminate/Foundation/Application.php
class Application extends Container implements ApplicationContract, HttpKernelInterface
{
    /**
     * Create a new Illuminate application instance.
     *
     * @param  string|null  $basePath
     * @return void
     */
    public function __construct($basePath = null)
    {
        if ($basePath) {
            $this->setBasePath($basePath);
        }

        $this->registerBaseBindings();

        $this->registerBaseServiceProviders();

        $this->registerCoreContainerAliases();
    }

    /**
     * Register the basic bindings into the container.
     *
     * @return void
     */
    protected function registerBaseBindings()
    {
        static::setInstance($this);

        $this->instance('app', $this);

        $this->instance(Container::class, $this);

        $this->instance(PackageManifest::class, new PackageManifest(
            new Filesystem, $this->basePath(), $this->getCachedPackagesPath()
        ));
    }

    /**
     * Register all of the configured providers.
     *
     * @return void
     */
    public function registerConfiguredProviders()
    {
        $providers = Collection::make($this->config['app.providers'])
                        ->partition(function ($provider) {
                            return Str::startsWith($provider, 'Illuminate\\');
                        });

        $providers->splice(1, 0, [$this->make(PackageManifest::class)->providers()]);

        (new ProviderRepository($this, new Filesystem, $this->getCachedServicesPath()))
                    ->load($providers->collapse()->toArray());
    }

    /**
     * Get the path to the cached services.php file.
     *
     * @return string
     */
    public function getCachedServicesPath()
    {
        return $this->bootstrapPath().'/cache/services.php';
    }

    /**
     * Get the path to the cached packages.php file.
     *
     * @return string
     */
    public function getCachedPackagesPath()
    {
        return $this->bootstrapPath().'/cache/packages.php';
    }
}
  • 深入了解 Laravel 5.5 Package Auto Discovery

Database

Cache

  1. 使用Redis存储Session
# config/database.php
return [
    'redis' => [
        'client' => 'predis',

        'default' => [
            'host' => env('REDIS_HOST', '127.0.0.1'),
            'password' => env('REDIS_PASSWORD', null),
            'port' => env('REDIS_PORT', 6379),
            'database' => 0,
        ],
    ],
];

# config/session.php
return [
    'driver' => env('SESSION_DRIVER', 'redis'),

    'connection' => 'default',
];

# .env
SESSION_DRIVER=redis
  1. 使用Cache存储Data
$value = Cache::remember('users', $minutes, function () {
    return DB::table('users')->get();
});

Eager Loading

  1. Lazy Loading
$books = App\Book::all();

foreach ($books as $book) {
    echo $book->author->name;
}

Lazy Loading可能引起N+1性能问题 详见记一次慢查询问题的解决过程 / 预加载

  1. Eager Loading
$books = App\Book::with('author')->get();

foreach ($books as $book) {
    echo $book->author->name;
}

Eager Loading单次查询会获取更多数据 导致内存占用高

Chunk

Flight::chunk(200, function ($flights) {
    foreach ($flights as $flight) {
        // code
    }
});

参考

  • Laravel 性能问题始终难以释怀,求指点

  • 十个 Laravel 5 程序优化技巧

  • 12 Tips for Laravel Performance Optimization

  • appstract/laravel-opcache

  • 深入浅出 JIT 编译器

你可能感兴趣的:(Laravel优化)