]> git.vanrenterghem.biz Git - git.ikiwiki.info.git/blob - doc/todo/Improving_the_efficiency_of_match__95__glob.mdwn
avoid an unnecessary hash lookup
[git.ikiwiki.info.git] / doc / todo / Improving_the_efficiency_of_match__95__glob.mdwn
1 [[!template id=gitbranch branch=smcv/ready/glob-cache
2   author="[[KathrynAndersen]], [[smcv]]"]]
3 [[!tag patch]]
5 I've been profiling my IkiWiki to try to improve speed (with many pages makes speed even more important) and I've written a patch to improve the speed of match_glob.  This matcher is a good one to improve the speed of, because it gets called so many times.
7 Here's my patch - please consider it! -- [[KathrynAndersen]]
9 > It seems to me as though changing `glob2re` to return qr/$re/, and calling
10 > `memoize(glob2re)` next to the other memoize calls, would be a less
11 > verbose way to do this? --[[smcv]]
13 >> I think so, yeah. Anyway, do you have any benchmark results handy,
14 >> Kathryn?  --[[Joey]] 
16 >>> See below.
17 >>> Also, would it make more sense for glob2re to return qr/^$re$/i rather than qr/$re/?  Everything that uses glob2re seems to use
18         $foo =~ /^$re$/i
19 >>> rather than /$re/ so I think that would make sense.
20 >>> -- [[KathrynAndersen]]
22 >>>> Git branch `smcv/ka-glob-cache` has Kathryn's patch. Git
23 >>>> branch `smcv/memoize-glob2re` does as I suggested, which
24 >>>> is less verbose than Kathryn's patch but also not as
25 >>>> fast; I'm not sure why, tbh. --[[smcv]]
27 >>>>> I think it's because my patch focuses on match_glob while the memoize patch focuses on `glob2re`, and `glob2re` is called in `filecheck`, `meta` and `po` as well as in `match_glob` and `match_user`; thus the memoized `glob2re` is dealing with a bigger set of globs to look up, and thus could be just that little bit slower. -- [[KathrynAndersen]]
29 >>>>>> What may be going on is that glob2re is already a fairly fast
30 >>>>>> function, so the overhead of memoizing it with the very generic
31 >>>>>> `_memoizer` (see its source) swamps the memoization gain. Note
32 >>>>>> that the few functions memoized with the Memoizer before were much
33 >>>>>> more expensive, so that little overhead was acceptable then.
34 >>>>>>
35 >>>>>> It also may be that Kathryn's patch is slightly faster due to using
36 >>>>>> the construct `$foo =~ $regexp` rather than `$foo =~ /$regexp/`
37 >>>>>> (probably avoids a copy or something like that internally) --
38 >>>>>> this despite checking both `exists` and `defined` on the hash, which
39 >>>>>> should be reundant AFAICS.
40 >>>>>>
41 >>>>>> My guess is that the best of both worlds would be to move
42 >>>>>> the byhand memoization to glob2re and have it return a compiled
43 >>>>>> `/^/i` regexp that can be used without further modifiction in most
44 >>>>>> cases. --[[Joey]] 
46 >>>>>>> Done, see `smcv/ready/glob-cache` and `smcv/glob-cache-too-far`.
47 >>>>>>>
48 >>>>>>> Kathryn's patch is a significant improvement; my first patch on top of
49 >>>>>>> that is a trivial cleanup that speeds it up a little, and the next two
50 >>>>>>> patches (using precompiled regexes) have surprisingly little effect
51 >>>>>>> (they don't slow it down either though, so either omit them or merge
52 >>>>>>> them, whichever). Detailed benchmark results below.
53 >>>>>>>
54 >>>>>>> Moving the memoization to `glob2re` actually seems to slow things down
55 >>>>>>> again - I suspect the docwiki has few enough mentions of `user()` etc.
56 >>>>>>> that caching them is a waste of time, but perhaps it's not the most
57 >>>>>>> representative.
58 >>>>>>> --[[smcv]]
60 --------------------------------------------------------------
62 [[!toggle id="smcv-benchmark" text="current benchmarks"]]
64 [[!toggleable id="smcv-benchmark" text="""
65 master at time of branch:
67     time elapsed (wall):   29.6348
68     time running program:  24.9212  (84.09%)
69     time profiling (est.): 4.7136  (15.91%)
70     number of calls:       1360181
71     number of exceptions:  13
72     
73     %Time    Sec.     #calls   sec/call  F  name
74     13.24    3.2986     3408   0.000968     Text::Balanced::_match_tagged
75     10.94    2.7253    79514   0.000034     IkiWiki::PageSpec::match_glob
76      3.19    0.7952    59454   0.000013     <anon>:IkiWiki/Plugin/inline.pm:223
78 `Improve the speed of match_glob`:
80     time elapsed (wall):   27.9755
81     time running program:  23.5293  (84.11%)
82     time profiling (est.): 4.4461  (15.89%)
83     number of calls:       1280875
84     number of exceptions:  13
85     
86     %Time    Sec.     #calls   sec/call  F  name
87     14.56    3.4257     3408   0.001005     Text::Balanced::_match_tagged
88      7.82    1.8403    79514   0.000023     IkiWiki::PageSpec::match_glob
89      3.27    0.7698    59454   0.000013     <anon>:IkiWiki/Plugin/inline.pm:223
91 `match_glob: streamline glob cache slightly`:
93     time elapsed (wall):   27.5753
94     time running program:  23.1714  (84.03%)
95     time profiling (est.): 4.4039  (15.97%)
96     number of calls:       1280875
97     number of exceptions:  13
98     
99     %Time    Sec.     #calls   sec/call  F  name
100     14.09    3.2637     3408   0.000958     Text::Balanced::_match_tagged
101      7.74    1.7926    79514   0.000023     IkiWiki::PageSpec::match_glob
102      3.30    0.7646    59454   0.000013     <anon>:IkiWiki/Plugin/inline.pm:223
104 `glob2re: return a precompiled, anchored case-insensitiv...`:
106     time elapsed (wall):   27.5656
107     time running program:  23.1464  (83.97%)
108     time profiling (est.): 4.4192  (16.03%)
109     number of calls:       1282189
110     number of exceptions:  13
111     
112     %Time    Sec.     #calls   sec/call  F  name
113     14.21    3.2891     3408   0.000965     Text::Balanced::_match_tagged
114      7.72    1.7872    79514   0.000022     IkiWiki::PageSpec::match_glob
115      3.32    0.7678    59454   0.000013     <anon>:IkiWiki/Plugin/inline.pm:223
117 `make use of precompiled regex objects`:
119     time elapsed (wall):   27.5357
120     time running program:  23.1289  (84.00%)
121     time profiling (est.): 4.4068  (16.00%)
122     number of calls:       1281981
123     number of exceptions:  13
124     
125     %Time    Sec.     #calls   sec/call  F  name
126     14.17    3.2776     3408   0.000962     Text::Balanced::_match_tagged
127      7.70    1.7814    79514   0.000022     IkiWiki::PageSpec::match_glob
128      3.35    0.7756    59454   0.000013     <anon>:IkiWiki/Plugin/inline.pm:223
130 `move memoization from match_glob to glob2re`:
132     time elapsed (wall):   28.7677
133     time running program:  23.9473  (83.24%)
134     time profiling (est.): 4.8205  (16.76%)
135     number of calls:       1360181
136     number of exceptions:  13
137     
138     %Time    Sec.     #calls   sec/call  F  name
139     13.98    3.3469     3408   0.000982     Text::Balanced::_match_tagged
140      8.85    2.1194    79514   0.000027     IkiWiki::PageSpec::match_glob
141      3.24    0.7750    59454   0.000013     <anon>:IkiWiki/Plugin/inline.pm:223
143 --[[smcv]]
144 """]]
146 --------------------------------------------------------------
148 [[!toggle id="ka-benchmarks" text="Kathryn's benchmarks"]]
150 [[!toggleable id="ka-benchmarks" text="""
151 Benchmarks done with Devel::Profile on the same testbed IkiWiki setup.  I'm just showing the start of the profile output, since that's what's relevant.
153 Before:
154 <pre>
155 time elapsed (wall):   27.4173
156 time running program:  22.5909  (82.40%)
157 time profiling (est.): 4.8264  (17.60%)
158 number of calls:       1314729
159 number of exceptions:  65
161 %Time    Sec.     #calls   sec/call  F  name
162 11.05    2.4969    62333   0.000040     IkiWiki::PageSpec::match_glob
163  4.10    0.9261      679   0.001364     Text::Balanced::_match_tagged
164  2.72    0.6139    59812   0.000010     IkiWiki::SuccessReason::merge_influences
165 </pre>
167 After:
168 <pre>
169 time elapsed (wall):   26.1843
170 time running program:  21.5673  (82.37%)
171 time profiling (est.): 4.6170  (17.63%)
172 number of calls:       1252433
173 number of exceptions:  65
175 %Time    Sec.     #calls   sec/call  F  name
176  7.66    1.6521    62333   0.000027     IkiWiki::PageSpec::match_glob
177  4.33    0.9336      679   0.001375     Text::Balanced::_match_tagged
178  2.81    0.6057    59812   0.000010     IkiWiki::SuccessReason::merge_influences
179 </pre>
181 Note that the seconds per call for match_glob in the "after" case has gone down by about a third.
183 K.A.
184 """]]
186 --------------------------------------------------------------
188 [[!toggle id="ka-patch" text="Kathryn's original patch"]]
190 [[!toggleable id="ka-patch" text="""
192 <pre>
193 diff --git a/IkiWiki.pm b/IkiWiki.pm
194 index 08a3d78..c187b98 100644
195 --- a/IkiWiki.pm
196 +++ b/IkiWiki.pm
197 @@ -2482,6 +2482,8 @@ sub derel ($$) {
198         return $path;
199  }
200  
201 +my %glob_cache;
203  sub match_glob ($$;@) {
204         my $page=shift;
205         my $glob=shift;
206 @@ -2489,8 +2491,15 @@ sub match_glob ($$;@) {
207         
208         $glob=derel($glob, $params{location});
209  
210 -       my $regexp=IkiWiki::glob2re($glob);
211 -       if ($page=~/^$regexp$/i) {
212 +       # Instead of converting the glob to a regex every time,
213 +       # cache the compiled regex to save time.
214 +       if (!exists $glob_cache{$glob}
215 +           or !defined $glob_cache{$glob})
216 +       {
217 +           my $re=IkiWiki::glob2re($glob);
218 +           $glob_cache{$glob} = qr/^$re$/i;
219 +       }
220 +       if ($page =~ $glob_cache{$glob}) {
221                 if (! IkiWiki::isinternal($page) || $params{internal}) {
222                         return IkiWiki::SuccessReason->new("$glob matches $page");
223                 }
224 </pre>
225 """]]
226 --------------------------------------------------------------