Інструмент аналізу швидкості PHP-функцій

останнім часом звертав увагу на матеріали про продуктивності і вимірах швидкості функцій PHP. Після аналізу ряду матеріалів був зроблений наступний висновок. Порівнянь досить багато, але всі виміри проводяться з різними вхідними умовами, вивід результатів тестування у кожного рішення свій, не кажучи вже про те, якщо з'явиться бажання перевірити тести в своєму середовищі, то доведеться копіпаст шматки коду.

Тому виникла ідея написати власний механізм простого тестування швидкості різних операцій. Задумка здалася цікавою, тому початок було покладено!

Цілі
Почати слід з постановки цілей майбутнього інструменту. Проект хотілося бачити як універсальний інструмент для тестування швидкості чого завгодно. Але поміркувавши над деталями і помилками минулого, вирішено було почати з малого.

Отже, треба було отримати інструмент, який:

  • порівнює швидкість виконання різних функцій
  • має можливість використання заздалегідь підготовлених тестів
  • виводить в читаному вигляді результат
  • має можливість створення власних тестів і варіантів представлення результатів
  • може бути легко використаний на інших платформах
Механізм вимірів
Для виміру часу виконання функції було вирішено скористатися подібним методом

protected function getTime($time = false)
{
return $time === false? microtime(true) : microtime(true) - $time;
}

Принцип дії простий, якщо відбувається виклик без параметрів, то повертаємо поточне стані часу, якщо виклик відбувається з параметром часу, тоді повертається різниця між поточним і переданим часом.

Але там де розрахунок часу, там же і споживаний обсяг пам'яті, тому надалі був доданий аналогічний метод вимірювання споживаної пам'яті.


protected function getMemory($memory = false)
{
return $memory === false? memory_get_usage() : memory_get_usage() - $memory;
}

Механізм аналогічний механіці виміру часу, тільки в даному випадку відбувається передача значення виділеної пам'яті скрипту.

Як відомо, деякі функції ведуть себе по-різному в залежності від вхідного набору даних, тому в кожному тесті визначено масив вхідних даних для функцій тестування. Для отримання найбільш точних результатів тестування функцій проводиться на кожному наборі кілька разів. Крім іншого, тестування проводиться у різній послідовності.

В ході експериментів, було помічено, що обсяг вільної пам'яті перед початком кожного тесту різний, хоч для більшості проводиться випробувань це було незначним, але для порядку хотілося б мати однакові умови для всіх випробувань або близькі до таких. Тому зберігання результатів було вирішено зберігати в локальному сховищі SQLite, а перед початком випробувань робити один перевірочний тест для заповнення змінних даними.

Для того, щоб мати можливість створення власних варіантів представлення результатів власних варіантів тесту, створено 2 моделі абстракції. Модель тестів (Test), що містить всю інформацію про тест, включаючи функції тестування. Модель представлення даних (DataViwer) містить методи перетворення результатів тестування в читабельний вигляд. Для зручного виведення вистав був задіяний шаблонизатор Twig і підключена бібліотека стилів bootstrap, також створений вьювер з графіком HighCharts.

У підсумку загальний механізм роботи такої. Беруться необхідні для порівняння функції і запускаються на виконання з різними наборами даних і в різній послідовності. При цьому відбувається вимірювання часу кожного виконання і запис результату в сховище. За цей етап відповідає модель тесту (Test). Після всіх вимірів відбувається передача результатів до представлення даних (DataViwer), де відбувається обробка і виведення інформації.

Реалізація тестування

Клас тесту

Простий приклад реалізації класу тесту, на прикладі тестування швидкості виконання операції перед инкрементирования і пост инкрементирования.

Приклад класу тесту

class IncPrefVsPos extends TestAbstract 
{
public $name = 'Speedy ++i vs i++';
public $valueTest = [100, 1000, 2000, 3000];
public $qntTest = 5;
public $viewers = [TestCore::VIEWER_TLIST, TestCore::VIEWER_TGROUP, TestCore::VIEWER_TAVG, TestCore::VIEWER_GBUBLE];
public $functions = ['postIncrement' => 'testPost', 'prefIncrement' => 'testPref'];
protected $strategy = [['testPost', 'testPref'], ['testPref', 'testPost']];

public function testPref($size)
{
$testCounter = 0;
for($i=0;$i<$size;$i++) {
++$testCounter; ++$testCounter; ++$testCounter; ++$testCounter; ++$testCounter; ++$testCounter;
++$testCounter; ++$testCounter; ++$testCounter; ++$testCounter; ++$testCounter; ++$testCounter;
++$testCounter; ++$testCounter; ++$testCounter; ++$testCounter; ++$testCounter; ++$testCounter;
++$testCounter; ++$testCounter; ++$testCounter; ++$testCounter; ++$testCounter; ++$testCounter;
}
}

public function testPost($size)
{
$testCounter = 0;
for($i=0;$i<$size;$i++) {
$testCounter++; $testCounter++; $testCounter++; $testCounter++; $testCounter++; $testCounter++;
$testCounter++; $testCounter++; $testCounter++; $testCounter++; $testCounter++; $testCounter++;
$testCounter++; $testCounter++; $testCounter++; $testCounter++; $testCounter++; $testCounter++;
$testCounter++; $testCounter++; $testCounter++; $testCounter++; $testCounter++; $testCounter++;
}
}
}

