(2015年までの)odaillyjp blog

イベント参加記録とプログラミング系の雑記

Rubyで文字列を連結するのにString#+を使うと、状況によっては遅くなる

試したRubyのバージョン: 2.2.0

文字列を連結するのにString#+を使わないこと

Rubyで文字列の後ろに別の文字列を破壊的に連結するには、String#+を使う方法と、String#concatを使う方法があります。

str = 'foo'
puts str += 'foo'
# => foofoo

str = 'bar'
puts str.concat('bar')
# => barbar

どちらも結果は同じなのですが、状況によってはString#+を使うと処理に時間がかかることがあります。具体的には、長い文字列の連結を行ったときにこの問題が起こります。

文字数が3万の文字列に対して連結

require 'benchmark'

n = 10_000
str_foo = 'foo' * n
str_bar = 'bar' * n

Benchmark.bmbm(8) do |x|
  x.report('+:')      { str_foo += 'foo' }
  x.report('concat:') { str_bar.concat('bar') }
end
# => 2回目の結果のみ抜き出し
#                user     system      total        real
# +:         0.000000   0.000000   0.000000 (  0.000014)
# concat:    0.000000   0.000000   0.000000 (  0.000006)

文字数が30万の文字列に対して連結

require 'benchmark'

n = 100_000
str_foo = 'foo' * n
str_bar = 'bar' * n

Benchmark.bmbm(8) do |x|
  x.report('+:')      { str_foo += 'foo' }
  x.report('concat:') { str_bar.concat('bar') }
end
# => 2回目の結果のみ抜き出し
#                user     system      total        real
# +:         0.000000   0.000000   0.000000 (  0.000165)
# concat:    0.000000   0.000000   0.000000 (  0.000006)

配列を連結するのにもArray#+を使わないこと

同じように、配列の後ろに別の配列を破壊的に連結する方法には、Array#+を使う方法と、Array#concatを使う方法があります。

ary = [1, 2]
p ary += [3, 4]
# => [1, 2, 3, 4]

ary = [1, 2]
p ary.concat([3, 4])
# => [1, 2, 3, 4]

こちらもArray#+を使うと処理に時間がかかることがありますので、Array#concatを使った方が良さそうです。

require 'benchmark'

n = 1_000
m = 100
ary1 = [*1..n]
ary2 = [*1..n]

Benchmark.bmbm(8) do |x|
  x.report('+:')      { m.times { ary1 += [1] } }
  x.report('concat:') { m.times { ary2.concat([1]) } }
end
# => 2回目の結果のみ抜き出し
#                user     system      total        real
# +:         0.000000   0.000000   0.000000 (  0.000169)
# concat:    0.000000   0.000000   0.000000 (  0.000024)

かなり細かいことだと思うのですが、String#+とArray#+の影響で1時間ほど無駄な待ち時間を生み出してしまったコードがありましたので、ブログに残しておきます。