Результаты простой проверки времени выполнения противоречат результатам профилирования Xdebug

У меня очень дорогой метод removeColumns(...), что в дополнение вызывается несколько раз. Поэтому я хочу увеличить его производительность. Для анализа результатов оптимизации я использую два инструмента: (1) Xdebug Profiler с Webgrind и (2) простой сценарий измерения времени выполнения (который выполняется в командной строке в тестовом методе PHPUnit):

$timeStart = microtime(true);
for ($i=0 ; $i < 1000000; $i++) {
// code to measure
$this->...->removeColumns($testArray, $columnNames, $isWhitelist);
}
$timeStop = microtime(true);
$resultTime = $timeStop - $timeStart;
$cycleTime = $resultTime / $i;
echo number_format($cycleTime, 10, ',', '') . ' sec/run';
die(PHP_EOL . '###' . PHP_EOL);

Но теперь я смотрю на результаты — и вижу, что результаты обоих тоже абсолютно противоречат друг другу.

Результаты выполнения скрипта измерения времени:

variant     sec/run (x69)       sec/run (x1000)     sec/run (x10000)    sec/run (x100000)
1           0,0000121144        0,0000102139        0,0000092316        0,0000089004
2           0,0000115650        0,0000112779        0,0000098540        0,0000098941
3           0,0000228260        0,0000240171        0,0000250236        0,0000800230

difference ms (1-2)     0,0000005494    -0,0000010640   -0,0000006224   -0,0000009937
yield % (1-2)           4,54%           -10,42%         -6,74%          -11,16%
difference ms (1-3)     -0,0000107116   -0,0000138032   -0,0000157920   -0,0000711226
yield % (1-3)           -88,42%         -135,14%        -171,06%        -799,09%

Как видите, оптимизация не удалась. Когда метод вызывается не очень часто, производительность становится лучше, но чем больше вызовов, тем хуже (нелинейный, до 900% потери производительности на 100.000 звонки).

Теперь давайте посмотрим результаты Xdebug Profiler:

variant XDP-filename    XDP-filesize    Calls   Total Self (ms) Total Inclusive (ms)
1       1474536556      445,678 KB      69      77325           77403
2       1474537523      402,208 KB      69      1267            1270
3       1474539908      402,963 KB      69      2443            2455

difference ms (1-2)                             76058           76133
yield % (1-2)                                   98,36%          98,36%
difference ms (1-3)                             74882           74948
yield % (1-3)                                   96,84%          96,83%

Так что тут производительность улучшенных вариантов (2 а также 3) значительно лучше, чем variant 1,

Что здесь не так и как это исправить, чтобы получить адекватные результаты тестирования производительности?


Все три варианта метода я оптимизирую:

вариант 1

public function removeColumns(array $table, array $columnNames, bool $isWhitelist = false)
{
foreach ($table as $rowKey => $row) {
if (is_array($row)) {
foreach ($row as $fieldName => $fieldValue) {
$remove = $isWhitelist
? ! in_array($fieldName, $columnNames)
: in_array($fieldName, $columnNames)
;
if ($remove) {
unset($table[$rowKey][$fieldName]);
}
}
}
}
return $table;
}

вариант 2

public function removeColumns(array $table, array $columnNames, bool $isWhitelist = false)
{
$tableKeys = array_keys($table);
$firstRowKey = $tableKeys[0];
$firstRow = $table[$firstRowKey];
$allColumnNames = array_keys($firstRow);
$resultColumns = [];
foreach ($allColumnNames as $columnName) {
$remain = $isWhitelist
? in_array($columnName, $columnNames)
: ! in_array($columnName, $columnNames)
;
if($remain) {
$resultColumns[$columnName] = array_column($table, $columnName);
}
}
$index = 0;
$resultTable = [];
foreach ($resultColumns as $resultColumnName => $resultColumn) {
foreach ($tableKeys as $index => $tableKey) {
$resultTable[$tableKey][$resultColumnName] = $resultColumn[$index];
}
}
return $resultTable;
}

вариант 3

public function removeColumns(array $table, array $columnNames, bool $isWhitelist = false)
{
$tableKeys = array_keys($table);
$firstRowKey = $tableKeys[0];
$firstRow = $table[$firstRowKey];
$allColumnNames = array_keys($firstRow);
$columns = [];
$i = 0;
$arrayMapInputVarNames = [];
foreach ($allColumnNames as $columnName) {
$remain =
($isWhitelist && in_array($columnName, $columnNames)) ||
(! $isWhitelist && ! in_array($columnName, $columnNames))
;
if($remain) {
$varName = 'column' . $i++;
$$varName = $columns[$columnName] = array_column($table, $columnName);
$arrayMapInputVarNames[] = '$' . $varName;
}
}
$arrayMapInputString = implode(', ', $arrayMapInputVarNames);
eval('$rows = array_map(null, ' . $arrayMapInputString . ');');
foreach ($rows as $index => $row) {
$rows[$index] = array_combine(array_keys($columns), array_values($row));
}
$table = array_combine(array_keys($table), $rows);
return $table;
}

0

Решение

это то, что ваша функция намеревалась сделать?

<?php
$table=array(
'col1'=>'val1',
'col2'=>'val2',
'col3'=>'val3',
'col4'=>'val4'
);

$columnNames=array(
'col2','col3'
);

function removeColumns($table, $columnNames, $isWhitelist = false) {
if ($isWhitelist) return array_intersect_key($table,array_flip($columnNames));
return array_diff_key($table,array_flip($columnNames));
}

print 'blacklist:'.var_export(removeColumns($table,$columnNames,false),1).PHP_EOL;
print 'whitelist:'.var_export(removeColumns($table,$columnNames,true),1).PHP_EOL;

выход:

blacklist:array (
'col1' => 'val1',
'col4' => 'val4',
)
whitelist:array (
'col2' => 'val2',
'col3' => 'val3',
)

но я не измерял производительность. Вы могли бы загрузить вывод xdebug своего кода где-нибудь?

0

Другие решения

Других решений пока нет …

По вопросам рекламы [email protected]