1 [[!template id=gitbranch branch=smcv/ready/glob-cache
2 author="[[KathrynAndersen]], [[smcv]]"]]
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]]
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
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.
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.
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`.
47 >>>>>>> Kathryn's patch is a significant improvement; my first patch on top of
48 >>>>>>> that is a trivial cleanup that speeds it up a little, and the other two
49 >>>>>>> patches (using precompiled regexes) have surprisingly little effect
50 >>>>>>> (they don't slow it down either though, so either omit them or merge
51 >>>>>>> them, whichever). Detailed benchmark results --[[smcv]]
53 --------------------------------------------------------------
55 [[!toggle id="smcv-benchmark" text="current benchmarks"]]
57 [[!toggleable id="smcv-benchmark" text="""
58 master at time of branch:
60 time elapsed (wall): 29.6348
61 time running program: 24.9212 (84.09%)
62 time profiling (est.): 4.7136 (15.91%)
63 number of calls: 1360181
64 number of exceptions: 13
66 %Time Sec. #calls sec/call F name
67 13.24 3.2986 3408 0.000968 Text::Balanced::_match_tagged
68 10.94 2.7253 79514 0.000034 IkiWiki::PageSpec::match_glob
69 3.19 0.7952 59454 0.000013 <anon>:IkiWiki/Plugin/inline.pm:223
71 `Improve the speed of match_glob`:
73 time elapsed (wall): 27.9755
74 time running program: 23.5293 (84.11%)
75 time profiling (est.): 4.4461 (15.89%)
76 number of calls: 1280875
77 number of exceptions: 13
79 %Time Sec. #calls sec/call F name
80 14.56 3.4257 3408 0.001005 Text::Balanced::_match_tagged
81 7.82 1.8403 79514 0.000023 IkiWiki::PageSpec::match_glob
82 3.27 0.7698 59454 0.000013 <anon>:IkiWiki/Plugin/inline.pm:223
84 `match_glob: streamline glob cache slightly`:
86 time elapsed (wall): 27.5753
87 time running program: 23.1714 (84.03%)
88 time profiling (est.): 4.4039 (15.97%)
89 number of calls: 1280875
90 number of exceptions: 13
92 %Time Sec. #calls sec/call F name
93 14.09 3.2637 3408 0.000958 Text::Balanced::_match_tagged
94 7.74 1.7926 79514 0.000023 IkiWiki::PageSpec::match_glob
95 3.30 0.7646 59454 0.000013 <anon>:IkiWiki/Plugin/inline.pm:223
97 `glob2re: return a precompiled, anchored case-insensitiv...`:
99 time elapsed (wall): 27.5656
100 time running program: 23.1464 (83.97%)
101 time profiling (est.): 4.4192 (16.03%)
102 number of calls: 1282189
103 number of exceptions: 13
105 %Time Sec. #calls sec/call F name
106 14.21 3.2891 3408 0.000965 Text::Balanced::_match_tagged
107 7.72 1.7872 79514 0.000022 IkiWiki::PageSpec::match_glob
108 3.32 0.7678 59454 0.000013 <anon>:IkiWiki/Plugin/inline.pm:223
110 `make use of precompiled regex objects`:
112 time elapsed (wall): 27.5357
113 time running program: 23.1289 (84.00%)
114 time profiling (est.): 4.4068 (16.00%)
115 number of calls: 1281981
116 number of exceptions: 13
118 %Time Sec. #calls sec/call F name
119 14.17 3.2776 3408 0.000962 Text::Balanced::_match_tagged
120 7.70 1.7814 79514 0.000022 IkiWiki::PageSpec::match_glob
121 3.35 0.7756 59454 0.000013 <anon>:IkiWiki/Plugin/inline.pm:223
127 --------------------------------------------------------------
129 [[!toggle id="ka-benchmarks" text="Kathryn's benchmarks"]]
131 [[!toggleable id="ka-benchmarks" text="""
132 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.
136 time elapsed (wall): 27.4173
137 time running program: 22.5909 (82.40%)
138 time profiling (est.): 4.8264 (17.60%)
139 number of calls: 1314729
140 number of exceptions: 65
142 %Time Sec. #calls sec/call F name
143 11.05 2.4969 62333 0.000040 IkiWiki::PageSpec::match_glob
144 4.10 0.9261 679 0.001364 Text::Balanced::_match_tagged
145 2.72 0.6139 59812 0.000010 IkiWiki::SuccessReason::merge_influences
150 time elapsed (wall): 26.1843
151 time running program: 21.5673 (82.37%)
152 time profiling (est.): 4.6170 (17.63%)
153 number of calls: 1252433
154 number of exceptions: 65
156 %Time Sec. #calls sec/call F name
157 7.66 1.6521 62333 0.000027 IkiWiki::PageSpec::match_glob
158 4.33 0.9336 679 0.001375 Text::Balanced::_match_tagged
159 2.81 0.6057 59812 0.000010 IkiWiki::SuccessReason::merge_influences
162 Note that the seconds per call for match_glob in the "after" case has gone down by about a third.
167 --------------------------------------------------------------
169 [[!toggle id="ka-patch" text="Kathryn's original patch"]]
171 [[!toggleable id="ka-patch" text="""
174 diff --git a/IkiWiki.pm b/IkiWiki.pm
175 index 08a3d78..c187b98 100644
178 @@ -2482,6 +2482,8 @@ sub derel ($$) {
184 sub match_glob ($$;@) {
187 @@ -2489,8 +2491,15 @@ sub match_glob ($$;@) {
189 $glob=derel($glob, $params{location});
191 - my $regexp=IkiWiki::glob2re($glob);
192 - if ($page=~/^$regexp$/i) {
193 + # Instead of converting the glob to a regex every time,
194 + # cache the compiled regex to save time.
195 + if (!exists $glob_cache{$glob}
196 + or !defined $glob_cache{$glob})
198 + my $re=IkiWiki::glob2re($glob);
199 + $glob_cache{$glob} = qr/^$re$/i;
201 + if ($page =~ $glob_cache{$glob}) {
202 if (! IkiWiki::isinternal($page) || $params{internal}) {
203 return IkiWiki::SuccessReason->new("$glob matches $page");
207 --------------------------------------------------------------