Rubyracer (связывание V8 для Ruby) работает очень медленно - PullRequest
2 голосов
/ 06 марта 2012

Итак, у меня есть TCP-сервер в eventmachine , а therubyracer используется в качестве способа предварительного ожидания операций (таких как фильтры или расширения) для сервера. Все это прекрасно работает, когда сервер не получает много данных, но когда он загружается (иногда это требуется), он становится очень медленным.

Итак, я сделал небольшой тест, чтобы увидеть, насколько медленнее rubyracer по сравнению с Ruby, и я был шокирован , когда увидел результаты:

          user     system      total        real
V8:     0.060000   0.000000   0.060000 (  0.059903)
Ruby:   0.000000   0.000000   0.000000 (  0.000524)

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

Код теста:

require 'v8'
require 'benchmark'

class User
    def initialize
        @name = "smack"
        @sex = "female"
        @age = rand(100)
        @health = rand(100)
        @level = rand(100)
        @colour = rand(14)
    end

    attr_accessor :name, :sex, :age, :health, :level, :colour
end

# Create context and the function
context = V8::Context.new
code = "obj = {
    __incybincy__: function() {
        user.name + '' + '' + ''
        user.sex + '' + '' + ''
        user.age + '' + '' + ''
        user.health + '' + '' + ''
        user.level + '' + '' + ''
        user.colour + '' + '' + ''
    }
}"
context.eval(code)

# Insert the user into the context
user = User.new
context["user"] = user

# Benchmark
n = 100
Benchmark.bm do |x|
    x.report("V8: ") do 
        n.times do
            context['obj'].__incybincy__
        end
    end

    x.report("Ruby: ") do 
        n.times do
            user.name + "" + ""
            user.sex + "" + ""
            user.age.to_s + "" + ""
            user.health.to_s + "" + ""
            user.level.to_s + "" + ""
            user.colour.to_s + "" + ""
        end
    end
end

EDIT

Вопрос: Есть ли способ устранить узкое место, вызванное therubyracer? Реализация JavaScript в Ruby другими способами приемлема.


07 марта 2012 Обновление

Итак, мне удалось оптимизировать код, поскольку я подумал, что узким местом было соединение Ruby <-> JS, которое происходило каждый раз при выполнении [native code], то есть постоянно, так как ruby ​​использует getter и методы установки для классов или когда объекты передавались непосредственно между языками.

                user     system      total        real
V8-optimized: 0.050000   0.000000   0.050000 (  0.049733)
V8-normal:    0.870000   0.050000   0.920000 (  0.885439)
Ruby:         0.010000   0.000000   0.010000 (  0.015064)
#where n is 1000

Итак, я уменьшил количество вызовов между Ruby и JS, кэшируя на стороне JS, но это не оптимизировало его так сильно, как я надеялся, поскольку при аренде один объект должен был бы быть передан функции: Hash или, по крайней мере, JSON String, я даже пошел на длину прохождения Fixnum - что заставило меня воскликнуть FML - что не было большим улучшением, чем передача строки (если вообще).

Я все еще надеюсь на лучшее и более быстрое решение, чем мое.

1 Ответ

4 голосов
/ 19 июня 2012

Проблема в том, что по умолчанию Ruby Racer копирует строки из Ruby в V8 и наоборот.

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

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

class Wrapper
  attr_reader :object

  def inititialize(object)
    @object = object
  end
end

cxt['aString'] = Wrapper.new('not copied')

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

см. https://github.com/cowboyd/therubyracer/wiki/Converting-ruby-object-to-javascript для получения более подробной информации.

V8 поддерживает концепцию внешних управляемых строк, которая позволит вам выделить char * в Ruby, но затем использовать его адрес из V8.Однако эта функция в настоящее время недоступна в The Ruby Racer.

...