Есть ли прирост производительности при использовании одинарных кавычек по сравнению с двойными в рубине?

Знаете ли вы, что использование двойных кавычек вместо одинарных кавычек в ruby ​​приводит к снижению производительности каким-либо значимым образом в ruby ​​1.8 и 1.9.

так что если я наберу

question = 'my question'

это быстрее чем

question = "my question"

Я полагаю, что ruby ​​пытается выяснить, нужно ли что-то оценивать, когда он встречает двойные кавычки и, вероятно, тратит на это несколько циклов.

14 ответов

Решение
$ ruby -v
ruby 1.9.3p0 (2011-10-30 revision 33570) [x86_64-darwin11.0.0]

$ cat benchmark_quotes.rb
# As of Ruby 1.9 Benchmark must be required
require 'benchmark'

n = 1000000
Benchmark.bm(15) do |x|
  x.report("assign single") { n.times do; c = 'a string'; end}
  x.report("assign double") { n.times do; c = "a string"; end}
  x.report("concat single") { n.times do; 'a string ' + 'b string'; end}
  x.report("concat double") { n.times do; "a string " + "b string"; end}
end

$ ruby benchmark_quotes.rb 

                      user     system      total        real
assign single     0.110000   0.000000   0.110000 (  0.116867)
assign double     0.120000   0.000000   0.120000 (  0.116761)
concat single     0.280000   0.000000   0.280000 (  0.276964)
concat double     0.270000   0.000000   0.270000 (  0.278146)

Примечание: я обновил это, чтобы оно работало с более новыми версиями Ruby, и очистил заголовок и запустил тест на более быстрой системе.

Этот ответ опускает некоторые ключевые моменты. Особенно обратите внимание на эти другие ответы, касающиеся интерполяции и причины, по которой нет существенной разницы в производительности при использовании одинарных или двойных кавычек.

Резюме: нет разницы в скорости; это великое совместное руководство по стилю Ruby рекомендует быть последовательным. Я сейчас пользуюсь 'string' если не требуется интерполяция (опция A в руководстве) и нравится, но вы, как правило, увидите больше кода с "string",

Подробности:

Теоретически, это может иметь значение, когда ваш код анализируется, но не только если вы не заботитесь о времени анализа в целом (ничтожно мало по сравнению со временем выполнения), вы не сможете найти существенную разницу в этом случае.

Важно то, что когда он будет выполнен, он будет точно таким же.

Сравнительный анализ показывает только отсутствие понимания того, как работает Ruby. В обоих случаях строки будут проанализированы tSTRING_CONTENT (см. источник в parse.y). Другими словами, процессор будет выполнять те же самые операции при создании 'string' или же "string", Точно такие же биты будут отображаться точно так же. Сравнительный анализ показывает только различия, которые не являются значительными и обусловлены другими факторами (включение GC и т. Д.); помните, в этом случае не может быть никакой разницы! Микро тесты, подобные этим, трудно получить правильные. Смотри мой драгоценный камень fruity для достойного инструмента для этого.

Обратите внимание, что если есть интерполяция формы "...#{...}..." это анализируется tSTRING_DBEG, куча tSTRING_DVAR для каждого выражения в #{...} и финал tSTRING_DEND, Это только в том случае, если есть интерполяция, но это не то, о чем говорит ОП.

