什么是ruby c擴展?
我們知道,我們調用的ruby方法,很多都是由c實現(xiàn)的:

image.png
上圖所示,String類的定義,他的方法都是由c實現(xiàn)的;使用類似的方法我們也可以用c語言實現(xiàn)一些擴展功能,成為ruby的c extension。
什么時候使用 c 擴展
- 想以 C 的速度優(yōu)化某個特別重要的方法
- 想在 C 庫和 Ruby 之間創(chuàng)建接口(一個好用的c庫,我想在ruby里調用)
三步實現(xiàn)一個c擴展
- ext_conf.rb
require 'mkmf'
create_makefile 'echo'
- echo.c
#include <stdio.h>
#include <ruby.h>
VALUE do_it(VALUE self, VALUE str) {
const char* c_string = StringValueCStr(str);
printf("from ruby:%s\n",c_string);
return Qnil;
}
void Init_echo() {
VALUE echo = rb_define_class("Echo", rb_cObject);
rb_define_singleton_method(echo, "do_it", do_it, 1);
}
- 生成,調用
ruby extconf.rb #create makefile
make # 編譯生成c擴展
ruby -e "require './echo';Echo.do_it('abc')" # ruby調用
# from ruby:abc
詳解
require 'mkmf'
create_makefile 'echo'
以上執(zhí)行后就會生成一個Makefile文件,指定了ruby的庫文件,頭文件位置,最終生成共享庫文件echo.bundle
// 引入ruby頭文件,可以使用ruby定義的數(shù)據(jù)類型,方法
#include <ruby.h>
// void Init_echo 中echo必須與上面的庫名一致
// ruby在require 擴展時會調用這個方法
void Init_echo() {
// ruby的數(shù)據(jù)都是由 c語言的VALUE類型表示的
// rb_cObject 就是ruby里的Object
// rb_define_class 定義一個Echo類,以rb_cObject為基類
VALUE echo = rb_define_class("Echo", rb_cObject);
// 給echo類定義一個單例方法,c中的函數(shù)名為do_it,1代表有一個參數(shù)
rb_define_singleton_method(echo, "do_it", do_it, 1);
}
// 給ruby方法對用的函數(shù)都要有返回值VALUE(ruby方法都有返回值)
// VALUE self 方法調用發(fā)
// VALUE str 傳過來的一個參數(shù)
VALUE do_it(VALUE self, VALUE str) {
// StringValueCStr 把 ruby的string轉化為c的char*
const char* c_string = StringValueCStr(str);
// 打印
printf("from ruby:%s\n",c_string);
// Qnil就是ruby的nil
return Qnil;
}
至此,我們完成了從ruby到c的穿梭;其實重要的無外乎兩點:
- ruby調用傳入的參數(shù)轉為c類型處理
- 傳出的參數(shù)包裝為VALUE(ruby數(shù)據(jù)類型)
不固定參數(shù) 及 塊調用
// args 參數(shù)數(shù)組
VALUE sum(VALUE self, VALUE args) {
// 獲取數(shù)組長度
long len = RARRAY_LEN(args);
long i;
long sum = 0; // 合計值
for (i = 0; i < len; i++) {
// 挨個獲取數(shù)組index的內容
VALUE element = rb_ary_entry(args, i);
// 轉為 long相加
sum = sum + FIX2LONG(element);
}
// 如果提供了block(block_given?)
if (rb_block_given_p()) {
// 調用block 傳參,1:1個參數(shù)
// 把sum * 2 轉回ruby int
rb_yield_values(1, LONG2FIX(sum*2));
}
// 返回 轉回ruby int
return LONG2FIX(sum);
}
void Init_sum() {
// 給Objectding定義單例方法,參數(shù)-2 表示參數(shù)以數(shù)組方式傳入
rb_define_singleton_method(rb_cObject, "sum", sum, -2);
}
常用的一些方法
https://docs.ruby-lang.org/en/3.2/extension_rdoc.html
typed_data_struct
TypedData 對象允許 C 擴展開發(fā)人員在對象中存儲他們自己的 C 結構。與實例變量的值必須是有效的 Ruby 對象不同,任何東西都可以放置在這個結構中。
需要注意的是,當我們將 TypedData 對象傳遞回 Ruby 時,它看起來就像任何其他 Ruby 對象一樣。換句話說,我們仍然可以執(zhí)行諸如訪問實例變量和調用 TypedData 對象上的方法之類的操作。

image.png

image.png
https://github.com/secondrocker/typeddata-benchmark
基準測試結果

image.png
typeddata 最快,提升近一倍速度,ivar快點有限,ruby最慢