Stash and retrieve computed regex from a cache#598
Conversation
7ec53aa to
40d2022
Compare
Profiler Diff Summary--- master branch https://travis-ci.org/gettalong/kramdown/jobs/536192573
+++ PR branch https://travis-ci.org/gettalong/kramdown/jobs/536298613
- Total allocated: 220.89 MB (1783975 objects)
- Total retained: 1.11 MB (1582 objects)
+ Total allocated: 210.65 MB (1737868 objects)
+ Total retained: 1.11 MB (1583 objects) |
|
🔔 Ding-Dong @gettalong |
|
No, didn't forget, just didn't have time... |
|
@ashmaroli Besides a 4,5% memory improvement and a 2,5% object allocation improvement, is it faster CPU-wise? |
|
I'm not sure how to benchmark this appropriately, both Was hoping you could provide me with the appropriate sample content that covers multiple use-cases... Until then, we could benchmark just the change.. Disclaimer: Micro-benchmarking such as this may be flawed because it disregards the overhead from rest of the codebase: require 'benchmark/ips'
class KParser
# Based purely on the two expressions generated while parsing Jekyll's docs
STOP_RE = /(\])|!?\[/
SPAN_START = /\*|_|`|<|<|\[|!?\[|[^\\]?["']|\$|\{:|&|--|\.\.\.|(?:\\| )?(?:<<|>>)|( |\\)(?=\n)|\\|~~/
def computed_pattern
/(?=#{Regexp.union(STOP_RE, SPAN_START)})/
end
def stashed_pattern
@stashed_pattern ||= {}
@stashed_pattern[STOP_RE] ||= {}
@stashed_pattern[STOP_RE][SPAN_START] ||= /(?=#{Regexp.union(STOP_RE, SPAN_START)})/
end
end
parser = KParser.new
return unless parser.computed_pattern == parser.stashed_pattern
Benchmark.ips do |x|
x.report('computed_pattern') { parser.computed_pattern }
x.report('stashed_pattern') { parser.stashed_pattern }
x.compare!
end |
|
Okay, so I checked how often the regexp would be compiled when generating the kramdown documentation, 1423 times. I didn't notice any runtime differences. Nonetheless, I think it's okay to merge this since the meaning of the change is quite clear when reading the code. One thing: Please move the creation of the hash into the initialization code with something like this: |
I think you're directing me to use an autovivifying hash: @cache ||= Hash.new { |h, k| h[k] = Hash.new(&h.default_proc) }
@cache[foo][bar] ||= resultHowever, from what I recall about profiling memory usage, the autovivified hash uses more memory (about 4-6x) than I'll try and rerun some tests.. |
|
Oh, we don't need the recursive auto-hash. We only need two levels, one for |
|
Apologies for jumping the gun back there. I've made the requested changes. Thanks. |
|
I thought about using a constant, too, but didn't mention it because I don't think there is much additional benefit to it. |
|
Thanks for the changes! I have merged them and moved the initialization of the hash into the class initializer - no need to perform that operation everytime. |
The Thanks for merging the change. |
|
The only difference is that the check of the LHS expression being truthy doesn't need to be done. So instructions are saved resulting in faster execution. As for the ETA: One last look through the open issues/PRs and then I will release the new version. So either today if I find the time, else tomorrow. |
Summary
Use a memoized cache to stash and retrieve computed regular expressions to avoid allocating duplicate regexes.
Context
Results from profiling Jekyll documentation source with
memory_profilerBefore
After