Раньше я предлагал вам использовать двойные кавычки везде (это упрощает добавление #{some_var} позже), но теперь я использую одинарные кавычки, если мне не нужна интерполяция, \n и т. д. Мне нравится визуально, и это немного более явно, так как нет необходимости анализировать строку, чтобы увидеть, содержит ли она какое-либо выражение.

Никто не мог измерить конкатенацию против интерполяции, хотя:

$ ruby -v
ruby 1.8.7 (2008-08-11 patchlevel 72) [i686-darwin9.6.2]
$ cat benchmark_quotes.rb
require 'benchmark'
n = 1000000
Benchmark.bm do |x|
  x.report("assign single") { n.times do; c = 'a string'; end}
  x.report("assign double") { n.times do; c = "a string"; end}
  x.report("assign interp") { n.times do; c = "a string #{'b string'}"; end}
  x.report("concat single") { n.times do; 'a string ' + 'b string'; end}
  x.report("concat double") { n.times do; "a string " + "b string"; end}
end

$ ruby -w benchmark_quotes.rb 
      user     system      total        real
assign single  2.600000   1.060000   3.660000 (  3.720909)
assign double  2.590000   1.050000   3.640000 (  3.675082)
assign interp  2.620000   1.050000   3.670000 (  3.704218)
concat single  3.760000   1.080000   4.840000 (  4.888394)
concat double  3.700000   1.070000   4.770000 (  4.818794)

В частности, обратите внимание assign interp = 2.62 против concat single = 3.76, Как глазурь на торте, я также считаю, что интерполяция будет более читабельной, чем 'a' + var + 'b' особенно в отношении пространств.

Нет разницы - если вы не используете #{some_var} интерполяция строки стиля. Но вы получите удар по производительности, только если действительно это сделаете.

Модифицировано из примера Zetetic:

require 'benchmark'
n = 1000000
Benchmark.bm do |x|
  x.report("assign single") { n.times do; c = 'a string'; end}
  x.report("assign double") { n.times do; c = "a string"; end}
  x.report("assign interp") { n.times do; c = "a #{n} string"; end}  
  x.report("concat single") { n.times do; 'a string ' + 'b string'; end}
  x.report("concat double") { n.times do; "a string " + "b string"; end}
  x.report("concat interp") { n.times do; "a #{n} string " + "b #{n} string"; end}
end

выход

               user       system     total    real
assign single  0.370000   0.000000   0.370000 (  0.374599)
assign double  0.360000   0.000000   0.360000 (  0.366636)
assign interp  1.540000   0.010000   1.550000 (  1.577638)
concat single  1.100000   0.010000   1.110000 (  1.119720)
concat double  1.090000   0.000000   1.090000 (  1.116240)
concat interp  3.460000   0.020000   3.480000 (  3.535724)

Одиночные кавычки могут быть немного быстрее двойных, потому что лексеру не нужно проверять #{} интерполяционные маркеры. В зависимости от реализации и т. Д. Обратите внимание, что это затраты на анализ, а не на выполнение.

Тем не менее, реальный вопрос заключался в том, "уменьшает ли использование строк в двойных кавычках производительность каким-либо значимым образом", и ответ на этот вопрос является решающим "нет". Разница в производительности настолько невероятно мала, что она совершенно незначительна по сравнению с реальными проблемами производительности. Не трать свое время.

Конечно, фактическая интерполяция - это отдельная история. 'foo' будет почти точно на 1 секунду быстрее, чем "#{sleep 1; nil}foo",

Двойные кавычки требуют вдвое больше ключевых нажатий, чем одинарные. Я всегда спешу. Я использую одинарные кавычки.:) И да, я считаю, что это "увеличение производительности".:)

Думал бы добавить сравнение 1.8.7 и 1.9.2. Я запускал их несколько раз. Дисперсия была около +-0,01.

require 'benchmark'
n = 1000000
Benchmark.bm do |x|
  x.report("assign single") { n.times do; c = 'a string'; end}
  x.report("assign double") { n.times do; c = "a string"; end}
  x.report("assign interp") { n.times do; c = "a #{n} string"; end}
  x.report("concat single") { n.times do; 'a string ' + 'b string'; end}
  x.report("concat double") { n.times do; "a string " + "b string"; end}
  x.report("concat interp") { n.times do; "a #{n} string " + "b #{n} string"; end}
end

ruby 1.8.7 (2010-08-16, уровень исправления 302) [x86_64-linux]

assign single  0.180000   0.000000   0.180000 (  0.187233)
assign double  0.180000   0.000000   0.180000 (  0.187566)
assign interp  0.880000   0.000000   0.880000 (  0.877584)
concat single  0.550000   0.020000   0.570000 (  0.567285)
concat double  0.570000   0.000000   0.570000 (  0.570644)
concat interp  1.800000   0.010000   1.810000 (  1.816955)

