purebred-mua / purebred

A terminal based mail user agent based on notmuch
GNU Affero General Public License v3.0
139 stars 19 forks source link

Add BodyPresentation; improve highlight perf #491

Closed frasertweedale closed 1 year ago

frasertweedale commented 1 year ago

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

And after:

   total alloc = 798,243,880 bytes  (excludes profiling overheads)

   COST CENTRE         MODULE                               SRC               
                                     %time %alloc

   str                 Brick.Widgets.Core                  
   src/Brick/Widgets/Core.hs:(341,1)-(356,70)            25.4   34.2 vBox     
             Brick.Widgets.Core                  
   src/Brick/Widgets/Core.hs:(481,1)-(483,41)             7.6   11.1 
   safeWcswidth        Graphics.Text.Width                 
   src/Graphics/Text/Width.hs:54:1-51                     7.5    2.7 txt      
             Brick.Widgets.Core                  
   src/Brick/Widgets/Core.hs:367:1-20                     6.5   12.6 crop     
             Brick.BorderMap                     
   src/Brick/BorderMap.hs:(189,1)-(195,9)                 5.2    3.7

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:

5b9cffb (Fraser Tweedale, 26 hours ago) extract brick attributes to separate module