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


126

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

так что если я напечатаю

question = 'my question'

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

question = "my question"

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


17
Запустите его полмиллиона раз и посмотрите. Скорее всего, ваш сайт не получает достаточно трафика. Преждевременная оптимизация вообще не стоит.
ceejayoz 02

60
почему так много людей ожидают, что рубин будет использоваться только для веб-программирования?
Йоханнес

17
Я бы не стал считать такую ​​оптимизацию преждевременной. Это больше похоже на «лучшую практику», поскольку возвращение после завершения вашего приложения и оптимизация для одиночного или двойного использования будет огромной головной болью.
Омар

7
Для меня это просто стиль: я использую одинарные кавычки для «статических» строк и двойные кавычки (или другие интерполированные строки) в других случаях.
tig

3
@Baddie: Это преждевременная оптимизация, если вы оптимизируете проблему, которой не существует.
Энди Лестер

Ответы:


86
$ 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, очистил заголовок и запустил тест на более быстрой системе.

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


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

Видимо, да, хотя разница незначительная. Что до того, почему - меня бьет.
zetetic 04

Этот тест был бы намного более убедительным, если бы он учитывал время компиляции, а также время выполнения.
nohat 05

9
Измеренные различия не имеют смысла. Только порядок (из-за сборки мусора) может иметь большое значение. Нет разницы во времени выполнения между 'и, "поскольку они анализируются на одно и то же.
Марк-Андре Лафортюн,

104

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

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

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

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

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

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

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


3
Кажется гораздо более важным, чем мельчайшая разница в производительности. Это двойные кавычки!
Венкат Д.

Спасибо, что указали мне на свой ответ. Не могли бы вы пояснить, почему вы говорите, что эталонное тестирование вводит в заблуждение? Я согласен, что различия, вероятно, незначительны, но эталонный тест в чем-то неправильный? (Кто-то уже выделил, что #{n}будет выполнять преобразование чисел). Разве это не показывает различия в парсинге ?.
PhilT

1
Спасибо за ссылку на руководство по стилю. Не могу поверить, что не сталкивался с этим раньше.
PhilT

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

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

35

Однако никто не смог измерить конкатенацию против интерполяции:

$ 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'особенно в отношении пробелов.


+1. Это единственный тест с интерполяцией, который сравнивает яблоки с яблоками.
Марк Томас

1
Бенчмаркинг может ввести в заблуждение; см. мой ответ, почему. Что касается сравнения конкатенации и интерполяции, должно быть очевидно, что интерполяция не может быть медленнее, чем конкатенация. В любом случае, вопрос не в этом!
Марк-Андре Лафортюн,

Можете ли вы добавить << к этому тесту?
Ник

16

Без разницы - если вы не используете #{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)

Интересный. Интерполяция выглядит немного дороже. Это было 1.8? Было бы неплохо посмотреть, изменится ли что-нибудь в 1.9.
zetetic 04

зететик - ага. Это было против Ruby 1.8.7
madlep

1
Версия с интерполяцией выполняет одновременно интерполяцию и объединение, а также дважды преобразует число в строку. Интерполяция выигрывает, если вы делаете те же результаты. См. Gist.github.com/810463 . Реальный вывод - больше беспокоиться о to_s, чем о одинарных или двойных кавычках.
Брайан Детерлинг,

Сравнительный анализ только этого может ввести в заблуждение и показать неправильное понимание того, как работает Ruby. Смотрите мой ответ.
Марк-Андре Лафортюн

13

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

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

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


4
+1 за то, что он отмечает, что затраты приходится на время компиляции, а не на время выполнения, поэтому приведенные выше ответы на основе тестов, получившие высокую оценку, вводят в заблуждение.
nohat 04

«это затраты времени синтаксического анализа, а не затраты времени выполнения». это ключевая фраза.
Железный Человек

9

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


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

3
Даже если IDE автоматически закрывает кавычки, двойные кавычки по-прежнему требуют на 100% больше нажатий клавиш. ;-)
Клинт Пахл

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

1
Иногда от лени следую этому совету. Но, к сожалению, в некоторых других языках все наоборот (например, одинарные кавычки требуют Shift + что-то, а двойные кавычки - это одиночное нажатие клавиши). К сожалению, если два человека с разными раскладками клавиатуры работают над одним проектом, одному из них придется пожертвовать некоторыми нажатиями клавиш :)
Халил Озгюр

«Я тороплюсь» - если вы не нажмете Shift и 2 (или любую другую клавишу) один за другим, вы вообще не сэкономите время, используя одинарные кавычки.
Machisuji

8

Думал добавить сравнение 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 (16.08.2010, уровень исправлений 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 (18.08.2010, редакция 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)

Interp выполняет преобразование числа в строку. См. Gist.github.com/810463 .
Брайан Детерлинг,

Смотрите мой ответ, почему вы получаете эти числа.
Марк-Андре Лафортюн

Хорошая точка зрения на Интерп. Я просто скопировал предыдущий ответ как основу для своего. Это меня научит.
PhilT

3

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

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

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

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

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


1

Я тоже подумал, что строки в одинарных кавычках могут быть быстрее для синтаксического анализа 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

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


0

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

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

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

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


0
~ > 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)

0

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

ЗДЕСЬ документ

попробуй это

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 и писать все эти put.

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

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


0

Я модифицировал ответ Тима Сноухайта.

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)

0

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

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

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

Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.