Есть ли прирост производительности при использовании одинарных кавычек по сравнению с двойными в рубине?
Знаете ли вы, что использование двойных кавычек вместо одинарных кавычек в 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)
Это, конечно, возможно в зависимости от реализации, но сканирующая часть интерпретатора должна смотреть на каждый символ только один раз. Для работы с блоками #{} потребуется просто дополнительное состояние (или возможный набор состояний) и переходы.
В табличном сканере это будет единственный поиск, чтобы определить переход, и в любом случае будет происходить для каждого персонажа.
Когда синтаксический анализатор получает выходные данные сканера, уже известно, что ему придется вычислить код в блоке. Таким образом, накладные расходы на самом деле являются только накладными расходами памяти в сканере / парсере для обработки блока #{}, который вы платите в любом случае.
Если я что-то упустил (или неправильно запомнил детали конструкции компилятора), что также возможно:)