Ruby и C. Часть 1.

    Ruby очень легко интегрируется с языком программирования C. Можно создавать расширения для Ruby. Или мы можем сделать обертку для библиотеки на C, и использовать ее как обычную Ruby библиотеку. Так же можно реализовать критичные вычисления на C прямо в Ruby коде! Другой вариант интеграции — это использование Ruby в C/C++ программах, в качестве скриптового языка. Например, как это сделано в Google SketchUp.

    Давайте посмотрим, какие возможности Ruby представляет для интеграции с C.


    Пусть в нашей программе есть bottleneck, какой-то кусок вычислений, который, например, выполняется в цикле. Эти вычисления очень сильно понижают производительность приложения.
    Решение — переписать критичный кусок на C, т.к. компилируемые языки заведомо быстрее интерпретируемых.

    Тут у нас два варианта, либо создать расширение для Ruby, либо использовать код C прямо из Ruby.
    Для начала выберем второй вариант, и в этом нам на помощь придет библиотека RubyInline.
    Пусть у нас есть метод разложения числа на простые делители. Пойдем по самому простому пути, и реализуем этот метод алгоритмом перебора:
        class Factor
            #num - входное число, show - выводить ли результат на экран
            def factorization num, show=false 
                num1 = num
                n = 2 #начинаем поиск делителей с двойки
                while n*n <= num1
                    if num%n == 0 # если текущее число - делитель, выводим его
                        num = num / n 
                        puts n if show
                    else #иначе увеличиваем текущее число
                        n += 1
                    end
                end
            end
        end

    Но нас не устраивает скорость работы этого метода, по-этому перепишем его на C, с помощью библиотеки RubyInline.
    Для начала установим ее:
    gem install RubyInline
    RubyInline требует POSIX систему, т.е либо *nix, либо Cygwin на Windows.
    А также C/C++ компилятор.

    Простой пример использования:
        require 'rubygems'
        require 'inline' #подключаем RubyInline
    
        class Test
            #обработчик встроенного кода
            inline do |builder| #в блок передается объект класса Inline::C
                builder.c '
                    static char *test() {
                        return "Hi from C!:)";
                    }
                '        
            end
        end
        
        puts Test.new.test # напечатает "Hi from C!:)"


    В примере кода мы сделали небольшой C метод, возвращающий строку приветсвия.
    Метод c выполняет следующие действия:
    — выполняет конверсию типов аргументов и возвращаемого значения из C в Ruby
    — компилирует строку с кодом метода
    — добавляет классу Test метод test
    После этого мы можем использовать test как обычный Ruby метод.

    Вернемся к факторизации. Перепишем метод factorization с помощью RubyInline.
        require 'rubygems'
        require 'inline'
        
        class FastFactor
            inline do |builder|
                builder.c '
                    static void factorization(int num, int show) {
                        int i = 0;
                        int num1 = num;
                        int n = 2;
                        while ( n*n <= num1 ) {
                            if ( num%n == 0 ) {
                                num = num / n;
                                if ( show )
                                    printf( "%d\n", n );
                                i ++;
                            } else {
                                n ++;
                            }
                        }
                    }
                '        
            end
        end


    А теперь, самое интересное. Тесты производительности.
    Тест Ruby реализации:
        f = Factor.new
        1000.times { f.factorization 999999}
    

    Результат:
    $time ruby factor.rb
        real	0m1.034s
        user	0m1.028s
        sys 	0m0.004s
    

    И аналогично C:
        f = FastFactor.new
        1000.times { f.factorization 999999, false }
    

    И результат:
    time ruby factorfast.rb
        real	0m0.116s
        user	0m0.092s
        sys          0m0.024s
    


    Как видно из теста, RubyInline позволяет ускорить критичные куски кода практически в 10 раз!
    Код тестов: factor.rb, factorfast.rb.
    Подводя итог, можно сказать, что если наше приложение не справляется с нагрузками, то мы всегда можем его ускорить, реализовав критичные моменты на C. Благо Ruby предоставляет нам мощные и удобные средства для этого.

    В следующей части я расскажу про создание C расширений для Ruby.

    Ссылки по теме:
    RubyInline: http://rubyforge.org/projects/rubyinline/
    Документация: http://rubyinline.rubyforge.org/RubyInline/
    Поделиться публикацией

    Похожие публикации

    Комментарии 40
      +1
      Как раз хотел разобраться в теме.
        +1
        В очередной раз убедился в универсальности Ruby.
        Спасибо за статью, с нетерпением буду ждать следующую часть.
          0
          Круто! Даже появилось желание попрогать на руби! )
            0
            Тест аналогичного куска на Perl/Python/PHP в студию!

            PS Perl тоже умеет использовать Си…
              +1
              >Тест аналогичного куска на Perl/Python/PHP в студию!
              Скорость сравнить? Я исходники дал, можете попробовать. Эта статья подразумевалась не как бенчмарк, а как описание методики.
              >Perl тоже умеет использовать Си
              Да, эта библиотека(RubyInline) как раз из мира Perl пришла, о чем на ее сайте и сказано.
                0
                За описание методики спасибо, но хотелось бы и кусочек бенчмарка посмотреть — у меня руби нет :)
                  0
                  Ок, возможно потом оформлю отдельным постом.
                0
                define('TIME', microtime(true));
                class Factor {
                public static function factorization($num, $show = FALSE) {
                $numl = $num;
                $n = 2;
                while (pow($n, 2) <= $numl) {
                if ($num % $n == 0) {
                $num = $num / $n;
                if ($show) echo $n.'<BR />';
                } else $n += 1;
                }
                }
                }
                Factor::factorization(999999);
                printf("%.5f", microtime(true) — TIME, 5);

                Время: 0.00086
                Система: винда ХП, пень 4 (нортвуд, 3.2Ггц, 512кб кэша), 2 гига оперативки
                  +1
                  На тысячу помножить еще забыли:)
                  В топике — 1000 раз в цикле.
                    0
                    Действительно, тогда в среднем будет 0.88с
                      0
                      Что-то у вас все равно медленно. Я сейчас как раз по заявкам читателей делаю бенчмарки:)
                      И PHP у меня в разы быстрее срабатывает чем у вас, хотя машинка послабее.
                      Но могу сказать что Ruby 1.9(в топике 1.8.6) вроде быстрее PHP:)
                        0
                        Какая у вас машина? У меня комп 6-летней давности с засранной виндой.
                          0
                          ivs@debian:~$ uname -a
                          Linux debian 2.6.26-1-686 #1 SMP Mon Dec 15 18:15:07 UTC 2008 i686 GNU/Linux
                          ivs@debian:~$ cat /proc/cpuinfo | grep "model name"
                          model name : Intel® Pentium® D CPU 2.80GHz
                          model name : Intel® Pentium® D CPU 2.80GHz
                          ivs@debian:~$ php -v
                          PHP 5.2.6-0.1~lenny1 with Suhosin-Patch 0.9.6.2 (cli) (built: Nov 29 2008 21:35:12)
                          Copyright © 1997-2008 The PHP Group
                          Zend Engine v2.2.0, Copyright © 1998-2008 Zend Technologies
                            0
                            На самом деле у вас помощнее…
                            очень жду бенчмаков :)
                              0
                              А, ну правильно, у меня двухядерный. На частоту посмотрел, подумалось что у вас быстрее:)
                                +1
                                >очень жду бенчмаков :)
                                Готово: iv_s.habrahabr.ru/blog/48952/
                    0
                    >Тест аналогичного куска на Perl/Python/PHP в студию!
                    Готово: iv_s.habrahabr.ru/blog/48952/
                    • НЛО прилетело и опубликовало эту надпись здесь
                      0
                      шикарная фича.
                      беру на вооружение
                      • НЛО прилетело и опубликовало эту надпись здесь
                          0
                          Для написания расширений в Ruby есть C API. Про это — в следующей части:)
                          • НЛО прилетело и опубликовало эту надпись здесь
                              0
                              Как и практически все динамические языки, которые я знаю:)
                              0
                              да, напишите плиз про C API. а то я уже замучался с lua (а питон как-то не катит)
                                +3
                                У меня по плану — 3 части. Первая эта, затем про С расширения для Ruby, а третья как раз про встраивание Ruby.
                                Сразу скажу, по личному впечатлению со встроенным Ruby приятнее в C работать чем с Lua:)
                                Так что ждите следующих частей:)
                                • НЛО прилетело и опубликовало эту надпись здесь
                                    0
                                    Ага, на maxidoors.ru/ читал, только у вас про сборку, а я планирую про само написание.
                                    Кстати, почему блог забросили? Интересно было читать.
                                    • НЛО прилетело и опубликовало эту надпись здесь
                                        0
                                        >Не, у меня и про написание было.
                                        Точно, старые архивы полистал — много информации. Спасибо, почитаю.
                            0
                            полезно!
                              +2
                              Прямо-таки неделя руби на лоре хабре.
                              А за пост большое спасибо.
                                0
                                а по-моему скорее неделя «резиновой» верстки…
                                0
                                точку с запятой после return «Hi from C!:)», не компилится
                                  0
                                  Точно. Спасибо, исправил.
                                  0
                                  Даешь Руби в массы! ;)
                                    0
                                    Очень круто. Написано кратко и понятно, пример подобран простой и по делу. Не дошли руки ещё до этого аспекта Ruby, так что ваша статья очень в тему.
                                    Спасибо! Ждём продолжения.
                                      0
                                      Кстати, таким образом можно использовать только C или C++ тоже?
                                      Вопрос задаю исключительно из интереса: в плане повышения производительности инлайновыми вставками, очевидно, имеет смысл использовать только С.
                                        0
                                        C++ также можно использовать. Подробнее можно в документации посмотреть(ссылка в топике).
                                        Притом также можно использовать и Fortran:) И есть возможность добавлять собственные языки.
                                        0
                                        Есть еще более интересный способ использовать Си и собственно не только его:
                                        FFI ( Foregin Function Interface )

                                        Вот пример прямо из докуменации:

                                        require 'ffi'

                                        module GetPid
                                        extend FFI::Library

                                        attach_function :getpid, [], :uint
                                        end

                                        puts GetPid.getpid

                                        Правда ведь удивительно красиво?

                                        blog.headius.com/2008/10/ffi-for-ruby-now-available.html
                                          0
                                          да не сильно. зависимость от этого процесса (!)работающего прикручивается :(

                                        Только полноправные пользователи могут оставлять комментарии. Войдите, пожалуйста.

                                        Самое читаемое