Rubyracer(Ruby的V8绑定)执行速度非常慢 [英] Rubyracer (V8 binding for Ruby) performs really slow

查看:116
本文介绍了Rubyracer(Ruby的V8绑定)执行速度非常慢的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

因此,我在 eventmachine

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.

因此,我做了一个小型基准测试,以查看将rubyracer与Ruby相比要慢得多,当我看到结果时,我感到震惊:

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)

老实说,我不介意它的速度是否很慢,但是我不希望它在完成数据处理之前将我的整个服务器锁定.使用EM::defer并不是一个真正的选择(我尝试过,但有时会产生大量的线程,具体取决于洪水的强度).因为我没有设计协议,所以我无法解决洪水泛滥,并且客户要求它们像那样(真恐怖).

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).

基准代码:

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


编辑

问题:是否有办法消除由竞争者造成的瓶颈?通过其他方式在Ruby中实现JavaScript也是可以接受的.


EDIT

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

2012年3月7日更新

因此,我设法优化了代码,因为我发现造成瓶颈的是Ruby <-> JS通信,该通信在每次执行[native code]时发生,这一直是ruby使用getter和类的设置方法,或在语言之间直接传递对象的时间.

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

因此,我通过在JS端进行缓存来减少了Ruby和JS之间的调用次数,但这并没有像我希望的那样对其进行优化,因为至少必须将一个对象传递给该函数: Hash或至少是JSON String,我什至花了一个Fixnum—的长度,这让我大叫FML—与传递一个字符串(如果有的话)相比,这没有什么大的进步.

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.

推荐答案

问题在于,默认情况下,Ruby Racer 将字符串从Ruby复制到V8,反之亦然.

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

在您的基准测试中,访问这6个字符串属性将导致至少6个memcpy()操作,这些操作必须分配新的内存并逐字节遍历字符串的长度才能将其移到新位置.将此与Ruby方面进行比较,而Ruby方面基本上是无操作的(字符串对象只是包装了已经分配和设置的指针),这也就怪怪它要慢得多.

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')

当然,如果要访问javascript中的字符串,则最终必须为副本付费.您可以将这种包装技术用于Nums,数组和哈希,默认情况下,所有这些均会复制到JavaScript.

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.

请参见 https://github.com/cowboyd/therubyracer/wiki/Converting-ruby-object-to-javascript 了解更多详情.

V8确实支持外部管理的字符串的概念,该概念允许您在Ruby中分配char *,但随后使用其来自V8的地址.但是,该功能目前在Ruby Racer中不可用.

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.

这篇关于Rubyracer(Ruby的V8绑定)执行速度非常慢的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