Is String build faster than concatenation, or benchmarking wrong?

I have learned like that, but my benchmarking code did not agree, is my code wrong?

require "benchmark"

Benchmark.ips do |x|
  x.report("build") do
    build_s
  end

  x.report("conc") do
    conc_s
  end
end

Benchmark.measure {
  10000.times do
    build_s
  end
}

Benchmark.bm do |x|
  x.report("build") do
    10000.times do
      build_s
    end
  end
end

def build_s
  String.build do |io|
    io << "foo" << 12
  end
end

def conc_s
  "foo" + 12.to_s
end

See related optimization in soon to be released Crystal 0.32.0 https://github.com/crystal-lang/crystal/pull/8400

Could you post your benchmark results? Otherwise we don’t know what you expect and what is the actual result.

In my case these are the results:

build   9.35M (106.91ns) (± 9.95%)   208B/op   2.97× slower
 conc  27.79M ( 35.99ns) (± 3.29%)  48.0B/op        fastest

The reason build is slower is because String.build will create a String::Builder instance (memory allocated) and then allocate unknown size for what you are going to build (64 bytes at least) and then create a string out of it.

On the other hand 12.to_s will allocate a string of exactly 2 bytes, then "foo" + ... will allocate a string of exactly 5 bytes.

Now that we’ll have String.interpolation in the next release we could add logic to optimize this. It’s a bit complex, but we could try to compute an effective size if all arguments to interpolate are strings or integers, and then use String.new instead of String.build.

2 Likes

Thanks for your explanation. The results are similar, I wonder this problem while I was reading “programming crystal”

For efficiency, you should avoid creating extra strings. In the following snippet, both to_s, +, and
string interpolation create a string on the heap, though interpolation is faster:

   rate = 0.84320536 
   p "rate: " + rate.to_s # => "rate: 0.84320536" 
   # string interpolation is more efficient: 
   p "rate: #{rate}" # => "rate: 0.84320536" 

You can also use a String builder and the << method:
str = String.build do |io|
io << "rate: " << rate
end
p str # => “rate: 0.84320536”

Thank you in advance.