20.11.2016     1042

В этой заметке я расскажу о своих результатах тестирования производительности двух ORM: xPDO и Propel. Весь код выложен на GitHub тут: https://github.com/andchir/xpdo_propel_comparison.

Я создал 3 таблички: книги, авторы и издательства. Сгенерировал 1000 книг в БД, плюс 100 авторов и 100 издательств. Не буду подробно останавливаться как создавал модели для табличек, это можно посмотреть на GitHub. По-моему для обеих ORM созданы абсолютно одинаковые условия.

Сначала самый простейший запрос, вытаскиваю по 100 записей из БД.

xPDO:

<?php

use xPDO\xPDO;

// setup the autoloading
require_once __DIR__ . '/vendor/autoload.php';

require_once __DIR__ . '/config.php';

$startTime = microtime(true);

$xpdo = xPDO::getInstance('aMySQLDatabase', [
    xPDO::OPT_CACHE_PATH => __DIR__ . '/xpdo/cache/',
    xPDO::OPT_HYDRATE_FIELDS => true,
    xPDO::OPT_HYDRATE_RELATED_OBJECTS => true,
    xPDO::OPT_HYDRATE_ADHOC_FIELDS => true,
    xPDO::OPT_CONNECTIONS => [
        [
            'dsn' => 'mysql:host=' . $config['db']['host'] . ';dbname=' . $config['db']['dbname'] . ';charset=utf8',
            'username' => $config['db']['username'],
            'password' => $config['db']['password'],
            'options' => [
                xPDO::OPT_CONN_MUTABLE => true,
            ],
            'driverOptions' => [],
        ],
    ],
]);

$xpdo->setLogLevel(xPDO::LOG_LEVEL_INFO);
$xpdo->setLogTarget(XPDO_CLI_MODE ? 'ECHO' : 'HTML');

$limit = 100;

$query = $xpdo->newQuery('bookstore\Book');
$query->sortby('title','ASC');
$query->limit($limit);

$books = $xpdo->getCollection('bookstore\Book', $query);
foreach($books as $book) {
    echo '<pre>' . print_r( $book->toJSON(), true ) . '</pre>';
}

$endTime = microtime(true);

echo '<br>Total records: ' . $limit;
echo '<br>Total time: ' . sprintf('%f', ( $endTime - $startTime ));
echo '<br>Memory: ' . round(memory_get_usage()/1024/1024, 4) . ' MB';

Propel:

<?php

// setup the autoloading
require_once __DIR__ . '/vendor/autoload.php';

$startTime = microtime(true);

// setup Propel
require_once __DIR__ . '/generated-conf/propel/config.php';

$limit = 100;

$books = BookQuery::create()
    ->orderByTitle()
    ->limit($limit)
    ->find();

foreach($books as $book) {
    echo '<pre>' . print_r( $book->toJSON(), true ) . '</pre>';
}

$endTime = microtime(true);

echo '<br>Total records: ' . $limit;
echo '<br>Total time: ' . sprintf('%f', ( $endTime - $startTime ));
echo '<br>Memory: ' . round(memory_get_usage()/1024/1024, 4) . ' MB';

Результаты

xPDO:

Total records: 100
Total time: 0.073161
Memory: 0.9901 MB

Propel:

Total records: 100
Total time: 0.012900
Memory: 1.2067 MB

xPDO немного обошел по потреблению памяти, но Propel очень прилично выиграл по времени выполнения (в 5 с половиной раз быстрее).


Теперь усложним задачу. Делаем джоин одной таблички, вытаскиваем 500 записей.

xPDO:

<?php
$limit = 500;

$query = $xpdo->newQuery('bookstore\Book');
$query->sortby('title','ASC');
$query->limit($limit);

$query->select($xpdo->getSelectColumns('bookstore\Book','Book'));
$query->select(array('Author.first_name AS author_name', 'Author.last_name AS author_last_name'));
$query->leftJoin('bookstore\Author', 'Author');

$books = $xpdo->getCollection('bookstore\Book', $query);
foreach($books as $book) {
    echo '<pre>' . print_r( $book->toJSON(), true ) . '</pre>';
}

Propel:

<?php
$limit = 500;

$books = BookQuery::create()
    ->leftJoin('Book.Author')
    ->withColumn('Author.FirstName', 'AuthorFirstName')
    ->withColumn('Author.LastName', 'AuthorLastName')
    ->orderByTitle()
    ->limit($limit)
    ->find();

foreach($books as $book) {
    echo '<pre>' . print_r( $book->toJSON(), true ) . '</pre>';
}

Результаты

xPDO:

Total records: 500
Total time: 0.358788
Memory: 2.9128 MB

Propel:

Total records: 500
Total time: 0.051907
Memory: 2.1503 MB

Propel выполнил задачу почти в 7 раз быстрее чем xPDO, по памяти тоже немного обошел.


Теперь тоже будем вытаскивать данные со второй таблички, но сделаем это без джоина, а отдельным запросом (только для теста).

xPDO:

<?php
$limit = 500;

$query = $xpdo->newQuery('bookstore\Book');
$query->sortby('title','ASC');
$query->limit($limit);

$books = $xpdo->getCollection('bookstore\Book', $query);
foreach($books as $book) {
    $author = $book->getOne('Author');
    echo '<br>Author: ' . $author->get('first_name') . ' ' . $author->get('last_name');
    echo '<pre>' . print_r( $book->toJSON(), true ) . '</pre>';
}

Propel:

<?php
$limit = 500;

$books = BookQuery::create()
    ->orderByTitle()
    ->limit($limit)
    ->find();

foreach($books as $book) {
    $author = $book->getAuthor();
    echo '<br>Author: ' . $author->getFirstName() . ' ' . $author->getLastName();
    echo '<pre>' . print_r( $book->toJSON(), true ) . '</pre>';
}

Результаты

xPDO:

Total records: 500
Total time: 1.026858
Memory: 4.1216 MB

Propel:

Total records: 500
Total time: 0.102198
Memory: 2.0729 MB

Propel выполнил задачу в 10 раз быстрее чем xPDO, памяти потратил в 2 раза меньше.


Выводы

На сайте xPDO авторы написали, что их творение это "ультра-легкая альтернатива Propel", но на практике на данный момент получается совсем не так. Propel очень существенно обходит xPDO по производительности.