Клас повинен успадковувати абстрактний клас TestAbstract, в якому закладена основна механіка роботи з потоками даних.

$name — задає назву тесту, яке може бути використане при виведенні в DataViewer.

$valueTest — обсяг вибірки тестування, масив значень, з кожним з яких буде виконана функція для тестування. Таким чином кожна досліджувана функція має обов'язково приймати один параметр. Буде він бути числом, рядком або масивом не важливо, все залежить від конкретного випадку. У нашому прикладі, досить чисел, які будуть позначати обсяг проведених операцій з інкрементом.

$qntTest — говорить про те, яка кількість разів буде протестований кожен обсяг вибірки.

$viewers — масив подання даних, який буде сформований при рендері звіту. Насправді це набір повних імен класів успадковують абстрактний клас ViewrAbstract. Заздалегідь підготовлені вюверів винесені в константи класу TestCore.

$functions — масив назв функцій які будуть використовуватися у тестуванні. Ключі масиву є назви, які будуть відображені в результатах.

$strategy — масив стратегій тестування, кожна стратегія повинна бути представлена масивом з послідовністю імен функцій. У прикладі, вказано 2 стратегії — прямий послідовності та у зворотному.

Безпосередньо самі функції, як говорилося вище, повинні приймати одне значення. У нашому прикладі, ми вважаємо це за кількість виконуваних операцій серії инкрементирования.

В результаті отримаємо, серію тестів де кожна стратегія тестування буде протестована по 5 разів з кожним обсягом вибірки ([100, 1000, 2000, 3000]).

Клас представлення даних

Зараз давайте докладніше розглянемо простий клас представлення даних.

Приклад класу подання

class TableList extends ViewerAbstract
{
public $view = 'tableList.php';

public function generateData($data){
return $data;
}

public function run($data)
{
$data = $this->generateData($data);
return App::render('/viewer/'.$this->view, compact('data'));
}

public static function model($class = __CLASS__) {
return parent::model($class);
}
}

Клас реалізує абстрактний клас ViewerAbstract.

$view — містить ім'я подання, яке поумолчанию повинно лежати в папці /views/viewers

function generateData($data) — метод обробки масиву результатів тестування. Результат буде переданий поумолчанию в параметр data у зазначене подання.

function run($data) — необов'язковий для реалізації метод, але якщо необхідно змінити шлях до подання даних, то саме він повинен бути змінені.

public static function model($class = __CLASS__) — метод підтримки статичного звернення до методів

Варіанти використання
Щоб скористатися функціоналом тестування, створено 2 основних методи.

Метод запуску тесту

