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
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”