php - 与 CLI 请求相比,导致 HTTP 请求速度变慢的原因是什么?
问题描述
我正在 WAMP 环境中使用 PHP 7.1 开发 Laravel 5.5 应用程序。我正在使用 Money 的值对象,并希望对其进行基准测试以确保使用它的基本算术运算不会太昂贵。因此,我编写了以下测试,比较了使用 PHP 浮点数(控件)和使用 Money 对象(测试)添加货币。它平均了多次测试的时间。
<?php
namespace App\Delegators;
use App\Admin\Attributes\Money;
use App\Admin\General\Currency;
use App\Admin\Marketplaces\NetworkStore;
use App\Admin\Repo;
/**
* Benchmark helper class.
*/
class Benchmark
{
public function money()
{
/** @var NetworkStore $store */
$store = Repo::GetSelectedStore();
/**
* Declare at what numbers the test starts and ends.
*
* This numbers represent the bounds for the number of times money will be added together.
*/
$testFrom = 90;
$testTo = 100;
// Declare the number of times each test will be run.
$numberOfTests = 2;
dump('Money Benchmark: Control');
// Foreach test.
for ($t = $testFrom; $t < $testTo; $t++)
{
// Declare the average time taken for this test.
$averageTimeTaken = 0;
// Average the times over multiple such tests.
for ($c = 0; $c < $numberOfTests; $c++)
{
$from = microtime(true);
$money1 = 100;
for ($i = 0; $i < $t; $i++)
{
$money2 = (float) random_int(1, 10);
$money1 += $money2;
}
$averageTimeTaken += microtime(true) - $from;
}
// Divide the average by the total number of tests.
$averageTimeTaken /= $numberOfTests;
// Declare the themed time in ms.
$themedTime = round($averageTimeTaken * 1000) .'ms';
dump("Test $t: $themedTime");
}
dump('Money Benchmark: Value Object');
/** @var Currency $currency */
$currency = $store->getCurrency();
// Foreach test.
for ($t = $testFrom; $t < $testTo; $t++)
{
// Declare the average time taken for this test.
$averageTimeTaken = 0;
// Average the times over multiple such tests.
for ($c = 0; $c < $numberOfTests; $c++)
{
$from = microtime(true);
$money1 = new Money(100, $currency);
for ($i = 0; $i < $t; $i++)
{
$money2 = new Money(random_int(1, 10), $currency);
$money1->add($money2);
}
$averageTimeTaken += microtime(true) - $from;
}
// Divide the average by the total number of tests.
$averageTimeTaken /= $numberOfTests;
// Declare the themed time in ms.
$themedTime = round($averageTimeTaken * 1000) .'ms';
dump("Test $t: $themedTime");
}
dd("Money Test Complete");
}
}
我在两个地方测试它:在控制器索引方法的顶部和工匠 CLI 命令内部,分别如下所示。
控制器:
<?php
namespace App\Http\Controllers\Admin;
...
class HomeController extends Controller
{
/**
* Show the application dashboard.
*
* @return \Illuminate\Http\Response
*/
public function index(Request $request)
{
$benchmark = new Benchmark;
$benchmark->money();
return view('admin.home');
}
}
命令行:
<?php
namespace App\Console\Commands;
...
class Test extends Command
{
...
/**
* Execute the console command.
*
* @return mixed
* @throws
*/
public function handle()
{
$benchmark = new Benchmark;
$benchmark->money();
}
}
但是 CLI 环境中的基准测试结果比我通过 HTTP 请求获得的结果快 10 倍以上,如下所示。我希望基于缓存和其他配置的两个环境之间存在差异,但我不明白其中任何一个会如何影响我编写的测试的性能。
HTTP 请求的结果:
"Money Benchmark: Control"
"Test 90: 0ms"
"Test 91: 0ms"
"Test 92: 0ms"
"Test 93: 0ms"
"Test 94: 0ms"
"Test 95: 0ms"
"Test 96: 0ms"
"Test 97: 0ms"
"Test 98: 0ms"
"Test 99: 0ms"
"Money Benchmark: Value Object"
"Test 90: 27ms"
"Test 91: 23ms"
"Test 92: 23ms"
"Test 93: 24ms"
"Test 94: 24ms"
"Test 95: 24ms"
"Test 96: 24ms"
"Test 97: 25ms"
"Test 98: 24ms"
"Test 99: 25ms"
"Money Test Complete"
CLI 请求的结果:
"Money Benchmark: Control"
"Test 90: 0ms"
"Test 91: 0ms"
"Test 92: 0ms"
"Test 93: 0ms"
"Test 94: 0ms"
"Test 95: 0ms"
"Test 96: 0ms"
"Test 97: 0ms"
"Test 98: 0ms"
"Test 99: 0ms"
"Money Benchmark: Value Object"
"Test 90: 2ms"
"Test 91: 1ms"
"Test 92: 1ms"
"Test 93: 1ms"
"Test 94: 1ms"
"Test 95: 1ms"
"Test 96: 1ms"
"Test 97: 1ms"
"Test 98: 1ms"
"Test 99: 1ms"
"Money Test Complete"
例如,“Test 90: 1ms”中的数字 90 表示 $money2 已创建并添加到 $money1 90 次。
我唯一的猜测是这是一个内存问题,通过 HTTP 请求加载的应用程序占用更多内存,所以我尝试在应用程序顶部使用 gc_disable(),确认垃圾收集已禁用,但这没有任何作用。我还尝试将 php.ini 中的内存限制加倍,但这也没有效果。
在这一点上,我几乎不知道是什么导致了这里的性能如此巨大的差异。有任何想法吗?
更新
此后,我进行了进一步的测试,将问题缩小到一般性能差距。它的重现性更高,而且是一个简单的加法测试:
<?php
...
/**
* Benchmark helper class.
*/
class Benchmark
{
public function addition()
{
/**
* Declare the number of times to add a float.
*/
$numberOfAdditions = 10000;
// Declare the number of times each test will be run.
$numberOfTests = 4;
dump('Addition Benchmark');
// Declare the number to add to.
$number = 0;
// Declare the average time taken for this test.
$averageTimeTaken = 0;
// Average the times over multiple such tests.
for ($c = 0; $c < $numberOfTests; $c++)
{
$from = microtime(true);
for ($i = 0; $i < $numberOfAdditions; $i++)
{
$number += rand(1, 5);
}
$averageTimeTaken += microtime(true) - $from;
}
// Divide the average by the total number of tests.
$averageTimeTaken /= $numberOfTests;
// Declare the themed time in ms.
$themedTime = round($averageTimeTaken * 1000) .'ms';
dd("Addition Test Complete: $themedTime");
}
}
这里又是通过 artisan 命令在控制器与 CLI 中运行的测试。
控制器:
"Addition Benchmark"
"Addition Test Complete: 20ms"
命令行:
"Addition Benchmark"
"Addition Test Complete: 2ms"
更新 2
以下是有关我在 WAMP 上运行的开发环境的其他详细信息:
PHP Version: 7.1.22
System: Windows NT LAPTOP 10.0 build 17134 (Windows 10) AMD64
Build Date: Sep 13 2018 00:39:35
Compiler: MSVC14 (Visual C++ 2015)
Architecture: x64
Zend Engine v3.1.0, Copyright (c) 1998-2018 Zend Technologies
with Zend OPcache v7.1.22, Copyright (c) 1999-2018, by Zend Technologies
with Xdebug v2.6.1, Copyright (c) 2002-2018, by Derick Rethans
Apache/2.4.35 (Win64) OpenSSL/1.1.1a PHP/7.1.22
解决方案
更新
正如您所指出的,与内核无关的性能问题。我进一步深入研究了这个问题,发现在我的系统上行为是相反的。
浏览器响应
"Addition Test Complete: 1ms"
CLI 响应
"Addition Test Complete: 13ms"
所以我开始想,也许 php 和 web 服务器版本以及操作系统可能会影响运行的结果。
此外,在查看 cli 中 php 的帮助时,我注意到一个标志:
-n No configuration (ini) files will be used
因此,我尝试使用该标志 ( php -n bench-test.php
) 运行 cli 命令,它确实花费了与 Web 浏览器相同的时间:1ms
我仍然没有 100% 的答案,但我认为这是 php.ini 中的一些参数,它正在做某种缓存,在你的情况下,它在你的 cli 中默认触发,但在从 webserver 执行 php 时不会触发。
您能否提供有关您的 php 版本、操作系统和 Web 服务器的更多信息?以及如果您进行了任何类型的特殊配置或安装了任何扩展
作为参考,我的机器正在运行具有最新更新和以下版本的 Windows 10:
PHP
PHP 7.3.2 (cli) (built: Feb 5 2019 23:16:38) ( ZTS MSVC15 (Visual C++ 2017) x86 )
Copyright (c) 1997-2018 The PHP Group
Zend Engine v3.3.2, Copyright (c) 1998-2018 Zend Technologies
with Xdebug v2.7.0RC2, Copyright (c) 2002-2019, by Derick Rethans
阿帕奇
Server version: Apache/2.4.35 (Win64)
Apache Lounge VC15 Server built: Sep 19 2018 16:08:47
原始答案
与通过 artisan (cli) 执行命令时相比,接收HTTP 请求时引导的内容是显着的区别之一。
这是因为,出于某些显而易见的原因,我们不需要在 CLI 执行中处理所有路由、请求、中间件和 http 相关的东西。
这就是为什么两次执行具有相似但不同的内核和引导过程的原因。
CLI 引导程序(控制台内核)
此过程在您执行任何工匠命令时开始:
php artisan [rest of command]
简而言之,artisan ( source )是一个 php 文件,其行为类似于public/index.php
(稍后我们将分析的 laravel 应用程序的 HTTP 入口点)。
artisan 脚本在加载boostrap/app.php
(源)文件以获取应用程序实例($app
)后,解析绑定到Illuminate\Contracts\Console\Kernel
IoC 容器中接口的类。
此类刚刚使用以下代码绑定到App\Console\Kernel.php
( source )中的boostrap/app.php
( source ):
$app->singleton(
Illuminate\Contracts\Console\Kernel::class,
App\Console\Kernel::class
);
然后 artisan 继续执行他刚刚得到的实例(记住这是一个类的实例)handle
上的方法,它扩展了 Laravel 的核心控制台内核类:这个方法在第 126 行定义的(源代码) 。$kernel
App\Console\Kernel.php
Illuminate\Foundation\Console\Kernel
内核执行以下操作:
- 通过执行
$bootstrappers
属性中声明的几个类来引导内核(第 64 行) Illuminate\Console\Application
获取( source )的单例实例(如果尚未创建,则创建一个新实例)(第 340 行)
注意:这个类可以被“简化”为 symfony 控制台应用程序类的包装器(这超出了本说明的范围)。
- 将命令注册到应用程序实例中(第 341 行)
- 将输入传递给应用程序的
run
方法(第 131 行)
注意: run 方法是在 symfony 控制台应用程序类中定义的,但它是由
Illuminate\Console\Application
该类继承的
- 返回第 4 点的结果(第 131 行)
Web 浏览器引导程序(HTTP 内核)
过程非常相似,但这次的入口点不再是工匠,而是public/index.php
文件。
如果将此文件与 artisan 进行比较,您只会注意到一些差异,关键是从容器解析的内核是绑定到类的Illuminate\Contracts\Http\Kernel
类。
同样,这个类刚刚被绑定到App\Http\Kernel
( source )中,boostrap/app.php
代码如下:
$app->singleton(
Illuminate\Contracts\Http\Kernel::class,
App\Http\Kernel::class
);
所以这次Kernel类文件是:Illuminate\Foundation\Http\Kernel
(source)
您可以开始注意到两个内核使用的组件以及两者的大小的一些差异,因为 http 包含更多与路由器组件相关的代码。
实际上,http 内核在创建类时会执行以下操作:
- 在路由器中复制中间件优先级
- 注册中间件组和别名
然后索引文件捕获并解析传入的HTTP请求并执行该handle
方法,因此http内核的操作将继续进行:
- 准备请求
- 引导内核(与 CLI 内核的第 1 点逻辑相同)
- 通过所有已注册的中间件发送请求(全局中间件,而不是附加到路由的中间件,稍后将在路由器内部完成)。
- 将请求发送到必须找到匹配路由的路由器,检查其中间件,运行相关的函数/控制器方法,从中获取响应并将其返回给调用者以将其显示为输出。
结论
特别是 http 内核的最后一点(除了一句话总结了路由器的所有工作),与注册几个类(用于命令)并在控制台内核中进行输入匹配相比,这是一项相当繁重的工作。
随着更多组件/功能在它们之间交互(您可以考虑请求、响应、中间件、策略、api 资源、验证、身份验证、授权等),前者有更多的内容。
我给了你一个更技术性的答案,因为我想让你知道这个框架的底层到底发生了什么。我希望这是您正在寻找的答案。
如果我的回答中有任何不清楚的地方,我愿意更详细地讨论我的回答。
推荐阅读
- count - 有没有像 count 这样的方法来产生下面的输出
- javascript - 使用 adminCreateUser 命令创建 Cognito 用户后如何确认它
- ocaml - 如何在 OCaml 中“公开”类型
- c++ - 语法错误:“常量”和缺少类型说明符 - 假定为 int。注意:C++ 不支持默认整数
- azure - 带有 CSV 的 Powershell 脚本从 DisplayName 获取 objectID
- azure - 使用自定义扩展检测和响应 Azure Boards 卡事件
- sublimetext3 - Sublime Text 保持突出显示区域而不是注释掉它
- typescript - ESLint 在 monorepo 中没有获取子项目的类型
- php - PDOException 2014 在其他无缓冲查询处于活动状态时无法执行查询
- r - 如何删除 R 中的重复项?