我有一个非常昂贵的方法removeColumns(...)
,此外还被多次调用。所以我想提高它的性能。为了分析优化结果,我使用了两个工具:(1)带有Webgrind的Xdebug Profiler和 (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;
}