Rubyracer (V8 binding for Ruby) performs really slow

☆樱花仙子☆ 提交于 2020-01-02 14:12:55

问题


So, I have a TCP server in eventmachine and therubyracer is used as a way to pre-pend operations (like filters or extensions) to the server. It all works charming when the server is not receiving a lot of data, but when it's being flooded (it is required sometimes) it becomes really slow.

So, I did a small benchmark to see how slower the rubyracer is compared to Ruby, and I was shocked when I saw the results:

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

I don't mind if it's slow, to be honest, but I don't want it to lock up my whole server until it finishes processing the data. Using EM::defer is not really an option (I tried it, but it spawns a gazillion threads sometimes, depending on how intensive the flooding is). I can't get around the flooding, since I didn't design the protocols, and the client requires them to be like that (as horrid as it is).

The benchmark code:

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

The question: Is there a way to remove the bottleneck caused by therubyracer? Implementing JavaScript into Ruby through other means is acceptable.


07 Mar 2012 Update

So, I managed to optimise the code, since I figured what was causing the bottleneck was the Ruby<->JS communication, which happened each time [native code] was being executed, which is all the time since ruby uses getter and setter methods for classes, or when objects were being passed directly between languages.

                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

So, I lessened the number of calls between Ruby and JS by caching on the JS side, but this didn't optimise it as much as I hoped, since at lease one object would have to be passed to the function: a Hash or at least a JSON String, I even went to the length of passing a Fixnum—which made me exclaim FML—which wasn't a big improvement than passing it a string (if at all).

I am still hoping for a better and faster solution than mine.


回答1:


The problem is that by default, The Ruby Racer copies strings from Ruby to V8 and vice versa.

In your benchmark, accessing those 6 string properties will result in at least 6 memcpy() operations which must allocate the new memory and walk the length of the string byte-by-byte to move it over to the new location. Compare this with the Ruby side which is basically a no-op (the string object just wraps a pointer that has already been allocated and setup) and it's no wonder it's far slower.

You can change this behavior to pass Strings by reference rather than by value.

class Wrapper
  attr_reader :object

  def inititialize(object)
    @object = object
  end
end

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

Of course, if you want to access the string in javascript, you will have to eventually pay for the copy. You can use this wrapper technique for Nums, arrays and Hashes all of which are copied over to JavaScript by default.

see https://github.com/cowboyd/therubyracer/wiki/Converting-ruby-object-to-javascript for more details.

V8 does support the concept of externally managed strings, which would allow you to allocate a char * in Ruby, but then use its address from V8. However, this functionality is not currently available in The Ruby Racer.



来源:https://stackoverflow.com/questions/9573818/rubyracer-v8-binding-for-ruby-performs-really-slow

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!