From 9b919186abb7d4fc1e3917ebd65fc361d0b8ddf8 Mon Sep 17 00:00:00 2001 From: JakeQZ Date: Sat, 29 Jun 2024 08:22:55 +0100 Subject: [PATCH] [TASK] Avoid poor scaling of array_search() with very long arrays (#628) When there were many many elements in `$aStack`, starting the delimiter search from the beginning for each loop iteration was very slow. This is addressed by building a new array, rather than modifying `$aStack` in place, and iterating over it in a single pass. A particular 1.6M style string is now parsed in 5 seconds rather than 4 minutes. Co-authored-by: Bart Butler --- CHANGELOG.md | 2 ++ src/Value/Value.php | 19 +++++++++++++------ 2 files changed, 15 insertions(+), 6 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 3f293533..e29397aa 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,6 +12,8 @@ This project adheres to [Semantic Versioning](https://semver.org/). ### Changed +- Improve performance of Value::parseValue with many delimiters by refactoring to remove array_search() (#413) + ### Deprecated ### Removed diff --git a/src/Value/Value.php b/src/Value/Value.php index ce6d5790..6be2110c 100644 --- a/src/Value/Value.php +++ b/src/Value/Value.php @@ -67,23 +67,30 @@ public static function parseValue(ParserState $oParserState, array $aListDelimit } // Convert the list to list objects foreach ($aListDelimiters as $sDelimiter) { - if (count($aStack) === 1) { + $iStackLength = count($aStack); + if ($iStackLength === 1) { return $aStack[0]; } - $iStartPosition = null; - while (($iStartPosition = array_search($sDelimiter, $aStack, true)) !== false) { + $aNewStack = []; + for ($iStartPosition = 0; $iStartPosition < $iStackLength; ++$iStartPosition) { + if ($iStartPosition === ($iStackLength - 1) || $sDelimiter !== $aStack[$iStartPosition + 1]) { + $aNewStack[] = $aStack[$iStartPosition]; + continue; + } $iLength = 2; //Number of elements to be joined - for ($i = $iStartPosition + 2; $i < count($aStack); $i += 2, ++$iLength) { + for ($i = $iStartPosition + 3; $i < $iStackLength; $i += 2, ++$iLength) { if ($sDelimiter !== $aStack[$i]) { break; } } $oList = new RuleValueList($sDelimiter, $oParserState->currentLine()); - for ($i = $iStartPosition - 1; $i - $iStartPosition + 1 < $iLength * 2; $i += 2) { + for ($i = $iStartPosition; $i - $iStartPosition < $iLength * 2; $i += 2) { $oList->addListComponent($aStack[$i]); } - array_splice($aStack, $iStartPosition - 1, $iLength * 2 - 1, [$oList]); + $aNewStack[] = $oList; + $iStartPosition += $iLength * 2 - 2; } + $aStack = $aNewStack; } if (!isset($aStack[0])) { throw new UnexpectedTokenException(