Optimize for the majority of methods not ending in ! or ?
Optimize for the majority of methods not ending in ! or ?

100_000 flush_cache calls is now 21% faster:

```
ruby flush_cache.rb  8.41s user 0.12s system 99% cpu 8.533 total
ruby flush_cache.rb  6.66s user 0.12s system 99% cpu 6.783 total
```

We compared the following code:

Old:
```ruby
  def self.old_escape_punctuation(string)
    string.sub(/\?\Z/, '_query'.freeze).sub(/!\Z/, '_bang'.freeze)
  end
```

New:
```
  def self.escape_punctuation(string)
    return string unless string.end_with?('?'.freeze, '!'.freeze)

    string = string.dup

    # A String can't end in both ? and !
    if string.sub!(/\?\Z/, '_query'.freeze)
    else
      string.sub!(/!\Z/, '_bang'.freeze)
    end
    string
  end
```

```
Comparison:
New escape_punctuation with ? short string:   675018.3 i/s
Old escape_punctuation with ? short string:   650733.9 i/s - 1.04x slower

Comparison:
New escape_punctuation with ? long string:   589100.4 i/s
Old escape_punctuation with ? long string:   547637.8 i/s - 1.08x slower

Comparison:
Old escape_punctuation with ! short string:   594697.5 i/s
New escape_punctuation with ! short string:   521999.0 i/s - 1.14x slower

Comparison:
Old escape_punctuation with ! long string:   530830.5 i/s
New escape_punctuation with ! long string:   474610.0 i/s - 1.12x slower

Comparison:
New escape_punctuation with short string:  5111961.5 i/s
Old escape_punctuation with short string:   939437.2 i/s - 5.44x slower

Comparison:
New escape_punctuation with long string:  5055869.7 i/s
Old escape_punctuation with long string:   955889.8 i/s - 5.29x slower
```

SCRIPT:

```ruby
require "benchmark/ips"

require './lib/memoist'

short_query = "run?"
long_query  = ("a" * 100) + "?"

short_bang = "run!"
long_bang  = ("a" * 100) + "!"

short_norm = "run"
long_norm  = ("a" * 100)

Benchmark.ips do |x|
  x.report("Old escape_punctuation with ? short string") { Memoist.old_escape_punctuation(short_query)}
  x.report("New escape_punctuation with ? short string") { Memoist.escape_punctuation(short_query) }
  x.compare!
end

Benchmark.ips do |x|
  x.report("Old escape_punctuation with ? long string") { Memoist.old_escape_punctuation(long_query)}
  x.report("New escape_punctuation with ? long string") { Memoist.escape_punctuation(long_query) }
  x.compare!
end

Benchmark.ips do |x|
  x.report("Old escape_punctuation with ! short string") { Memoist.old_escape_punctuation(short_bang)}
  x.report("New escape_punctuation with ! short string") { Memoist.escape_punctuation(short_bang) }
  x.compare!
end

Benchmark.ips do |x|
  x.report("Old escape_punctuation with ! long string") { Memoist.old_escape_punctuation(long_bang)}
  x.report("New escape_punctuation with ! long string") { Memoist.escape_punctuation(long_bang) }
  x.compare!
end

Benchmark.ips do |x|
  x.report("Old escape_punctuation with short string") { Memoist.old_escape_punctuation(short_norm)}
  x.report("New escape_punctuation with short string") { Memoist.escape_punctuation(short_norm) }
  x.compare!
end

Benchmark.ips do |x|
  x.report("Old escape_punctuation with long string") { Memoist.old_escape_punctuation(long_norm)}
  x.report("New escape_punctuation with long string") { Memoist.escape_punctuation(long_norm) }
  x.compare!
end

```

matthewrudy/memoist