С выходом 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 тут.

Комментарии (15)


  1. Kroid
    03.09.2015 14:48

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


    1. sergeylanz
      03.09.2015 18:57
      +1

      пробывал пускать кучу горутин и каждая шла в сеть. работало на тестах. есть стандартная библиотека так что можно много что делать.


  1. andrewnester
    03.09.2015 16:59

    до выхода 1.5 писал такие shared library на Rust (правда для python), всё работало, всё классно, но Rust лично мне показался несколько сложным
    сейчас же с выходом 1.5 удовольствием перешёл на Go и справляется со своими задачами на отлично
    например, недавно переписывал долго работающие тесты на Go c использование горутин — проблем с использованием такой shared library в Python не возникло


    1. erlyvideo
      03.09.2015 22:22

      как с памятью? Что у вас были за задачи? Мы сейчас думаем раст в эрланг воткнуть.


      1. andrewnester
        03.09.2015 23:37
        +1

        Задачи в основном:
        1) мониторинг запущенных процессов
        2) задачи с параллельными вычилениями
        3) какие-то ресурсоемкие вычисления
        Rust хорошо с этим все справлялся, но
        1) он сложнее, чем Go (но более гибкий и мощный)
        2) более нестабилен и неясно «выстрелит» или нет
        3) намного меньше библиотек

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

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

        По памяти ни в Rust ни в Go проблем не было, но Rust конечно лучше в плане управления памятью
        Если для Вас это важно, выбирайте Rust


  1. yul
    03.09.2015 19:14
    -1

    Интересно, что-то вспомнились ассемблерные вставки в Delphi, похоже пора учить go…


  1. crackedmind
    03.09.2015 23:31

    А без FFI? где .a файл?


    1. andrewnester
      03.09.2015 23:39

      в Go много buildmode, так что можно и .a ;)

      Я думаю можно и gem написать на Go, потому что модуль для Python на Go пишется достаточно легко


  1. QtRoS
    04.09.2015 11:18

    А сборка мусора при этом тоже работает? Интересно, как это работает…

    P.S.

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

    Шедевральный перевод.


    1. erlyvideo
      04.09.2015 11:59
      -4

      это самое интересное. В rust «сборка мусора» на этапе компиляции происходит. А тут то как?


      1. andrewnester
        04.09.2015 12:19

        как это сборка мусора на этапе компиляции?

        у rust не GC вообще
        doc.rust-lang.org/complement-design-faq.html

        а вот как он работает с памятью
        doc.rust-lang.org/book/the-stack-and-the-heap.html


        1. erlyvideo
          04.09.2015 20:20
          -2

          а так. У раста нет операций по явному удалению данных, значит есть сборка мусора. Просто она очень похожа на RAII


    1. andrewnester
      04.09.2015 12:06

      из данного документа 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


      1. sergeylanz
        04.09.2015 14:19

        Если я правильно понел это когда подключать надо несколько packages "Multiples copies of a Go package"


  1. fuCtor
    06.09.2015 17:08
    +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 постоянно, надо подробней почитать документацию будет.