perf: Improve performance for large bracketed pastes.

The current algorithm has polynomial time complexity. The new algorithm
runs in linear time and is generally much more efficient because it
operates on packed byte strings instead of linked lists of Char.

Some timing:
* 100KB: 1 second
* 200KB: 2.5 seconds
* 300KB: 4 seconds
* 400KB: 7 seconds
* 500KB: 12 seconds
* 600KB: 16 seconds
* 700KB: 22 seconds

As we can see, it's still `O(n^2)` overall, probably because of the
calls to `bracketedPasteFinished`. I'll investigate that next. The
constant factor overall is much lower now:
```
Before: 2.866E-6n^3  - 1.784E-4n^2 + 0.114n - 2.622
After:  -1.389E-8n^3 + 5.53E-5n^2  - 1.604E-3n + 0.273
```
This commit is contained in:
iphydf 2022-03-09 18:49:34 +00:00
parent b3504fbcb0
commit 6b8d0639c3
No known key found for this signature in database
GPG Key ID: 3855DBA2D74403C9

View File

@ -34,16 +34,9 @@ bracketedPasteFinished = isInfixOf bracketedPasteEnd
-- 'True'.
parseBracketedPaste :: String -> KClass
parseBracketedPaste s =
let (p, rest) = takeUntil (drop (length bracketedPasteStart) s) bracketedPasteEnd
rest' = if bracketedPasteEnd `isPrefixOf` rest
then drop (length bracketedPasteEnd) rest
else rest
in Valid (EvPaste $ BS8.pack p) rest'
takeUntil :: (Eq a) => [a] -> [a] -> ([a],[a])
takeUntil [] _ = ([], [])
takeUntil cs sub
| length cs < length sub = (cs, [])
| take (length sub) cs == sub = ([], drop (length sub) cs)
| otherwise = let (pre, suf) = takeUntil (tail cs) sub
in (head cs:pre, suf)
Valid (EvPaste p) (BS8.unpack $ BS8.drop (BS8.length end) rest')
where
start = BS8.pack bracketedPasteStart
end = BS8.pack bracketedPasteEnd
(_, rest ) = BS8.breakSubstring start . BS8.pack $ s
(p, rest') = BS8.breakSubstring end . BS8.drop (BS8.length start) $ rest