0
我有一個非常昂貴的方法removeColumns(...)
,此外還有多次調用。所以我想提高它的表現。爲了分析的優化結果我使用兩個工具:(1)Xdebug Profiler與Webgrind和(2)一個簡單的執行時間測量腳本(即一個PHPUnit的測試方法中的命令行上執行):簡單執行時間檢查的結果與Xdebug性能分析結果相矛盾
$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%
正如你所看到的,優化的失敗。當該方法不是經常被調用時,性能會變得更好,但呼叫越多,情況就越糟糕(非線性,在調用100.000
時性能損失高達900%
)。
現在,讓我們看到了Xdebug的探查的結果:
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;
}