Laravel优化

Contents

PHP

PHP7

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

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

OPcache

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

  1. Config OPcache
1
2
3
4
5
php -m | grep "Zend OPcache"
# Zend OPcache

sudo vim /etc/php/7.1/cli/php.ini
# opcache.validate_timestamps=0
1
vim cache.php
1
2
3
<?php
$a = '12';
echo $a;
1
2
3
4
php -S 0.0.0.0:8088

curl http://192.168.56.201:8088/cache.php
# 此时修改cache.php 修改不会生效
  1. PHP info
1
vim info.php
1
2
<?php
phpinfo();
1
curl http://192.168.56.201:8088/info.php
  1. OpCacheGUI
1
2
3
4
5
git clone https://github.com/PeeHaa/OpCacheGUI.git && cd OpCacheGUI

cp config.sample.php config.php

sudo vim /etc/nginx/sites-enabled/opcache.conf
1
2
3
4
5
6
7
8
9
10
11
12
13
14
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;
}
}
1
2
3
sudo nginx -t && sudo nginx -s reload

curl http://192.168.56.201.xip.io/index.php

JIT

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

1
2
3
4
5
6
7
8
默认
PHP源代码 => 经过zend编译=> OPcode字节码 => PHP解释器 => 机器码

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

启用JIT
PHP源代码 =>编译 => 机器码

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

Laravel

Config Cache

1
2
3
4
php artisan config:cache
# bootstrap/cache/config.php

php artisan config:clear
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
# 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) {
//
}
}
}

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.

1
2
3
4
php artisan route:cache
# bootstrap/cache/routes.php

php artisan route:clear
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
# 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';
}
}

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

Autoloader

1
2
3
4
composer install
# 包含composer dumpautoload -o
# bootstrap/cache/packages.php
# bootstrap/cache/services.php
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# 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"
]
},
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
# 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("<info>Discovered Package:</info> {$package}");
}

$this->info('Package manifest generated successfully.');
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
# 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';
}
}

Database

Cache

  1. 使用Redis存储Session
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
# 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
1
2
3
$value = Cache::remember('users', $minutes, function () {
return DB::table('users')->get();
});

Eager Loading

  1. Lazy Loading
1
2
3
4
5
$books = App\Book::all();

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

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

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

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

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

Chunk

1
2
3
4
5
Flight::chunk(200, function ($flights) {
foreach ($flights as $flight) {
// code
}
});

参考