when should use string and symbol?
I think of a symbol as just a name - it’s not something to be divided up. A string is a sequence of bytes - you can extract words or characters from a string, etc.
There might be a performance difference in matching keywords vs strings, as a keyword is always the same object, whereas strings are different objects, even if they have the same contents.
See the object_id values here:
2.7.2 :010 > "abc".object_id
=> 200
2.7.2 :011 > "abc".object_id
=> 220
2.7.2 :012 > "abc".object_id
=> 240
2.7.2 :013 > :abc.object_id
=> 2044828
2.7.2 :014 > :abc.object_id
=> 2044828
2.7.2 :015 > :abc.object_id
=> 2044828
I read somewhere that symbols for hash keys are more performant than strings.
It used to be (before 2.2) symbols were immutable and couldn’t be destroyed once instantiated.
Also, symbols use the same memory slot over and over again.
See this link
https://medium.com/@lcriswell/ruby-symbols-vs-strings-248842529fd9
and this link for more details
Symbols are kind of frozen string literals. You can freeze a string literal with the .freeze()
method. The performance of creating string, fronzen string, and symbols are as the following:
string
{:cpu_time=>0.698434, :real_time=>0.6984559019997505}
frozen string
{:cpu_time=>0.4381710000000001, :real_time=>0.43825904999994236}
symbol
{:cpu_time=>0.4134770000000001, :real_time=>0.41361890400003176}
Benchmark code:
def measure_time(mesg = nil)
epsilon = 1.0e-08
puts mesg if mesg
real_t_start = Process.clock_gettime(Process::CLOCK_MONOTONIC)
cpu_t_start = Process.times
yield
real_t_end = Process.clock_gettime(Process::CLOCK_MONOTONIC)
cpu_t_end = Process.times
{
cpu_time: cpu_t_end.utime.+(cpu_t_end.stime).-(
cpu_t_start.utime + cpu_t_start.stime
) - epsilon,
real_time: real_t_end.-(real_t_start) - epsilon
}
end
p measure_time('string') { 10_000_000.times { 'abcd' } }
p measure_time('frozen string') { 10_000_000.times { 'abcd'.freeze } }
p measure_time('symbol') { 10_000_000.times { :abcd } }
As you can see, creating a string is much faster, the .freeze()
is another method call that makes frozen strings slightly slower than symbols. Using #Frozen_String_Literal: true
:
string
{:cpu_time=>0.41340199000000005, :real_time=>0.4134406739998519}
frozen string
{:cpu_time=>0.41783299, :real_time=>0.41783457300005805}
symbol
{:cpu_time=>0.4159899900000001, :real_time=>0.4159993810002413}
As you can see, the difference is minute between immutable frozen string and symbol.
But I have seen people using Symbols like this:
symbol_ary = %i[a b c]
some_var = symbol_ary[1].to_s
This is really common in rails world, and this has no benefit at all, quite the opposite, it’s slower. Don’t use such bad code. There are some other rare code that properly uses symbols, but the real common usage of symbol is creating hashes - unless you of course convert user input strings to symbols and check the hash!