1. Если у вас есть готовое решение проблемы на go, то не зачем его заново писать на Ruby;
2. Go работает явно быстрее Ruby если мы говорим про свою логику а не готовые решения с gem в которых часто работает C;
3. Go требует меньше памяти если вам надо работать с кучей данный.
Начнем с Go package.
Пишем наше быстрое хорошое рабочее решения на 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:
require 'ffi'
module MegaSum
extend FFI::Library
ffi_lib 'lib/libadd.so'
attach_function :add, [:int, :int], :int
end
Тут мы указываем где лежит наш .so файл, какие у него есть функции вызовы (на которых мы написали "//export"), что они принимают и что возвращают(полный список типов можно посмотреть на тут). После этого можно работать:
def self.add_go(x,y)
Sum.add(x,y)
end
Первый вызов будет немного медленный (наверно загружает все в память).
Benchmarks!
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)
andrewnester
03.09.2015 16:59до выхода 1.5 писал такие shared library на Rust (правда для python), всё работало, всё классно, но Rust лично мне показался несколько сложным
сейчас же с выходом 1.5 удовольствием перешёл на Go и справляется со своими задачами на отлично
например, недавно переписывал долго работающие тесты на Go c использование горутин — проблем с использованием такой shared library в Python не возниклоerlyvideo
03.09.2015 22:22как с памятью? Что у вас были за задачи? Мы сейчас думаем раст в эрланг воткнуть.
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
yul
03.09.2015 19:14-1Интересно, что-то вспомнились ассемблерные вставки в Delphi, похоже пора учить go…
crackedmind
03.09.2015 23:31А без FFI? где .a файл?
andrewnester
03.09.2015 23:39в Go много buildmode, так что можно и .a ;)
Я думаю можно и gem написать на Go, потому что модуль для Python на Go пишется достаточно легко
QtRoS
04.09.2015 11:18А сборка мусора при этом тоже работает? Интересно, как это работает…
P.S.
Это должно содержать main.
Шедевральный перевод.erlyvideo
04.09.2015 11:59-4это самое интересное. В rust «сборка мусора» на этапе компиляции происходит. А тут то как?
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.htmlerlyvideo
04.09.2015 20:20-2а так. У раста нет операций по явному удалению данных, значит есть сборка мусора. Просто она очень похожа на RAII
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
sergeylanz
04.09.2015 14:19Если я правильно понел это когда подключать надо несколько packages "Multiples copies of a Go package"
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 постоянно, надо подробней почитать документацию будет.
Kroid
На первый взгляд выглядит довольно просто. Открывая статью, ожидал, что будет много плясок с бубном. Пробовали подключать что-нибудь более сложное, чем add(a+b) и были ли проблемы с этим?
sergeylanz
пробывал пускать кучу горутин и каждая шла в сеть. работало на тестах. есть стандартная библиотека так что можно много что делать.