on master help sign in
perf: Process chunks of bracketed pastes only once.

The current code is `O(n^2)` because it processes the entire input each time a new chunk arrives. This gets very expensive for large inputs. This commit changes the code to process each chunk exactly twice: once to see if it contains the end of the bracketed paste, and another time to parse the whole paste and return it as an event. The code can now process more than 1MB/sec. At this point, my tests are probably more network bound, so these timings might be off, and instead be testing my network throughput. Good enough for me anyway :). Timing: * 1MB: 1s * 4MB: 3s * 7MB: 6s * 10MB: 7s * 13MB: 10s * 15MB: 12s * 20MB: 16s

commit547ab196fc14761cdbeec11760629cf32efd2017 authoriphydf <iphydf@users.noreply.github.com> dateWed 9 Mar 2022, 23:38 (5 years ago) parent98b2a83
3 files changed+55−18
src/Graphics/Vty/Input/Classify.hs+32−11 src/Graphics/Vty/Input/Classify/Types.hs+3 src/Graphics/Vty/Input/Loop.hs+20−7
src/Graphics/Vty/Input/Classify.hs+32−11
@@ -5,6 +5,7 @@
55module Graphics.Vty.Input.Classify
66 ( classify
77 , KClass(..)
8+ , ClassifierState(..)
89 )
910where
1011
@@ -27,6 +28,17 @@ import qualified Data.ByteString as BS
2728import qualified Data.ByteString.Char8 as BS8
2829import Data.ByteString.Char8 (ByteString)
2930
31+-- | Whether the classifier is currently processing a chunked format.
32+-- Currently, only bracketed pastes use this.
33+data ClassifierState
34+ = ClassifierStart
35+ -- ^ Not processing a chunked format.
36+ | ClassifierInChunk ByteString [ByteString]
37+ -- ^ Currently processing a chunked format. The initial chunk is in the
38+ -- first argument and a reversed remainder of the chunks is collected in
39+ -- the second argument. At the end of the processing, the chunks are
40+ -- reversed and concatenated with the final chunk.
41+
3042compile :: ClassifyMap -> ByteString -> KClass
3143compile table = cl' where
3244 -- take all prefixes and create a set of these
@@ -54,18 +66,27 @@ compile table = cl' where
5466 -- neither a prefix or a full event.
5567 [] -> Invalid
5668
57−classify :: ClassifyMap -> ByteString -> KClass
58−classify table =
59− let standardClassifier = compile table
60− in \s -> case BS.uncons s of
61− _ | bracketedPasteStarted s ->
69+classify :: ClassifyMap -> ClassifierState -> ByteString -> KClass
70+classify table = process
71+ where
72+ standardClassifier = compile table
73+
74+ process ClassifierStart s =
75+ case BS.uncons s of
76+ _ | bracketedPasteStarted s ->
77+ if bracketedPasteFinished s
78+ then parseBracketedPaste s
79+ else Chunk
80+ _ | isMouseEvent s -> classifyMouseEvent s
81+ _ | isFocusEvent s -> classifyFocusEvent s
82+ Just (c,cs) | c >= 0xC2 -> classifyUtf8 c cs
83+ _ -> standardClassifier s
84+
85+ process (ClassifierInChunk p ps) s | bracketedPasteStarted p =
6286 if bracketedPasteFinished s
63− then parseBracketedPaste s
64− else Prefix
65− _ | isMouseEvent s -> classifyMouseEvent s
66− _ | isFocusEvent s -> classifyFocusEvent s
67− Just (c,cs) | c >= 0xC2 -> classifyUtf8 c cs
68− _ -> standardClassifier s
87+ then parseBracketedPaste $ BS.concat $ p:reverse (s:ps)
88+ else Chunk
89+ process ClassifierInChunk{} _ = Invalid
6990
7091classifyUtf8 :: Word8 -> ByteString -> KClass
7192classifyUtf8 c cs =
src/Graphics/Vty/Input/Classify/Types.hs+3
@@ -19,4 +19,7 @@ data KClass
1919 | Prefix
2020 -- ^ The input characters form the prefix of a valid event character
2121 -- sequence.
22+ | Chunk
23+ -- ^ The input characters are either start of a bracketed paste chunk
24+ -- or in the middle of a bracketed paste chunk.
2225 deriving(Show, Eq)
src/Graphics/Vty/Input/Loop.hs+20−7
@@ -80,9 +80,10 @@ makeLenses ''InputBuffer
8080
8181data InputState = InputState
8282 { _unprocessedBytes :: ByteString
83+ , _classifierState :: ClassifierState
8384 , _appliedConfig :: Config
8485 , _inputBuffer :: InputBuffer
85− , _classifier :: ByteString -> KClass
86+ , _classifier :: ClassifierState -> ByteString -> KClass
8687 }
8788
8889makeLenses ''InputState
@@ -154,28 +155,40 @@ applyConfig _ _ = fail "(vty) applyConfig was not provided a complete configurat
154155parseEvent :: InputM Event
155156parseEvent = do
156157 c <- use classifier
158+ s <- use classifierState
157159 b <- use unprocessedBytes
158− case c b of
160+ case c s b of
159161 Valid e remaining -> do
160162 logMsg $ "valid parse: " ++ show e
161163 logMsg $ "remaining: " ++ show remaining
164+ classifierState .= ClassifierStart
162165 unprocessedBytes .= remaining
163166 return e
164− _ -> mzero
167+ _ -> mzero
165168
166169dropInvalid :: InputM ()
167170dropInvalid = do
168171 c <- use classifier
172+ s <- use classifierState
169173 b <- use unprocessedBytes
170− when (c b == Invalid) $ do
171− logMsg "dropping input bytes"
172− unprocessedBytes .= BS8.empty
174+ case c s b of
175+ Chunk -> do
176+ classifierState .=
177+ case s of
178+ ClassifierStart -> ClassifierInChunk b []
179+ ClassifierInChunk p bs -> ClassifierInChunk p (b:bs)
180+ unprocessedBytes .= BS8.empty
181+ Invalid -> do
182+ logMsg "dropping input bytes"
183+ classifierState .= ClassifierStart
184+ unprocessedBytes .= BS8.empty
185+ _ -> return ()
173186
174187runInputProcessorLoop :: ClassifyMap -> Input -> IO ()
175188runInputProcessorLoop classifyTable input = do
176189 let bufferSize = 1024
177190 allocaArray bufferSize $ \(bufferPtr :: Ptr Word8) -> do
178− s0 <- InputState BS8.empty
191+ s0 <- InputState BS8.empty ClassifierStart
179192 <$> readIORef (_configRef input)
180193 <*> pure (InputBuffer bufferPtr bufferSize)
181194 <*> pure (classify classifyTable)