I recommend giving this a test drive before merging.
518fe41 (Fraser Tweedale, 17 minutes ago)
improve substring search highlight performance
Move away from the markup implementation we adopted from Brick to something
hand-rolled and specialised to our needs. The new implementation is hugely
faster. This is mainly due to avoiding
`String`, but a O(n²) (n ~ line length) slowdown in the Brick
implementation did not help either.
The profiling results below are on a user scenario of opening a very large
mail, performing a substring search where there are ~20 results, and
advancing through the first 11 matches (i.e. press `n` 10 times).
Typical output before this change:
``` total time = 1.42 secs (1423 ticks @ 1000 us, 1 processor)
total alloc = 3,449,428,704 bytes (excludes profiling overheads)
COST CENTRE MODULE SRC
%time %alloc
markupToList Data.Text.Markup
src/Data/Text/Markup.hs:(73,1)-(83,72) 59.9 80.3 vBox
Brick.Widgets.Core
src/Brick/Widgets/Core.hs:(481,1)-(483,41) 2.8 2.6 markup
Purebred.Brick.Markup
src/Purebred/Brick/Markup.hs:(49,1)-(62,55) 2.8 1.0
safeWctwidth Graphics.Text.Width
src/Graphics/Text/Width.hs:59:1-53 2.5 1.2 hBox
Brick.Widgets.Core
src/Brick/Widgets/Core.hs:(491,1)-(493,41) 2.3 0.2
Allocation was reduced by an order of magnitude, and CPU time halved.
str and txt from Brick are now among the top cost centres for both
resources. Together they account for over 30% of CPU and nearly 50% of
allocation. We can focus future optimisation efforts there.
08c7c81 (Fraser Tweedale, 23 hours ago)
add uniform BodyPresentation type
Overview
Add the BodyPresentation type. Arbitrary data types could be lifted into
a BodyPresentation. The BodyPresentation defines, in a
"source-specific" way, how to perform substring searches, how to build the
widget, and computes the scroll offsets on behalf of the UI.
Put another way, the UI sees all BodyPresentation values as the same,
even though they may be very different "Under the hood". For the current
time, we unconditionally construct a MailBody (as before) and lift it
into a BodyPresentation (new shiny). Later we can implement
BodyPresentation for other "source" types, e.g. HTML or Pandoc. The
configuration interfaces to handle conversion and converter selection are
not yet worked out, but this commit establishes the presentation interface.
The core types of the machinery are defined in
Purebred.Types.Presentation. The MailBody-specific implementation is
in Purebred.Types.Presentation.MailBody. Existing MailBody types and
functions were gathered here from around the codebase.
Use of render cache
The "scroll to match" behaviour is no longer implemented via scroll
requests generated by Actions. The relevant actions now merely update the
match index in the program state. Scrolling is accomplished via Brick
"visibility requests" generated while composing the UI.
This requires use of the render cache. Without the render cache, Brick
"sees" the visibility requests at all times, which "clamps" the scrollable
bounds to those containing the region requested to be visible. However,
the render cache does not "remember" visibility requests. (This is by
design; see Brick's Changelog.) By using
cached, the "inner" widget's visibility request (if any) causes the
"scroll to match" on the initial render only. The user is then free to
scroll anywhere in the viewport.
We invalidate the render cache on the following events:
Search term applied, changed, or cleared
Match index changed (i.e. cycling through matches)
MailView entity changes
The upsides of this implementation detail are several:
Simpler Action code. We no longer need to peek into application
state to emit scroll requests.
The scroll offset is produced by the BodyPresentation; the
behaviour is no longer coupled to a specific data type.
Performance improvements by virtue of render cache. Previously
even a mere scroll up/down would cause the whole entity to be
re-rendered.
Tests
Several MailBody functions are no longer exported. There were a couple
of basic tests for parsing and substring search. I removed these. We will
instead rely on the UAT suite.
5b9cffb (Fraser Tweedale, 26 hours ago)
extract brick attributes to separate module
I recommend giving this a test drive before merging.
And after:
Allocation was reduced by an order of magnitude, and CPU time halved.
str
andtxt
from Brick are now among the top cost centres for both resources. Together they account for over 30% of CPU and nearly 50% of allocation. We can focus future optimisation efforts there.08c7c81 (Fraser Tweedale, 23 hours ago) add uniform BodyPresentation type
Overview
Add the
BodyPresentation
type. Arbitrary data types could be lifted into aBodyPresentation
. TheBodyPresentation
defines, in a "source-specific" way, how to perform substring searches, how to build the widget, and computes the scroll offsets on behalf of the UI.Put another way, the UI sees all
BodyPresentation
values as the same, even though they may be very different "Under the hood". For the current time, we unconditionally construct aMailBody
(as before) and lift it into aBodyPresentation
(new shiny). Later we can implementBodyPresentation
for other "source" types, e.g. HTML or Pandoc. The configuration interfaces to handle conversion and converter selection are not yet worked out, but this commit establishes the presentation interface.The core types of the machinery are defined in
Purebred.Types.Presentation
. TheMailBody
-specific implementation is inPurebred.Types.Presentation.MailBody
. ExistingMailBody
types and functions were gathered here from around the codebase.Use of render cache
The "scroll to match" behaviour is no longer implemented via scroll requests generated by Actions. The relevant actions now merely update the match index in the program state. Scrolling is accomplished via Brick "visibility requests" generated while composing the UI.
This requires use of the render cache. Without the render cache, Brick "sees" the visibility requests at all times, which "clamps" the scrollable bounds to those containing the region requested to be visible. However, the render cache does not "remember" visibility requests. (This is by design; see Brick's Changelog.) By using
cached
, the "inner" widget's visibility request (if any) causes the "scroll to match" on the initial render only. The user is then free to scroll anywhere in the viewport.We invalidate the render cache on the following events:
Search term applied, changed, or cleared
Match index changed (i.e. cycling through matches)
MailView entity changes
The upsides of this implementation detail are several:
Simpler Action code. We no longer need to peek into application state to emit scroll requests.
The scroll offset is produced by the
BodyPresentation
; the behaviour is no longer coupled to a specific data type.Performance improvements by virtue of render cache. Previously even a mere scroll up/down would cause the whole entity to be re-rendered.
Tests
Several
MailBody
functions are no longer exported. There were a couple of basic tests for parsing and substring search. I removed these. We will instead rely on the UAT suite.5b9cffb (Fraser Tweedale, 26 hours ago) extract brick attributes to separate module