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'. -- 'True'.
parseBracketedPaste :: String -> KClass parseBracketedPaste :: String -> KClass
parseBracketedPaste s = parseBracketedPaste s =
let (p, rest) = takeUntil (drop (length bracketedPasteStart) s) bracketedPasteEnd Valid (EvPaste p) (BS8.unpack $ BS8.drop (BS8.length end) rest')
rest' = if bracketedPasteEnd `isPrefixOf` rest where
then drop (length bracketedPasteEnd) rest start = BS8.pack bracketedPasteStart
else rest end = BS8.pack bracketedPasteEnd
in Valid (EvPaste $ BS8.pack p) rest' (_, rest ) = BS8.breakSubstring start . BS8.pack $ s
(p, rest') = BS8.breakSubstring end . BS8.drop (BS8.length start) $ 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)