问题
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