Сейчас я расскажу о бесполезной микрооптимизации, или как ускорить то, что и так быстро работает, в четыре раза.

Вот, допустим, в каком-то опенсорс проекте есть вот такой код:

def base_zip_log_name
  t = Time.now.utc.iso8601
  # Name the file based on GUID and time.  GUID and Date/time of the request are as close to unique filename as we're going to get
  %(App-#{guid}-#{t}).gsub!(/:|\./, "_")
end

guid тут - это, скорее всего, строка вроде такой: 644e1dd7-2a7f-18fb-b8ed-ed78c3f92c2b. t - что-то такое: 2005-08-09T18:31:42.201

gsub!, очевидно, заменяет двоеточия и точки на подчёркивания.

Его и будем ускорять.

Оптимизируя gsub!, можно использовать разные подходы. Можно упростить регулярку. Иногда можно заменить gsub! на sub!. Можно заменить gsub! на gsub. Иногда помогает замена gsub! на tr!, если это применимо. Иногда помогает замена регулярки на простую строку. Попробуем всё, что можно:

require 'benchmark/ips'
Benchmark.ips do |x|
  t    = '2005-08-09T18:31:42.201'
  guid = '644e1dd7-2a7f-18fb-b8ed-ed78c3f92c2b'
  x.report("*gsub!(/:|\./, '_')            ") { %(App-#{guid}-#{t}).gsub!(/:|\./, "_")              }
  x.report("gsub!(/[:.]/, '_')             ") { %(App-#{guid}-#{t}).gsub!(/[:.]/, '_')              }
  x.report("gsub(/:|\./, '_')              ") { %(App-#{guid}-#{t}).gsub(/:|\./, "_")               }
  x.report("gsub(/[:.]/, '_')              ") { %(App-#{guid}-#{t}).gsub(/[:.]/, '_')               }
  x.report("gsub!('.', '_').gsub!(':', '_')") { %(App-#{guid}-#{t}).gsub!('.', '_').gsub!(':', '_') }
  x.report("gsub('.', '_').gsub(':', '_')  ") { %(App-#{guid}-#{t}).gsub('.', '_').gsub(':', '_')   }
  x.report("tr!(':.', '__')                ") { %(App-#{guid}-#{t}).tr!(':.', '__')                 }
  x.report("tr(':.', '__')                 ") { %(App-#{guid}-#{t}).tr(':.', '__')                  }
  x.report("tr!(':.', '_')                 ") { %(App-#{guid}-#{t}).tr!(':.', '_')                  }
  x.report("tr(':.', '_')                  ") { %(App-#{guid}-#{t}).tr(':.', '_')                   }
  x.report("tr!(':', '_').tr!('.', '_')    ") { %(App-#{guid}-#{t}).tr!(':', '_').tr!('.', '_')     }
  x.report("tr(':', '_').tr('.', '_')      ") { %(App-#{guid}-#{t}).tr(':', '_').tr('.', '_')       }
  x.compare!
end

И вот результаты:

Comparison:
tr!(':.', '_')                 :   388004.6 i/s
tr!(':.', '__')                :   376692.5 i/s - 1.03x slower
tr!(':', '_').tr!('.', '_')    :   292588.6 i/s - 1.33x slower
tr(':.', '_')                  :   287673.0 i/s - 1.35x slower
tr(':.', '__')                 :   281670.7 i/s - 1.38x slower
tr(':', '_').tr('.', '_')      :   194984.3 i/s - 1.99x slower
gsub('.', '_').gsub(':', '_')  :   140625.4 i/s - 2.76x slower
gsub!('.', '_').gsub!(':', '_'):   135940.1 i/s - 2.85x slower
gsub(/[:.]/, '_')              :   107319.9 i/s - 3.62x slower
gsub(/:|\./, '_')              :   106426.4 i/s - 3.65x slower
gsub!(/[:.]/, '_')             :   104080.7 i/s - 3.73x slower
*gsub!(/:|\./, '_')            :   103308.7 i/s - 3.76x slower

Звёздочкой отмечена оригинальная версия. Как и следовало ожидать, tr с друзьями разгромил gsub-ы. Также легко заметить что gsub со строкой в качестве первого аргумента рвёт регексповый вариант.

Легко заметить, что двоиточия и точки могут быть только в t, поэтому достаточно tr прогонять только по этой подстроке. Попробуем:

Benchmark.ips do |x|
  t    = '2005-08-09T18:31:42.201'
  guid = '644e1dd7-2a7f-18fb-b8ed-ed78c3f92c2b'
  x.report("global tr!") { %(App-#{guid}-#{t.dup}).tr!(':.', '_') }
  x.report("local tr!")  { %(App-#{guid}-#{t.dup.tr!(':.', '_')}) }
  x.compare!
end
__END__
Comparison:
           local tr!:   325396.0 i/s
          global tr!:   306451.9 i/s - 1.06x slower

Можно попробовать ещё по хардкору ускорить. Мы-то знаем, где в t двоеточия и точки, правильно? Вот туда и вкостылим подчёркивания.

Benchmark.ips do |x|
  guid = '644e1dd7-2a7f-18fb-b8ed-ed78c3f92c2b'
  x.report("[]=") {
    t = '2005-08-09T18:31:42.201'
    t[13] = t[16] = t[19] = '_'
    %(App-#{guid}-#{t})
  }
  x.report("tr!") {
    t = '2005-08-09T18:31:42.201'
    %(App-#{guid}-#{t.tr!(':.', '_')})
  }
  x.compare!
end
__END__
Comparison:
                 tr!:   376810.5 i/s
                 []=:   358605.9 i/s - 1.05x slower

Провал. Можно ещё попробовать зафризить строковые константы.

Benchmark.ips do |x|
  guid = '644e1dd7-2a7f-18fb-b8ed-ed78c3f92c2b'
  x.report("freeze inline") {
    t = '2005-08-09T18:31:42.201'
    %(App-#{guid}-#{t.tr!(':.'.freeze, '_'.freeze)})
  }
  CD = ':.'.freeze
  U  = '_'.freeze
  x.report("global freeze") {
    t = '2005-08-09T18:31:42.201'
    %(App-#{guid}-#{t.tr!(CD, U)})
  }
  x.report('no freeze') {
    t = '2005-08-09T18:31:42.201'
    %(App-#{guid}-#{t.tr!(':.', '_')})
  }
  x.compare!
end
__END__
Comparison:
       global freeze:   428366.0 i/s
       freeze inline:   426449.9 i/s - 1.00x slower
           no freeze:   373307.3 i/s - 1.15x slower

Победно. Тут, кстати, заметно, что срабатывает костыль, добавленный в руби 2, связанный с заморозкой строковых литералов. Когда руби парсер видит замороженный литерал, он делает свою чёрную магию, и под мороженый литерал память выделяется только один раз, даже если его морозить в цикле. В 1.9.3 такого не было! В качестве пруфа - результаты этого же бенчмарка, но для руби 1.9.3:

Comparison:
       global freeze:   360031.1 i/s
           no freeze:   318148.6 i/s - 1.13x slower
       freeze inline:   282103.0 i/s - 1.28x slower

И, наконец, сравним победителя и оригинальную версию:

Benchmark.ips do |x|
  guid = '644e1dd7-2a7f-18fb-b8ed-ed78c3f92c2b'
  x.report("before") {
    t = '2005-08-09T18:31:42.201'
    %(App-#{guid}-#{t}).gsub!(/:|\./, "_")
  }
  x.report("after") {
    t = '2005-08-09T18:31:42.201'
    %(App-#{guid}-#{t.tr!(':.'.freeze, '_'.freeze)})
  }
  x.compare!
end
__END__
Comparison:
               after:   428007.6 i/s
              before:    99713.1 i/s - 4.29x slower

Лютый вин. Вот полный код победителя:

def base_zip_log_name
  t = Time.now.utc.iso8601
  # Name the file based on GUID and time.  GUID and Date/time of the request are as close to unique filename as we're going to get
  %(App-#{guid}-#{t.tr!(':.'.freeze, '_'.freeze)})
end

Правда, ужасно?