Подключения Go shared library к Ruby

С выходом Go 1.5 появилась возможность делать go library для сторонних программ на других языках. То есть можно написать package который делает что то интересное и тяжело или просто уже готовое решения и подключить его в другую не Go программу. Это может быть C, android, objective C and etc. Я покажу как это легко можно подключить к Ruby.

1. Если у вас есть готовое решение проблемы на go, то не зачем его заново писать на Ruby;
2. Go работает явно быстрее Ruby если мы говорим про свою логику а не готовые решения с gem в которых часто работает C;
3. Go требует меньше памяти если вам надо работать с кучей данный.

Начнем с Go package.

Пишем наше быстрое хорошое рабочее решения на Go:
Код на Go
package main

import "C"

//export add
func add(x, y int) int {
	c := 0
	for i := 0; i < 50000; i++ {
		c += x + y + 1
	}
	return c
}

func main() {}


Это должно содержать main. Обязательно указать:
import "C"

И для функции которые будут доступны с наружи надо указать:
//export %имя функции%

Теперь когда GO программа готова, надо сделать ей build:
go build -buildmode=c-shared -o libadd.so testruby.go

-buildmode — это то, что появилось на Go 1.5, есть несколько разных вариантов, нам надо c-shared. После компиляции получаем .so и .h файл. Теперь это можно поключить в стороние не GO программы.

Теперь часть Ruby.

Нам нужен gem ffi. Ставим его через gem install или через gemfile+bundle install. Подключаем нашу библиотеку к Ruby:
Код на Ruby
require 'ffi'

module MegaSum
  extend FFI::Library
  ffi_lib 'lib/libadd.so'
  attach_function :add, [:int, :int], :int
end


Тут мы указываем где лежит наш .so файл, какие у него есть функции вызовы (на которых мы написали "//export"), что они принимают и что возвращают(полный список типов можно посмотреть на тут). После этого можно работать:
Вызов Go
 def self.add_go(x,y)
    Sum.add(x,y)
  end


Первый вызов будет немного медленный (наверно загружает все в память).

Benchmarks!
Код на Ruby который делает тоже самое
def self.add_ruby(x,y)
    c = 0
    i = 0
    while i<50000
      c += x + y + 1
      i = i+1
    end
    c
  end


[21] pry(main)> Benchmark.realtime { (1..1000).to_a.each {GoHello.add_ruby(3,2) }}
=> 1.763254
[22] pry(main)> Benchmark.realtime { (1..1000).to_a.each {GoHello.add_go(3,2) }}
=> 0.030442
[23] pry(main)> Benchmark.realtime { (1..100000).to_a.each {GoHello.add_go(3,2) }}
=> 3.103797
[24] pry(main)> Benchmark.realtime { (1..100000).to_a.each {GoHello.add_ruby(3,2) }}
=> 195.282368

Как видно что на арифметике простой Go обгоняет Ruby в 60 раз.

Минусы:
1. Не уверен, что можно в Go разводить кучу горутин. У меня это работало на маленькой проверки (не тысячи горутин);

П.C.: Есть похожее решение для Python тут.
AdBlock has stolen the banner, but banners are not teeth — they will be back

More
Ads

Comments 15

    0
    На первый взгляд выглядит довольно просто. Открывая статью, ожидал, что будет много плясок с бубном. Пробовали подключать что-нибудь более сложное, чем add(a+b) и были ли проблемы с этим?

      +1
      пробывал пускать кучу горутин и каждая шла в сеть. работало на тестах. есть стандартная библиотека так что можно много что делать.
      0
      до выхода 1.5 писал такие shared library на Rust (правда для python), всё работало, всё классно, но Rust лично мне показался несколько сложным
      сейчас же с выходом 1.5 удовольствием перешёл на Go и справляется со своими задачами на отлично
      например, недавно переписывал долго работающие тесты на Go c использование горутин — проблем с использованием такой shared library в Python не возникло
        0
        как с памятью? Что у вас были за задачи? Мы сейчас думаем раст в эрланг воткнуть.
          +1
          Задачи в основном:
          1) мониторинг запущенных процессов
          2) задачи с параллельными вычилениями
          3) какие-то ресурсоемкие вычисления
          Rust хорошо с этим все справлялся, но
          1) он сложнее, чем Go (но более гибкий и мощный)
          2) более нестабилен и неясно «выстрелит» или нет
          3) намного меньше библиотек

          А поскольку Rust в основном ради shared library использовал, то с Go 1.5 мой выбор стал очевиден

          Единственное в Rust очень удобная штука Cargo

          По памяти ни в Rust ни в Go проблем не было, но Rust конечно лучше в плане управления памятью
          Если для Вас это важно, выбирайте Rust
        –1
        Интересно, что-то вспомнились ассемблерные вставки в Delphi, похоже пора учить go…
          0
          А без FFI? где .a файл?
            0
            в Go много buildmode, так что можно и .a ;)

            Я думаю можно и gem написать на Go, потому что модуль для Python на Go пишется достаточно легко
            0
            А сборка мусора при этом тоже работает? Интересно, как это работает…

            P.S.
            Это должно содержать main.

            Шедевральный перевод.
              –4
              это самое интересное. В rust «сборка мусора» на этапе компиляции происходит. А тут то как?
              0
              из данного документа Go Execution Modes

              That is, we must disable linker garbage collection of variables when -buildmode=c-archive, shared, c-shared, or plugin. We must also disable linker garbage collection of variables when the plugin package is imported
                0
                Если я правильно понел это когда подключать надо несколько packages "Multiples copies of a Go package"
              +1
              Со строками пришлось чуток вывернуться, они не совсем очевидно передаются (хорошо есть заголовочный файл):

              typedef struct { char *p; GoInt n; } GoString;
              extern void echo(GoString p0);
              

              module Go
                class String < FFI::Struct
                  layout :p, :pointer,
                         :n, :int
                end
              
                attach_function :echo, [Go::String.by_value], :void
              
              # ... и потом где-то
                def self.do_echo(text)
                  btext = text.force_encoding 'binary'
              
                  str = Go::String.new
                  str[:p] = FFI::MemoryPointer.from_string(btext)
                  str[:n] = btext.size
                  self.echo str
              
                end
              
              end
              


              Пока только не получилось прокинуть callback во внутрь, seg fault постоянно, надо подробней почитать документацию будет.

              Only users with full accounts can post comments. Log in, please.