ruby 1.9.2p0 (2010-08-18, редакция 29036) [x86_64-linux]

  user          system      total      real
assign single  0.140000   0.000000   0.140000 (  0.144076)
assign double  0.130000   0.000000   0.130000 (  0.142316)
assign interp  0.650000   0.000000   0.650000 (  0.656088)
concat single  0.370000   0.000000   0.370000 (  0.370663)
concat double  0.370000   0.000000   0.370000 (  0.370076)
concat interp  1.420000   0.000000   1.420000 (  1.412210)

В обоих направлениях нет существенной разницы. Это должно быть огромным, чтобы это имело значение.

За исключением случаев, когда вы уверены, что существует реальная проблема с синхронизацией, оптимизируйте для удобства обслуживания программиста.

Затраты машинного времени очень и очень малы. Затраты программиста на написание кода и его поддержку огромны.

Какая польза от оптимизации, позволяющей экономить секунды, даже минуты времени выполнения при тысячах запусков, если это означает, что код сложнее поддерживать?

Выберите стиль и придерживайтесь его, но не выбирайте этот стиль на основе статистически незначимых миллисекунд времени выполнения.

Я попробовал следующее:

def measure(t)
  single_measures = []
  double_measures = []
  double_quoted_string = ""
  single_quoted_string = ''
  single_quoted = 0
  double_quoted = 0

  t.times do |i|
    t1 = Time.now
    single_quoted_string << 'a'
    t1 = Time.now - t1
    single_measures << t1

    t2 = Time.now
    double_quoted_string << "a"
    t2 = Time.now - t2
    double_measures << t2

    if t1 > t2 
      single_quoted += 1
    else
      double_quoted += 1
    end
  end
  puts "Single quoted did took longer in #{((single_quoted.to_f/t.to_f) * 100).round(2)} percent of the cases"
  puts "Double quoted did took longer in #{((double_quoted.to_f/t.to_f) * 100).round(2)} percent of the cases"

  single_measures_avg = single_measures.inject{ |sum, el| sum + el }.to_f / t
  double_measures_avg = double_measures.inject{ |sum, el| sum + el }.to_f / t
  puts "Single did took an average of #{single_measures_avg} seconds"
  puts "Double did took an average of #{double_measures_avg} seconds"
    puts "\n"
end
both = 10.times do |i|
  measure(1000000)
end

И это выходы:

1.

Single quoted did took longer in 32.33 percent of the cases
Double quoted did took longer in 67.67 percent of the cases
Single did took an average of 5.032084099982639e-07 seconds
Double did took an average of 5.171539549983464e-07 seconds

2.

Single quoted did took longer in 26.9 percent of the cases
Double quoted did took longer in 73.1 percent of the cases
Single did took an average of 4.998066229983696e-07 seconds
Double did took an average of 5.223457359986066e-07 seconds

3.

Single quoted did took longer in 26.44 percent of the cases
Double quoted did took longer in 73.56 percent of the cases
Single did took an average of 4.97640888998877e-07 seconds
Double did took an average of 5.132918459987151e-07 seconds

4.

Single quoted did took longer in 26.57 percent of the cases
Double quoted did took longer in 73.43 percent of the cases
Single did took an average of 5.017136069985988e-07 seconds
Double did took an average of 5.004514459988143e-07 seconds

5.

Single quoted did took longer in 26.03 percent of the cases
Double quoted did took longer in 73.97 percent of the cases
Single did took an average of 5.059069689983285e-07 seconds
Double did took an average of 5.028807639983705e-07 seconds

6.

Single quoted did took longer in 25.78 percent of the cases
Double quoted did took longer in 74.22 percent of the cases
Single did took an average of 5.107472039991399e-07 seconds
Double did took an average of 5.216212339990241e-07 seconds

7.

