Ruby 使用 Rust 提高 Ruby 性能 Demo (10~1000 倍提升)

a112121788 · 2021年08月28日 · 最后由 dajianshi 回复于 2021年08月31日 · 1101 次阅读
本帖已被管理员设置为精华贴

缘起

Ruby 不太适合执行一些 CPU 密集型的任务,比如视频转码,音频压缩,数学计算。 但是 Ruby 在 Web 开发领域的生产力优势,别的语言还是无法取代。

但是为了解决 CPU 密集任务,真的有必要换个 Web 开发框架吗?或许不必要。

常规来说,如果遇到 CPU 敏感性的任务,用 C 扩展一下,在 Ruby 中正常调用即可。据我所知,同时能玩转 C 和 Ruby 的并不多。

也可以使用 Go 语言,Go 语言在 Web 领域使用的也比较广泛,但是 Go 的灵活性并不如 Ruby。 有时候需要考虑下为了一点点性能要求,放弃 Rails 开发经验到底值不值?不同的人会得到不同的大胆。

大部分 Web 服务的大部分需求都不是 CPU 密集型业务。对于这一小部分需要提升性能的点,你可以选择用比的语言重写。然后通过 MQ 通知 Rails 服务即可。

当然你可以不用 MQ,直接调用别的语言生成的模块,通过一定的运行机制加载执行即可。比如之前需要 10S 才能完成的计算, 现在可能需要不到 1 秒既可以,对于偏计算性业务的性能提示,带着的直接受益就是服务器成本下降。比如之前需要 10 台服务器才能完成的计算任务,现在只需要 1-2 台就够了。

直接调用别的语言的模块适合不容易实现,比如在 Ruby 中直接调用 Go 程序可以,但是如何直接调用一个 Go 模块中的方法呢?似乎不太可能。直接调用 Go 源码和 Go 程序都不现实。

做过软件开发的都应该清楚,如果遇到问题不容易解决,分层往往能让问题换个方式解决。

WebAssembly 与 Ruby

Ruby 语言的,或者所解释性语言的瓶颈是 Ruby 解释器。即使引入 JIT 技术,也很难 10 倍速提升性能。JavaScript 也遇到了这样的问题,比如 JavaScript 最开始也很慢, 谷歌开发的 Chrome 浏览器为其 JavaScript 解析引擎加入了 JIT。使得 JavaScript 的性能提升了 20 到~40 倍。这也是 Node.js 比 Ruby 和 Python 都快好几倍的一个重要因素, 另一个因素是 JavaScript 的异步非阻塞特性。长话短说,直接运行机器码是最快的,但是可移植性就大大兼容,这是大部分 Web 开发者都不会支持的。大部分 Web 应用的 JavaScript 性能是够用的。 最终,几个重要的浏览器厂商引入了 WebAssembly。 Assembly,即汇编。我们可把 WebAssembly 理解成一门汇编语言,它有两种语言格式,一个是 .wat,另一个是 .wasm。 WebAssembly 更多的是约定一种格式,而不是让我们直接使用汇编语言代码。

准确地说,wasm 是一种类汇编语言文件,在支持 WebAssembly 的虚拟机中运行,可以达到接近 Native 的性能。

WebAssembly (简称 Wasm) 是基于堆栈的虚拟机的二进制指令格式,Wasm 被设计为可编程 C/C++/Rust 等高级语言的可移植目标,可在 Web 上部署客户端和服务器应用程序。

但是 WebAssembly 不只能用于浏览器,还能用于服务端,比如 Node.js 从 8.x 开始就支持了 WebAssembly。

.wasm 文件是可移植的,如果其运行环境环境可以被移植到别的编程环境中,.wasm 的使用场景就放开了。

wasmer 就是在这样的背景下诞生的。 借助 wasmer-ruby

就可以在 Ruby 中运行 .wsam 文件。

选择合适的语言生成 .wasm

参看 https://webassembly.org/getting-started/developers-guide/,选择你比较熟悉的一门语言来编译 wasm。如果都不熟悉,建议花 2 天时间学习下 Rust。

目前来说,Rust 是编译生成 .wasm 的最佳语言。

案例