function test($test, $params = [], $onlyData = false

$test — повне ім'я запущеного тесту. Можуть бути задіяні як свої варіанти тестів або заздалегідь підготовлені.

$params — масив параметрів для запуску тесту. Цим параметром можна точково змінити параметри тестування.

$onlyData — параметр, що відповідає за висновок набору результатів або отрендеренное подання.

Метод порівняння функцій

Метод порівняння функцій без створення додаткових класів

function compare($func = [], $params = [], $onlyData = false

$func — масив анонімних функцій, де ключі масиву є назвами функцій в результатах тестування.

$params — масив параметрів тестування, аналогічний параметрами методу test.

$onlyData — також аналогічний параметру методу test, і відповідає за варіант виводу результатів.

Приклади роботи

Самий простий і швидкий варіант використання — це скористатися заздалегідь підготовленим тістом. Всі підготовлені тести винесені в константи класу Speedy.

print Speedy::test(Speedy::PHP_SOF_VS_COUNT);

Для порівняння функцій користувача, необхідно створити анонімні функції і викликати метод Speedy::compare

Приклад порівняння функцій
$pref = function($size)
{
$testCounter = 0;
for($i=0;$i<$size;$i++) {
++$testCounter; ++$testCounter; ++$testCounter; ++$testCounter; ++$testCounter; ++$testCounter;
++$testCounter; ++$testCounter; ++$testCounter; ++$testCounter; ++$testCounter; ++$testCounter;
++$testCounter; ++$testCounter; ++$testCounter; ++$testCounter; ++$testCounter; ++$testCounter;
++$testCounter; ++$testCounter; ++$testCounter; ++$testCounter; ++$testCounter; ++$testCounter;
}
};

$post = function($size)
{
$testCounter = 0;
for($i=0;$i<$size;$i++) {
$testCounter++; $testCounter++; $testCounter++; $testCounter++; $testCounter++; $testCounter++;
$testCounter++; $testCounter++; $testCounter++; $testCounter++; $testCounter++; $testCounter++;
$testCounter++; $testCounter++; $testCounter++; $testCounter++; $testCounter++; $testCounter++;
$testCounter++; $testCounter++; $testCounter++; $testCounter++; $testCounter++; $testCounter++;
}
};
print \speedy\Speedy::compare(['pref' => $pref, 'post' => $post]);

Результати тестування
Результати тестування на даний момент можуть бути виведені 4ма уявленнями.

VIEWER_TLIST

Подання в табличній формі списку всіх проведених замірів часу. По суті це виведення всіх записів із сховища, без яких-небудь перетворень. У наборі даних міститься інформація про імені досліджуваної функції, витраченому часу, обсягу вибірки, використовуваному обсягом пам'яті, номер партії у якій виконувався тест і коментар. У коментарі зазначається в рамках якої стратегії був виконаний тест даної функції.

Приклад виводу результатів списком в табличній форміРезультат тестування операторів инкрементирования.



















































































name time size memory part comment postInc 0.000650882720947 100 48 2 postInc-prefInc prefInc 0.000411987304688 100 48 2 postInc-prefInc prefInc 0.000406980514526 100 48 3 prefInc-postInc postInc 0.000549077987671 100 48 3 prefInc-postInc postInc 0.000330924987793 100 48 5 postInc-prefInc prefInc 0.000287055969238 100 48 5 postInc-prefInc prefInc 0.00043797492981 100 48 6 prefInc-postInc postInc 0.000365018844604 100 48 6 prefInc-postInc postInc 0.000295162200928 100 48 8 postInc-prefInc prefInc 0.000373125076294 100 48 8 postInc-prefInc prefInc 0.000263929367065 100 48 9 prefInc-postInc postInc 0.000449895858765 100 48 9 prefInc-postInc postInc 0.00030517578125 100 48 11 postInc-prefInc prefInc 0.000247955322266 100 48 11 postInc-prefInc prefInc 0.000244140625 100 48 12 prefInc-postInc postInc 0.000265121459961 100 48 12 prefInc-postInc postInc 0.000267028808594 100 48 14 postInc-prefInc prefInc 0.000245094299316 100 48 14 postInc-prefInc prefInc 0.000285148620605 100 48 15 prefInc-postInc postInc 0.000273942947388 100 48 15 prefInc-postInc postInc 0.00273203849792 1000 48 17 postInc-prefInc prefInc 0.00240206718445 1000 48 17 postInc-prefInc prefInc 0.00274896621704 1000 48 18 prefInc-postInc postInc 0.00259804725647 1000 48 18 prefInc-postInc postInc 0.00391817092896 1000 48 20 postInc-prefInc prefInc 0.00303602218628 1000 48 20 postInc-prefInc prefInc 0.00229096412659 1000 48 21 prefInc-postInc postInc 0.00281691551208 1000 48 21 prefInc-postInc postInc 0.00273108482361 1000 48 23 postInc-prefInc prefInc 0.00221014022827 1000 48 23 postInc-prefInc prefInc 0.00266814231873 1000 48 24 prefInc-postInc postInc 0.00307106971741 1000 48 24 prefInc-postInc postInc 0.00283098220825 1000 48 26 postInc-prefInc prefInc 0.00239992141724 1000 48 26 postInc-prefInc prefInc 0.00246214866638 1000 48 27 prefInc-postInc postInc 0.00273704528809 1000 48 27 prefInc-postInc postInc 0.00283288955688 1000 48 29 postInc-prefInc prefInc 0.00229215621948 1000 48 29 postInc-prefInc prefInc 0.00220608711243 1000 48 30 prefInc-postInc postInc 0.0028657913208 1000 48 30 prefInc-postInc postInc 0.00557017326355 2000 48 32 postInc-prefInc prefInc 0.0048840045929 2000 48 32 postInc-prefInc prefInc 0.00449013710022 2000 48 33 prefInc-postInc postInc 0.0064799785614 2000 48 33 prefInc-postInc postInc 0.00543594360352 2000 48 35 postInc-prefInc prefInc 0.00509881973267 2000 48 35 postInc-prefInc prefInc 0.00483298301697 2000 48 36 prefInc-postInc postInc 0.00555992126465 2000 48 36 prefInc-postInc postInc 0.00516104698181 2000 48 38 postInc-prefInc prefInc 0.00512599945068 2000 48 38 postInc-prefInc prefInc 0.00484395027161 2000 48 39 prefInc-postInc postInc 0.00530505180359 2000 48 39 prefInc-postInc postInc 0.00509691238403 2000 48 41 postInc-prefInc prefInc 0.00525093078613 2000 48 41 postInc-prefInc prefInc 0.00447416305542 2000 48 42 prefInc-postInc postInc 0.00536584854126 2000 48 42 prefInc-postInc postInc 0.0054669380188 2000 48 44 postInc-prefInc prefInc 0.00468182563782 2000 48 44 postInc-prefInc prefInc 0.00512504577637 2000 48 45 prefInc-postInc postInc 0.00545692443848 2000 48 45 prefInc-postInc postInc 0.00782418251038 3000 48 47 postInc-prefInc prefInc 0.00726389884949 3000 48 47 postInc-prefInc prefInc 0.00674796104431 3000 48 48 prefInc-postInc postInc 0.007483959198 3000 48 48 prefInc-postInc postInc 0.00781297683716 3000 48 50 postInc-prefInc prefInc 0.0069580078125 3000 48 50 postInc-prefInc prefInc 0.00711393356323 3000 48 51 prefInc-postInc postInc 0.0072808265686 3000 48 51 prefInc-postInc postInc 0.00790119171143 3000 48 53 postInc-prefInc prefInc 0.00662994384766 3000 48 53 postInc-prefInc prefInc 0.00825595855713 3000 48 54 prefInc-postInc postInc 0.00739097595215 3000 48 54 prefInc-postInc postInc 0.00811100006104 3000 48 56 postInc-prefInc prefInc 0.00712990760803 3000 48 56 postInc-prefInc prefInc 0.00698399543762 3000 48 57 prefInc-postInc postInc 0.00758218765259 3000 48 57 prefInc-postInc postInc 0.00795316696167 3000 48 59 postInc-prefInc prefInc 0.00725698471069 3000 48 59 postInc-prefInc prefInc 0.00684094429016 3000 48 60 prefInc-postInc postInc 0.00778198242188 3000 48 60 prefInc-postInc

VIEWER_TGROUP

Подання в табличній формі у вигляді згрупованих даних по партії тестування (за номером проходу по стратегії), тобто в одному рядку виявляться результати тестування функцій які проводилися в рамках однієї стратегії і в рамках одного проходу тесту.

У стовпцях таблиці будуть відображені дані про розмір вибірки, часу виконання, відсотку швидкості від гіршого результату, витраченої пам'яті, коментаря і назви функції, яка стала переможцем по часу серед поточного проходу. Окремо хотілося б пояснити стовпець відсотка. Дане значення вираховується як відсоток, на скільки по часу функція випередила виконання самої повільної функції. Якщо значення не встановлено, значить ця функція по часу виконання є аутсайдером у проході.

Приклад виведення згрупованих результатів в табличній форміРезультат тестування операторів инкрементирования.












































size postInc prefInc comment time win time % memory time % memory 100 0.000650882720947 48 0.000411987304688 36.7 48 postInc-prefInc prefInc 100 0.000549077987671 48 0.000406980514526 25.88 48 prefInc-postInc prefInc 100 0.000330924987793 48 0.000287055969238 13.26 48 postInc-prefInc prefInc 100 0.000365018844604 16.66 48 0.00043797492981 48 prefInc-postInc postInc 100 0.000295162200928 20.89 48 0.000373125076294 48 postInc-prefInc postInc 100 0.000449895858765 48 0.000263929367065 41.34 48 prefInc-postInc prefInc 100 0.00030517578125 48 0.000247955322266 18.75 48 postInc-prefInc prefInc 100 0.000265121459961 48 0.000244140625 7.91 48 prefInc-postInc prefInc 100 0.000267028808594 48 0.000245094299316 8.21 48 postInc-prefInc prefInc 100 0.000273942947388 3.93 48 0.000285148620605 48 prefInc-postInc postInc 1000 0.00273203849792 48 0.00240206718445 12.08 48 postInc-prefInc prefInc 1000 0.00259804725647 5.49 48 0.00274896621704 48 prefInc-postInc postInc 1000 0.00391817092896 48 0.00303602218628 22.51 48 postInc-prefInc prefInc 1000 0.00281691551208 48 0.00229096412659 18.67 48 prefInc-postInc prefInc 1000 0.00273108482361 48 0.00221014022827 19.07 48 postInc-prefInc prefInc 1000 0.00307106971741 48 0.00266814231873 13.12 48 prefInc-postInc prefInc 1000 0.00283098220825 48 0.00239992141724 15.23 48 postInc-prefInc prefInc 1000 0.00273704528809 48 0.00246214866638 10.04 48 prefInc-postInc prefInc 1000 0.00283288955688 48 0.00229215621948 19.09 48 postInc-prefInc prefInc 1000 0.0028657913208 48 0.00220608711243 23.02 48 prefInc-postInc prefInc 2000 0.00557017326355 48 0.0048840045929 12.32 48 postInc-prefInc prefInc 2000 0.0064799785614 48 0.00449013710022 30.71 48 prefInc-postInc prefInc 2000 0.00543594360352 48 0.00509881973267 6.2 48 postInc-prefInc prefInc 2000 0.00555992126465 48 0.00483298301697 13.07 48 prefInc-postInc prefInc 2000 0.00516104698181 48 0.00512599945068 0.68 48 postInc-prefInc prefInc 2000 0.00530505180359 48 0.00484395027161 8.69 48 prefInc-postInc prefInc 2000 0.00509691238403 2.93 48 0.00525093078613 48 postInc-prefInc postInc 2000 0.00536584854126 48 0.00447416305542 16.62 48 prefInc-postInc prefInc 2000 0.0054669380188 48 0.00468182563782 14.36 48 postInc-prefInc prefInc 2000 0.00545692443848 48 0.00512504577637 6.08 48 prefInc-postInc prefInc 3000 0.00782418251038 48 0.00726389884949 7.16 48 postInc-prefInc prefInc 3000 0.007483959198 48 0.00674796104431 9.83 48 prefInc-postInc prefInc 3000 0.00781297683716 48 0.0069580078125 10.94 48 postInc-prefInc prefInc 3000 0.0072808265686 48 0.00711393356323 2.29 48 prefInc-postInc prefInc 3000 0.00790119171143 48 0.00662994384766 16.09 48 postInc-prefInc prefInc 3000 0.00739097595215 10.48 48 0.00825595855713 48 prefInc-postInc postInc 3000 0.00811100006104 48 0.00712990760803 12.1 48 postInc-prefInc prefInc 3000 0.00758218765259 48 0.00698399543762 7.89 48 prefInc-postInc prefInc 3000 0.00795316696167 48 0.00725698471069 8.75 48 postInc-prefInc prefInc 3000 0.00778198242188 48 0.00684094429016 12.09 48 prefInc-postInc prefInc

VIEWER_TAVG

Подання в табличній формі у вигляді усереднених показників по розміру вибірки, тобто в одному рядку виявляться середні показники тестування функцій по одній вибірці.

У стовпцях представлена інформація за кількістю перемог функції у вибірці, середній відсоток часу перемог, ім'я функції-переможця. Функції-переможець — визначається кількістю перемог у вибірці.

Приклад виведення усереднених результатів в табличній форміРезультат тестування операторів инкрементирования.








size postInc prefInc winner winns % winns % 100 3 13.83 7 21.72 prefInc 1000 1 5.49 9 16.98 prefInc 2000 1 2.93 9 12.08 prefInc 3000 1 10.48 9 9.68 prefInc

VIEWER_GBUBLE
Подання в графічній формі у вигляді набору точок, кожна з яких відповідає значенню часу і обсягу вибірки. Дане подання реалізовано за допомогою графіків HighCharts.
Приклад виведення результатів у графічному виглядіРезультат тестування операторів инкрементирования.
image

Підсумки
Підсумувати хотілося б тим, що поставлені цілі були досягнуті. Але, звичайно ж, ще є над чим попрацювати. У планах робота над обробкою виключень, додавання можливості зміни в налаштуваннях набору стовпчиків а також можливість задавати власні імена стовпців, збільшення кількості тестів. Також хотілося б додати можливість зручного тестування швидкості роботи з базами даних.

Вихідний код проекту можна дивитися тут

p.s.: Даний проект розробляється в рамках особистого інтересу до даної теми і не претендує на місце кращого в своєму роді. Буду радий, якщо комусь сподобається і буде корисний цей труд. Конструктивна критика і поради вітаються.
Джерело: Хабрахабр

0 коментарів

Тільки зареєстровані та авторизовані користувачі можуть залишати коментарі.