Статьи

Раскрытие производительности ключа строки в Ruby 2.2

Висячие ключи (XXXL)

Каждый хочет, чтобы их программы были быстрее и занимали меньше памяти. Однако не часто это достигается без необходимости изменения исходного кода. В этом посте будут представлены оптимизации, добавленные в Ruby 2.2.0 при работе с ключами Hash и string. Чтобы понять оптимизацию, вам нужно знать текущее поведение.

В Ruby 2.1 и более ранних версиях, если вы напишите что-то вроде этого:

hash["access_string"]

Ruby выделит строку "access_string" Точно так же Ruby должен выделять строку каждый раз, когда вы создаете хеш-литерал:

 hash = { "foo" => @variable }

Здесь мы выделяем хеш и строку "foo" Это может показаться не большим делом, но если вы Rack и создаете хеш с одинаковыми строковыми ключами и обращаетесь к ним с одинаковыми строковыми ключами по КАЖДОМУ запросу, это может сложиться, создав массу нежелательных объектов.

Например, к "PATH_INFO"

 path = env["PATH_INFO"]

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

Строки против символов

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

 require 'benchmark/ips'

STRING_HASH = { "foo" => "bar" }
SYMBOL_HASH = { :foo => "bar"  }

Benchmark.ips do |x|
  x.report("string") { STRING_HASH["foo"] }
  x.report("symbol") { SYMBOL_HASH[:foo]  }
end

Результаты:

 Calculating -------------------------------------
              string   129.746k i/100ms
              symbol   152.476k i/100ms
-------------------------------------------------
              string      4.619M (± 5.0%) i/s -     23.095M
              symbol      8.587M (± 5.4%) i/s -     42.846M

Использование символа примерно в два раза быстрее строки, потому что нам не нужно делать распределение. Итак, почему мы когда-либо используем строковые ключи? Ну что ж, символы собраны в Ruby 2.2, но не раньше. Поэтому, если вы создаете хэш на основе пользовательского ввода, возможно, вы открываете свое приложение для символической атаки типа «отказ в обслуживании» .

В Ruby 2.2 добавлен символ GC , поэтому проблема безопасности уменьшается, но вызов to_sym

Струнные бассейны для веселья и производительности

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

Мы можем оптимизировать поиск и создание хешей на основе строк внутри Rack, добавив freeze

 path = env["PATH_INFO".freeze]

Ruby понимает, что String#freeze Хотя это выглядит как метод, эта оптимизация выполняется во время компиляции.

 require 'benchmark/ips'
HASH = { "PATH_INFO" => "bar" }

Benchmark.ips do |x|
  x.report("freeze") { HASH["PATH_INFO".freeze] }
  x.report("normal") { HASH["PATH_INFO"] }
end

Результат:

 Calculating -------------------------------------
              freeze   144.130k i/100ms
              normal   127.572k i/100ms
-------------------------------------------------
              freeze      7.326M (± 3.2%) i/s -     36.609M
              normal      4.566M (± 4.1%) i/s -     22.835M

Использование метода String#freeze Я использовал похожую технику, чтобы увеличить скорость работы Rack на 2-4% и уменьшить объем используемой памяти .


чирикать

В качестве бонуса мы также уменьшаем необходимую рабочую память Rack.

Повышение производительности хэша в Ruby 2.2

Хотя это была довольно простая оптимизация и привела к довольно хорошему повышению производительности, она не очень похожа на Ruby. Почему Руби не может сделать это для нас? Ну … это может.

Представленный в патче r43870 , мастер Ruby (т. Е. Ruby 2.2.0+) теперь автоматически делает это повышение производительности за вас. Мне рассказали об этой оптимизации через Эрика Вонга в группе Rack Devel .

Ранее мы видели, что сравнение строки и доступа к символам сильно благоприятствует символу:

 # Ruby 2.1.5

Calculating -------------------------------------
              string   129.746k i/100ms
              symbol   152.476k i/100ms
-------------------------------------------------
              string      4.619M (± 5.0%) i/s -     23.095M
              symbol      8.587M (± 5.4%) i/s -     42.846M

Запуск этого же теста в Ruby 2.2:

 # Ruby 2.2.0

Calculating -------------------------------------
              string    141371 i/100ms
              symbol    143241 i/100ms
-------------------------------------------------
              string  7475494.0 (±7.6%) i/s -   37039202 in   5.001749s
              symbol  8128373.4 (±10.6%) i/s -   40107480 in   5.011651s

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

Дедупликация строки

При исследовании этих оптимизаций я обнаружил, что интересно, что только недавно в Java была реализована такая функция дедупликации строк, как эта оптимизация хеш-ключа . Виртуальная машина Ruby не дублирует каждую строку, она фокусируется только на горячей точке, вызванной хешами. Благодаря строковым ключам возможность сделать это повысит производительность большинства крупных приложений Ruby.

Стоит также отметить, что хотя Ruby делает это по умолчанию, если вам абсолютно необходим самый быстрый код, все же быстрее использовать метод freeze Это похоже на Java 8, где они рекомендуют вручную интернировать строки (аналогично freeze Здесь вы можете увидеть некоторые тесты различных скоростей доступа в Ruby 2.2.0.

Что мне нравится в этой функции, так это то, что она позволяет нам писать код на Ruby и сосредоточиться на намерениях. ВМ может видеть, когда она может оптимизировать этот случай и ускорить наши программы. Нам не нужно ничего делать, чтобы воспользоваться преимуществами повышения производительности наших программ.

Еще одна причина, по которой Ruby делает меня счастливым, бесплатное выступление.


Если вы заботитесь о производительности, строках, Ruby или тестах производительности, следуйте @schneems в твиттере