rust 核心代码

#[no_mangle]
pub extern "C" fn sum(x: i64, y: i64) -> i64 {
    let mut result: i64 = 0;
    for i in x..y {
        result = result + i;
    } 
    result
}

#[no_mangle]
pub extern "C" fn sum_sqrt(x: i64, y: i64) -> f64 {
    let mut result: f64 = 0.0;
    for i in x..y {
        result = result + (i as f64).sqrt();
    } 
    result
}

rust_sum.rb


def fetch_instance(path)
  file = File.expand_path path, File.dirname(__FILE__)
  store = Wasmer::Store.new
  module_ = Wasmer::Module.new store, IO.read(file, mode: "rb")
  Wasmer::Instance.new module_, nil
end

WASM_PATH = 'pkg/sum_sqrt_bg.wasm'

def rust_sum_x_to_y(x, y)
  fetch_instance(WASM_PATH).exports.sum.(x, y)
end


def rust_sum_sqrt_x_to_y(x, y)
  fetch_instance(WASM_PATH).exports.sum_sqrt.(x, y)
end

ruby_sum.rb

def ruby_sum_x_to_y(x, y)
  sum = 0
  (x...y).each do |i|
    sum = sum + i
  end
  sum
end

def ruby_sum_sqrt_x_to_y(x, y)
  sum = 0
  (x...y).each do |i|
    sum = sum + Math.sqrt(i)
  end
  sum
end

test.rb

require "wasmer"
require 'benchmark'
require './rust_sum'
require './ruby_sum'


puts 'rust_sum_x_to_y(0, 10**8)'
puts Benchmark.measure {
  rust_sum_x_to_y(0, 10**8)
}

puts 'ruby_sum_x_to_y(0, 10**8)'
puts Benchmark.measure {
  ruby_sum_x_to_y(0, 10**8)
}


puts 'rust_sum_sqrt_x_to_y(0, 10**8)'
puts Benchmark.measure {
  rust_sum_sqrt_x_to_y(0, 10**8)
}

puts 'ruby_sum_sqrt_x_to_y(0, 10**8)'
puts Benchmark.measure {
  ruby_sum_sqrt_x_to_y(0, 10**8)
}

测试结果

✗ ruby test.rb
rust_sum_x_to_y(0, 10**8)
  0.003248   0.002018   0.005266 (  0.003324)
ruby_sum_x_to_y(0, 10**8)
  5.111140   0.024763   5.135903 (  5.175571)

rust_sum_sqrt_x_to_y(0, 10**8)
  0.707307   0.004177   0.711484 (  0.716802)
ruby_sum_sqrt_x_to_y(0, 10**8)
  9.483871   0.047976   9.531847 (  9.608818)

结语

WebAssembly 不只用于 JS 环境。 真实项目中的性能问题往往没有那么简单。上述案例提供了一个改进 Ruby 性能的思路。

.wasm 文件,对于 ruby 来说,就是一串字节码。 理论上可以从网络加载,也就是说可以动态改进 Ruby 程序的性能。 wasm 技术本身也在研究,Ruby 在 jit 和 aot 方向也有实质的推进。

原文地址: https://rust.upgradecoder.com/07.html

速度不在一个数量级,可读性和美感也不在一个数量级

感觉是个可行的办法,我觉得比较吸引人的是可以 Runtime 从网络 Load WASM。

如果只是提高本地 gem 性能的话,C ext 会比较成熟。

不知道楼主能不能用 C ext 和 wasmer-ruby 做个 profiling,看看在本地 gem 的 case 下哪个 overhead 更小。

数据类型比较严格,需要保证 i64,超过就凉了吧。。。

pynix 回复

只是个演示。类型不严格的话,很难保证性能。

我就不懂 为什么不直接使用 rust 或者 go 而需要 ruby 过一手才香

Jim 回复

确实,这么玩不如直接用 Rust 算了

huacnlee 将本帖设为了精华贴。 08月31日 10:12
gaicitadie 回复

WebAssembly 可读性还是很好的

我感觉挺好,直接用 Rust,你能赶上 ror 的开发速度?

需要 登录 后方可回复, 如果你还没有账号请 注册新账号