Single quoted did took longer in 26.48 percent of the cases
Double quoted did took longer in 73.52 percent of the cases
Single did took an average of 5.082368429989468e-07 seconds
Double did took an average of 5.076817109989933e-07 seconds

8.

Single quoted did took longer in 25.97 percent of the cases
Double quoted did took longer in 74.03 percent of the cases
Single did took an average of 5.077162969990005e-07 seconds
Double did took an average of 5.108381859991112e-07 seconds

9.

Single quoted did took longer in 26.28 percent of the cases
Double quoted did took longer in 73.72 percent of the cases
Single did took an average of 5.148080479983138e-07 seconds
Double did took an average of 5.165793929982176e-07 seconds

10.

Single quoted did took longer in 25.03 percent of the cases
Double quoted did took longer in 74.97 percent of the cases
Single did took an average of 5.227828659989748e-07 seconds
Double did took an average of 5.218296609988378e-07 seconds

Если я не ошибся, мне кажется, что оба они занимают примерно одно и то же время, хотя в большинстве случаев одинарные кавычки немного быстрее.

Я тоже думал, что строки в одинарных кавычках могут быть быстрее проанализированы для Ruby. Кажется, это не так.

Во всяком случае, я думаю, что вышеупомянутый тест измеряет не то, что нужно. Само собой разумеется, что обе версии будут проанализированы в одних и тех же внутренних строковых представлениях, поэтому, чтобы получить ответ о том, какой из них быстрее анализировать, мы должны измерять не производительность строковыми переменными, а скорость разбора строк в Ruby.

generate.rb: 
10000.times do
  ('a'..'z').to_a.each {|v| print "#{v}='This is a test string.'\n" }
end

#Generate sample ruby code with lots of strings to parse
$ ruby generate.rb > single_q.rb
#Get the double quote version
$ tr \' \" < single_q.rb > double_q.rb

#Compare execution times
$ time ruby single_q.rb 

real    0m0.978s
user    0m0.920s
sys     0m0.048s
$ time ruby double_q.rb 

real    0m0.994s
user    0m0.940s
sys     0m0.044s

Повторные пробеги, кажется, не имеют большого значения. Для анализа любой версии строки все равно требуется примерно одинаковое время.

Есть один, который вы все пропустили.

ЗДЕСЬ док

попробуй это

require 'benchmark'
mark = <<EOS
a string
EOS
n = 1000000
Benchmark.bm do |x|
  x.report("assign here doc") {n.times do;  mark; end}
end

Это дало мне

`asign here doc  0.141000   0.000000   0.141000 (  0.140625)`

а также

'concat single quotes  1.813000   0.000000   1.813000 (  1.843750)'
'concat double quotes  1.812000   0.000000   1.812000 (  1.828125)'

так что это, конечно, лучше, чем concat и писать все эти путы.

Мне бы хотелось, чтобы Руби преподавал больше по языку манипулирования документами.

В конце концов, разве мы не делаем это в Rails, Sinatra и при выполнении тестов?

~ > ruby -v   
jruby 1.6.7 (ruby-1.8.7-p357) (2012-02-22 3e82bc8) (Java HotSpot(TM) 64-Bit Server VM 1.6.0_37) [darwin-x86_64-java]
~ > cat qu.rb 
require 'benchmark'

n = 1000000
Benchmark.bm do |x|
  x.report("assign single") { n.times do; c = 'a string'; end}
  x.report("assign double") { n.times do; c = "a string"; end}
  x.report("concat single") { n.times do; 'a string ' + 'b string'; end}
  x.report("concat double") { n.times do; "a string " + "b string"; end}
end
~ > ruby qu.rb
      user     system      total        real
assign single  0.186000   0.000000   0.186000 (  0.151000)
assign double  0.062000   0.000000   0.062000 (  0.062000)
concat single  0.156000   0.000000   0.156000 (  0.156000)
concat double  0.124000   0.000000   0.124000 (  0.124000)

Я изменил ответ Тима Сноухайта.

