zed.0xff.me
You MUST free memory you got from ALLOC_N & friends
It’s not documented anywhere, but you must call “xfree()” (note ‘x’ there) on memory blocks you got from ALLOC_N & friends.
Ruby will not free that memory automatically during it’s usual GC process.
Guys from ruby-talk and rubyforge also noticed that.
sample code:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25  | 
  VALUE method_parse(VALUE self, VALUE text) {
    VALUE s;
    char *p    = RSTRING(text)->ptr;
    in_buf_len = RSTRING(text)->len;
    // [skipped some code here]
    buf = ALLOC_N(char, bufsize); 
    // protect buf from GC (theoretically)
    rb_iv_set(self,"@obj",Data_Wrap_Struct(rb_cData,NULL,NULL,buf));
    // [skipped some hard work here]
    // make ruby string from our char[] data
    s = rb_str_new(buf,bufptr-buf);
    // cleanup
    rb_iv_set(self,"@obj",Qnil);
    xfree(buf);
    buf  = NULL;
    bufsize = 0;
    return s;
}
 | 
benchmark: Ruby's rb_str_cat() vs C's strcat()
В связи с началом написания расширений для ruby решил провести бенчмарк рубевской функции rb_str_cat() VS сишной родной strcat()
Результат меня немало удивил:
1 2  | 
  strcat 3.360s rb_str_cat 0.002s  | 
Тесты проводились в тяжелых полевых условиях – испытуемым надо было 100000 (сто тысяч) раз приклеить к строке(изначально пустой) один заранее заданный символ.
И ruby сделал это, грубо говоря, в 1500 раз быстрее!
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18  | 
  #define N 100000 VALUE test_concat_rb_str_cat(VALUE self){ int i; VALUE s = rb_str_new2(""); for(i=0;i<N;i++) rb_str_cat(s,"t",1); return s; } VALUE test_concat_strcat(VALUE self){ int i; char buf[N+2]; *buf=0; for(i=0;i<N;i++) strcat(buf,"t"); return rb_str_new2(buf); }  | 
На самом деле никакого подвоха тут нет – просто в руби объект “строка” содержит в себе не только указатель на байты содержимого, но и длину строки тоже, а сишный вариант при этом каждый раз считает длину строки. Т.е. ему, бедному, пришлось перемолотить при этом суммарно почти 5 гигабайт, каждый раз считая длину строки начиная с самого первого символа, и до 100-тысячного в конце.
Прелесть руби здесь в том что не надо думать о памяти – он сам выделит сколько надо и освободит когда будет можно. В сях это слегка сложнее, но можно доработать сишный код так, чтобы он и память выделял динамически, и длину строки стопицот раз не пересчитывал. Например, так:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20  | 
  VALUE test_concat_char(VALUE self){
    int i;
    int bufsize = 0x1000;
    char *buf = malloc(bufsize);
    char *p=buf;
    VALUE s;
    for(i=0;i<N;i++){
        *p++ = 't';
        if( p-buf >= bufsize ){
            char *oldbuf = buf;
            bufsize += 0x1000;
            buf = realloc(buf, bufsize);
            p = buf + (p-oldbuf);
        }
    }
    s=rb_str_new(buf,p-buf);
    free(buf);
    return s;
}
 | 
Скорость обработки получается уже совсем другая:
1 2 3  | 
  strcat 3.36732s rb_str_cat 0.00215s char 0.00013s  | 
Вот теперь сишный код в 16 раз быстрее чем руби, и в 24000 с лишним раз быстрее, чем в начале.
PS: это всё на Linux 2.6.31-gentoo-r6-zz x86_64 Intel(R) Core(TM)2 Duo CPU E8400 @ 3.00GHz
пишем на C расширение для ruby
Это не просто, а очень просто (как два пальца об асфальт два файла написать):
1й файл: mytest.c
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15  | 
  #include "ruby.h" void Init_mytest(); VALUE method_sayhello(VALUE); VALUE mytest = Qnil; void Init_mytest() { mytest = rb_define_module("MyTest"); rb_define_method(mytest, "sayhello", method_sayhello, 0); } VALUE method_sayhello(VALUE self) { puts("Hello World!"); return Qnil; }  | 
2й файл: extconf.rb
1 2 3 4  | 
  require 'mkmf' extension_name = 'mytest' dir_config(extension_name) create_makefile(extension_name)  | 
запуск:
1 2 3 4 5 6 7 8 9 10 11 12  | 
  #ruby extconf.rb creating Makefile #make x86_64-pc-linux-gnu-gcc -shared -o mytest.so mytest.o -L. -L/usr/lib64 -Wl,-R/usr/lib64 -L. -Wl,-O1 -rdynamic -Wl,-export-dynamic -Wl,-R -Wl,/usr/lib64 -L/usr/lib64 -lruby18 -lpthread -lrt -ldl -lcrypt -lm -lc #irb irb(main):001:0> require 'mytest' => true irb(main):002:0> include MyTest => Object irb(main):003:0> sayhello Hello World! => nil  | 
// пример содран с Введения в расширения Ruby на C, но там всё разжевывается под винду и вижуалстудию