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.

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.