require 'benchmark'
n = 1000000
attr_accessor = :a_str_single, :b_str_single, :a_str_double, :b_str_double
@a_str_single = 'a string'
@b_str_single = 'b string'
@a_str_double = "a string"
@b_str_double = "b string"
@did_print = false
def reset!
    @a_str_single = 'a string'
    @b_str_single = 'b string'
    @a_str_double = "a string"
    @b_str_double = "b string"
end
Benchmark.bm do |x|
    x.report('assign single       ') { n.times do; c = 'a string'; end}
    x.report('assign via << single') { c =''; n.times do; c << 'a string'; end}
    x.report('assign double       ') { n.times do; c = "a string"; end}
    x.report('assing interp       ') { n.times do; c = "a string #{'b string'}"; end}
    x.report('concat single       ') { n.times do; 'a string ' + 'b string'; end}
    x.report('concat double       ') { n.times do; "a string " + "b string"; end}
    x.report('concat single interp') { n.times do; "#{@a_str_single}#{@b_str_single}"; end}
    x.report('concat single <<    ') { n.times do; @a_str_single << @b_str_single; end}
    reset!
    # unless @did_print
    #   @did_print = true
    #   puts @a_str_single.length 
    #   puts " a_str_single: #{@a_str_single} , b_str_single: #{@b_str_single} !!"
    # end
    x.report('concat double interp') { n.times do; "#{@a_str_double}#{@b_str_double}"; end}
    x.report('concat double <<    ') { n.times do; @a_str_double << @b_str_double; end}
end

Результаты:

jruby 1.7.4 (1.9.3p392) 2013-05-16 2390d3b on Java HotSpot(TM) 64-Bit Server VM 1.7.0_10-b18 [darwin-x86_64]
       user     system      total        real
assign single         0.220000   0.010000   0.230000 (  0.108000)
assign via << single  0.280000   0.010000   0.290000 (  0.138000)
assign double         0.050000   0.000000   0.050000 (  0.047000)
assing interp         0.100000   0.010000   0.110000 (  0.056000)
concat single         0.230000   0.010000   0.240000 (  0.159000)
concat double         0.150000   0.010000   0.160000 (  0.101000)
concat single interp  0.170000   0.000000   0.170000 (  0.121000)
concat single <<      0.100000   0.000000   0.100000 (  0.076000)
concat double interp  0.160000   0.000000   0.160000 (  0.108000)
concat double <<      0.100000   0.000000   0.100000 (  0.074000)

ruby 1.9.3p429 (2013-05-15 revision 40747) [x86_64-darwin12.4.0]
       user     system      total        real
assign single         0.100000   0.000000   0.100000 (  0.103326)
assign via << single  0.160000   0.000000   0.160000 (  0.163442)
assign double         0.100000   0.000000   0.100000 (  0.102212)
assing interp         0.110000   0.000000   0.110000 (  0.104671)
concat single         0.240000   0.000000   0.240000 (  0.242592)
concat double         0.250000   0.000000   0.250000 (  0.244666)
concat single interp  0.180000   0.000000   0.180000 (  0.182263)
concat single <<      0.120000   0.000000   0.120000 (  0.126582)
concat double interp  0.180000   0.000000   0.180000 (  0.181035)
concat double <<      0.130000   0.010000   0.140000 (  0.128731)

Это, конечно, возможно в зависимости от реализации, но сканирующая часть интерпретатора должна смотреть на каждый символ только один раз. Для работы с блоками #{} потребуется просто дополнительное состояние (или возможный набор состояний) и переходы.

В табличном сканере это будет единственный поиск, чтобы определить переход, и в любом случае будет происходить для каждого персонажа.

Когда синтаксический анализатор получает выходные данные сканера, уже известно, что ему придется вычислить код в блоке. Таким образом, накладные расходы на самом деле являются только накладными расходами памяти в сканере / парсере для обработки блока #{}, который вы платите в любом случае.

Если я что-то упустил (или неправильно запомнил детали конструкции компилятора), что также возможно:)

Другие вопросы по тегам