使用Yii profiling一个应用

如果在部署一个Yii应用时,你使用了所有的最佳实践,但是你仍然得不到你想要的性能,很有可能是应用本身存在一些性能瓶颈。在处理这些性能瓶颈时最主要的原则是你不应该假设任何事请,并在尝试优化它之前去测试和profile代码。

在本小节中,我们将会尝试找出Yii2最小应用的性能瓶颈。

准备

按照官方指南http://www.yiiframework.com/doc-2.0/guide-start-installation.html的描述,使用Composer包管理器创建一个新的yii2-app-basic应用。

  1. 设置数据库连接,并应用如下migration:
  1. <?php
  2. use yii\db\Migration;
  3. class m160308_093233_create_example_tables extends Migration
  4. {
  5. public function up()
  6. {
  7. $tableOptions = null;
  8. if ($this->db->driverName === 'mysql') {
  9. $tableOptions = 'CHARACTER SET utf8 COLLATE utf8_general_ci ENGINE=InnoDB';
  10. }
  11. $this->createTable('{{%category}}', [
  12. 'id' => $this->primaryKey(),
  13. 'name' => $this->string()->notNull(),
  14. ], $tableOptions);
  15. $this->createTable('{{%article}}', [
  16. 'id' => $this->primaryKey(),
  17. 'category_id' => $this->integer()->notNull(),
  18. 'title' => $this->string()->notNull(),
  19. 'text' => $this->text()->notNull(),
  20. ], $tableOptions);
  21. $this->createIndex('idx-article-category_id',
  22. '{{%article}}', 'category_id');
  23. $this->addForeignKey('fk-article-category_id',
  24. '{{%article}}', 'category_id', '{{%category}}', 'id');
  25. }
  26. public function down()
  27. {
  28. $this->dropTable('{{%article}}');
  29. $this->dropTable('{{%category}}');
  30. }
  31. }
  1. 在Yii中为每一个表生成模型。
  2. 写如下控制台命令:
  1. <?php
  2. namespace app\commands;
  3. use app\models\Article;
  4. use app\models\Category;
  5. use Faker\Factory;
  6. use yii\console\Controller;
  7. class DataController extends Controller
  8. {
  9. public function actionInit()
  10. {
  11. $db = \Yii::$app->db;
  12. $faker = Factory::create();
  13. $transaction = $db->beginTransaction();
  14. try {
  15. $categories = [];
  16. for ($id = 1; $id <= 100; $id++) {
  17. $categories[] = [
  18. 'id' => $id,
  19. 'name' => $faker->name,
  20. ];
  21. }
  22. $db->createCommand()->batchInsert(Category::tableName(), ['id', 'name'], $categories)->execute();
  23. $articles = [];
  24. for ($id = 1; $id <= 100; $id++) {
  25. $articles[] = [
  26. 'id' => $id,
  27. 'category_id' => $faker->numberBetween(1, 100),
  28. 'title' => $faker->text($maxNbChars = 100),
  29. 'text' => $faker->text($maxNbChars = 200),
  30. ];
  31. }
  32. $db->createCommand()
  33. ->batchInsert(Article::tableName(), ['id', 'category_id', 'title', 'text'], $articles)->execute();
  34. $transaction->commit();
  35. } catch (\Exception $e) {
  36. $transaction->rollBack();
  37. throw $e;
  38. }
  39. }
  40. }

并执行它:

  1. ./yii data/init
  1. 添加ArticleController类:
  1. <?php
  2. namespace app\controllers;
  3. use Yii;
  4. use app\models\Article;
  5. use yii\data\ActiveDataProvider;
  6. use yii\web\Controller;
  7. class ArticleController extends Controller
  8. {
  9. public function actionIndex()
  10. {
  11. $query = Article::find();
  12. $dataProvider = new ActiveDataProvider([
  13. 'query' => $query,
  14. ]);
  15. return $this->render('index', [
  16. 'dataProvider' => $dataProvider,
  17. ]);
  18. }
  19. }
  1. 添加views/article/index.php视图:
  1. <?php
  2. use yii\helpers\Html;
  3. use yii\widgets\ListView;
  4. /* @var $this yii\web\View */
  5. /* @var $dataProvider yii\data\ActiveDataProvider */
  6. $this->title = 'Articles';
  7. $this->params['breadcrumbs'][] = $this->title;
  8. ?>
  9. <div class="article-index">
  10. <h1><?= Html::encode($this->title) ?></h1>
  11. <?= ListView::widget([
  12. 'dataProvider' => $dataProvider,
  13. 'itemOptions' => ['class' => 'item'],
  14. 'itemView' => '_item',
  15. ]) ?>
  16. </div>

然后添加views/article/_item.php

  1. <?php
  2. use yii\helpers\Html;
  3. /* @var $this yii\web\View */
  4. /* @var $model app\models\Article */
  5. ?>
  6. <div class="panel panel-default">
  7. <div class="panel-heading"><?= Html::encode($model->title);
  8. ?></div>
  9. <div class="panel-body">
  10. Category: <?= Html::encode($model->category->name) ?>
  11. </div>
  12. </div>

如何做…

跟随如下步骤,profile基于Yii的应用:

  1. 打开文章页面:

使用Yii profiling一个应用 - 图1

  1. 打开views/article/index.php并在ListView小部件之前和之后添加profiler调用:
  1. <div class="article-index">
  2. <h1><?= Html::encode($this->title) ?></h1>
  3. <?php Yii::beginProfile('articles') ?>
  4. <?= ListView::widget([
  5. 'dataProvider' => $dataProvider,
  6. 'itemOptions' => ['class' => 'item'],
  7. 'itemView' => '_item',
  8. ]) ?>
  9. <?php Yii::endProfile('articles') ?>
  10. </div>

现在刷新这个页面。

  1. 展开页面底部的调试面板,点击timing badge(在我们的例子中是73ms):

使用Yii profiling一个应用 - 图2

现在检查Profiling报告:

使用Yii profiling一个应用 - 图3

我们可以看到我们的文章块花费了将近40ms。

  1. 打开我们的控制器,并为文章的category关系添加主动加载:
  1. class ArticleController extends Controller
  2. {
  3. public function actionIndex()
  4. {
  5. $query = Article::find()->with('category');
  6. $dataProvider = new ActiveDataProvider([
  7. 'query' => $query,
  8. ]);
  9. return $this->render('index', [
  10. 'dataProvider' => $dataProvider,
  11. ]);
  12. }
  13. }
  1. 回到网站,刷新页面,再次打开Profiling报告:

使用Yii profiling一个应用 - 图4

现在这个文章列表花费了将近25ms,因为应用使用主动加载做了更少的SQL查询。

工作原理…

你可以使用Yii::beginProfileYii::endProfile查看源代码的任何片段:

  1. Yii::beginProfile('articles');
  2. // ...
  3. Yii::endProfile('articles');

在执行过页面以后,你可以在调试模块的Profiling页面看到这个报告,有所有的执行时间。

此外,你可以使用嵌套profiling调用:

  1. Yii::beginProfile('outer');
  2. Yii::beginProfile('inner');
  3. // ...
  4. Yii::endProfile('inner');
  5. Yii::endProfile('outer');

注意:注意要正确的打开和关闭调用,以及正确命名block名称。如果你忘记调用Yii::endProfile,或者颠倒了Yii::endProfile('inner')Yii::endProfile('outer')的嵌套顺序,性能Profiling将不会工作。

参考