virtual_module 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/README.md +28 -0
- data/lib/virtual_module/bridge.jl +10 -0
- data/lib/virtual_module/version.rb +3 -0
- data/lib/virtual_module.rb +297 -0
- metadata +104 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 4bfab3d164f02ef899d40766d55420467ece2e23
|
4
|
+
data.tar.gz: 8f125f80cc967b5e40fa59879a53dcb97bbca308
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: ec550f409511efd94ecaf381a5f5e1e73ba6c1000a0d663f142dc7582d487db8ee2cb04c51c7b709c81dd3b3416f5a1a8c1cd206a740fad4f9787e5d0b80c364
|
7
|
+
data.tar.gz: 40a0f820f59e823896b9b20b4b1c4c85f1f4c05902b2692e37c2c6821004831298834a4d1712b209cff6afba8c73da08f174d38ba5fbad18fd83f4230c9b8532
|
data/README.md
ADDED
@@ -0,0 +1,28 @@
|
|
1
|
+
## VirtualModule
|
2
|
+
|
3
|
+
If you feel good with Ruby's language design but *totally* disappointed with Ruby's computational performance such as terribly slow speed of loop or Math functions, then VirtualModule may save you a little.
|
4
|
+
|
5
|
+
With VirtualModule, you can run your Ruby code on the other language VM process(currently only Julia is supported).
|
6
|
+
|
7
|
+
```
|
8
|
+
vm = VirtualModule.new(<<EOS)
|
9
|
+
def hi
|
10
|
+
"ho"
|
11
|
+
end
|
12
|
+
|
13
|
+
# @param [Fixnum] num
|
14
|
+
def hello(num)
|
15
|
+
num*num
|
16
|
+
end
|
17
|
+
EOS
|
18
|
+
p vm.hi #"ho"
|
19
|
+
include vm
|
20
|
+
p hello(33) #1089
|
21
|
+
```
|
22
|
+
|
23
|
+
## Other explanation
|
24
|
+
|
25
|
+
TBC
|
26
|
+
|
27
|
+
## License
|
28
|
+
MIT
|
@@ -0,0 +1,297 @@
|
|
1
|
+
require 'msgpack'
|
2
|
+
require 'msgpack-rpc'
|
3
|
+
require 'ripper'
|
4
|
+
require 'binding_of_caller'
|
5
|
+
require 'julializer'
|
6
|
+
require 'open3'
|
7
|
+
require 'tmpdir'
|
8
|
+
|
9
|
+
module VirtualModule
|
10
|
+
require 'virtual_module/version'
|
11
|
+
|
12
|
+
class << self
|
13
|
+
def new(methods, config={})
|
14
|
+
if !config.is_a?(BaseBuilder)
|
15
|
+
config[:lang] = :julia if config[:lang].nil?
|
16
|
+
vm_builder = instance_eval("#{config[:lang].to_s.capitalize}Builder").new(config)
|
17
|
+
else
|
18
|
+
vm_builder = config
|
19
|
+
end
|
20
|
+
vm_builder.add(methods)
|
21
|
+
vm_builder.build
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
class BaseBuilder
|
26
|
+
def initialize(config={})
|
27
|
+
@source = []
|
28
|
+
if !config.is_a?(BaseIpcInterface)
|
29
|
+
props = {:ipc=>:file, :work_dir=>nil}
|
30
|
+
props.each do |k,v|
|
31
|
+
instance_variable_set("@#{k}", config[k] || v)
|
32
|
+
end
|
33
|
+
@ipc = instance_eval("#{@ipc.to_s.capitalize}IpcInterface").new(props.merge(config))
|
34
|
+
else
|
35
|
+
@ipc = config
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
def add(methods)
|
40
|
+
@source << methods
|
41
|
+
@ipc.reset get_compiled_code
|
42
|
+
end
|
43
|
+
|
44
|
+
def build
|
45
|
+
vm_builder = self
|
46
|
+
ipc = @ipc
|
47
|
+
vm = Module.new{
|
48
|
+
@vm_builder = vm_builder
|
49
|
+
@ipc = ipc
|
50
|
+
def self.virtual_module_eval(*args)
|
51
|
+
@vm_builder.send(:virtual_module_eval, *args)
|
52
|
+
end
|
53
|
+
def self.method_missing(key, *args, &block)
|
54
|
+
@vm_builder.send(:call, key, *args)
|
55
|
+
end
|
56
|
+
}
|
57
|
+
extract_defs(Ripper.sexp(@source.join(";"))).split(",").each{|e|
|
58
|
+
vm.class_eval {
|
59
|
+
define_method e.to_sym, Proc.new { |*args|
|
60
|
+
vm_builder.call(e.to_sym, *args)
|
61
|
+
}
|
62
|
+
}
|
63
|
+
}
|
64
|
+
vm
|
65
|
+
end
|
66
|
+
|
67
|
+
def call(name, *args)
|
68
|
+
Dir.mktmpdir(nil, Dir.home) do |tempdir|
|
69
|
+
@work_dir = @ipc.work_dir = tempdir
|
70
|
+
@ipc.call(name, *args)
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
private
|
75
|
+
def extract_defs(s)
|
76
|
+
if s.instance_of?(Array) && s[0].instance_of?(Symbol) then
|
77
|
+
if [:def].include?(s[0])
|
78
|
+
"#{s[1][1]},"
|
79
|
+
else
|
80
|
+
s.map{|e| extract_defs(e)}.join
|
81
|
+
end
|
82
|
+
elsif s.instance_of?(Array) && s[0].instance_of?(Array) then
|
83
|
+
s.map{|e| extract_defs(e)}.join
|
84
|
+
end
|
85
|
+
end
|
86
|
+
|
87
|
+
def extract_args(s)
|
88
|
+
if s.instance_of?(Array) && s[0].instance_of?(Symbol) then
|
89
|
+
if [:vcall, :var_field].include?(s[0])
|
90
|
+
"#{s[1][1]},"
|
91
|
+
else
|
92
|
+
s.map{|e| extract_args(e)}.join
|
93
|
+
end
|
94
|
+
elsif s.instance_of?(Array) && s[0].instance_of?(Array) then
|
95
|
+
s.map{|e| extract_args(e)}.join
|
96
|
+
end
|
97
|
+
end
|
98
|
+
|
99
|
+
def inspect_local_vars(context, script)
|
100
|
+
vars = extract_args(Ripper.sexp(script)).split(",").uniq.map{|e| e.to_sym} & context.eval("local_variables")
|
101
|
+
#p "vars=#{vars}"
|
102
|
+
|
103
|
+
type_info = {}
|
104
|
+
type_info[:params] = context.eval("Hash[ *#{vars}.collect { |e| [ e, eval(e.to_s).class.to_s ] }.flatten ]").select{|k,v| ["FloatArray", "IntArray"].include?(v)}
|
105
|
+
#p "type_info=#{type_info}"
|
106
|
+
|
107
|
+
params = context.eval(<<EOS)
|
108
|
+
require 'msgpack'
|
109
|
+
___params = {}
|
110
|
+
___params = local_variables.map{|e| [e, eval(e.to_s)]}.each{|e| ___params[e[0]]=e[1] if #{vars}.include?(e[0])}[0][1]
|
111
|
+
___params
|
112
|
+
EOS
|
113
|
+
#File.write("#{@work_dir}.polykit.params", params)
|
114
|
+
[vars, type_info, params]
|
115
|
+
end
|
116
|
+
end
|
117
|
+
|
118
|
+
|
119
|
+
class JuliaBuilder < BaseBuilder
|
120
|
+
def virtual_module_eval(script, auto_binding=true)
|
121
|
+
vars, type_info, params = inspect_local_vars(binding.of_caller(2), script)
|
122
|
+
boot_script = <<EOS
|
123
|
+
function ___convert_type(name, typename, params)
|
124
|
+
if length(findin(#{type_info[:params].keys.map{|e| e.to_s}},[name]))>0
|
125
|
+
if typename=="FloatArray"
|
126
|
+
convert(Array{Float64,1}, params[name])
|
127
|
+
elseif typename=="IntArray"
|
128
|
+
convert(Array{Int64,1}, params[name])
|
129
|
+
end
|
130
|
+
else
|
131
|
+
params[name]
|
132
|
+
end
|
133
|
+
end
|
134
|
+
|
135
|
+
function ___main(params, type_info)
|
136
|
+
#{vars.map{|e| e.to_s + '=___convert_type("'+e.to_s+'","'+(type_info[:params][e]||"")+'", params)'}.join(";")}
|
137
|
+
##{vars.map{|e| 'println("'+e.to_s+'=", typeof('+e.to_s+'))' }.join(";")}
|
138
|
+
___evaluated = #{Julializer.ruby2julia(script)}
|
139
|
+
|
140
|
+
#{vars.map{|e| 'params["'+e.to_s+'"]='+e.to_s }.join(";") if auto_binding}
|
141
|
+
|
142
|
+
(___evaluated,#{auto_binding ? "params" : "-1" })
|
143
|
+
end
|
144
|
+
EOS
|
145
|
+
|
146
|
+
# File.write("#{@work_dir}.polykit.rb", <<EOS)
|
147
|
+
# require 'msgpack-rpc'
|
148
|
+
# client = MessagePack::RPC::Client.new('127.0.0.1', #{@port})
|
149
|
+
# client.timeout = #{@timeout}
|
150
|
+
# p client.call(:___main, #{params}, #{type_info})
|
151
|
+
#EOS
|
152
|
+
|
153
|
+
@ipc.reset get_compiled_code + ";#{boot_script}"
|
154
|
+
evaluated = self.call(:___main, params, type_info)
|
155
|
+
if auto_binding
|
156
|
+
binding.of_caller(2).eval(evaluated[1].map{|k,v| "#{k}=#{v};" if !v.nil? }.join)
|
157
|
+
end
|
158
|
+
|
159
|
+
return evaluated[0]
|
160
|
+
end
|
161
|
+
|
162
|
+
alias_method :virtual_instance_eval, :virtual_module_eval
|
163
|
+
alias_method :virtual_eval, :virtual_module_eval
|
164
|
+
|
165
|
+
private
|
166
|
+
def get_compiled_code
|
167
|
+
File.read(File.dirname(__FILE__)+"/virtual_module/bridge.jl") + ";" + Julializer.ruby2julia(@source.join(";\n"))
|
168
|
+
end
|
169
|
+
|
170
|
+
end
|
171
|
+
|
172
|
+
class BaseIpcInterface
|
173
|
+
INPUT_ARGS = "virtualmodule-input.msgpack"
|
174
|
+
OUTPUT_ARGS = "virtualmodule-output.msgpack"
|
175
|
+
ENTRYPOINT_SCRIPT = "virtualmodule-entrypoint.jl"
|
176
|
+
LIB_SCRIPT = "virtualmodule-lib.jl"
|
177
|
+
|
178
|
+
attr_accessor :work_dir
|
179
|
+
|
180
|
+
def initialize(config)
|
181
|
+
#do nothing
|
182
|
+
end
|
183
|
+
def call(name, *args)
|
184
|
+
#do nothing
|
185
|
+
end
|
186
|
+
def reset(source)
|
187
|
+
#do nothing
|
188
|
+
end
|
189
|
+
end
|
190
|
+
|
191
|
+
class FileIpcInterface < BaseIpcInterface
|
192
|
+
def call(name, *args)
|
193
|
+
File.write("#{@work_dir}/#{LIB_SCRIPT}", @lib_source)
|
194
|
+
File.write("#{@work_dir}/#{INPUT_ARGS}", MessagePack.pack(args))
|
195
|
+
|
196
|
+
if is_installed?(:julia)
|
197
|
+
File.write("#{@work_dir}/#{ENTRYPOINT_SCRIPT}", generate_entrypoint(@work_dir, "#{@work_dir}/#{OUTPUT_ARGS}", name, *args))
|
198
|
+
command = "julia --depwarn=no -L #{@work_dir}/#{LIB_SCRIPT} #{@work_dir}/#{ENTRYPOINT_SCRIPT}"
|
199
|
+
elsif is_installed?(:docker)
|
200
|
+
File.write("#{@work_dir}/#{ENTRYPOINT_SCRIPT}", generate_entrypoint("/opt/", "/opt/#{OUTPUT_ARGS}", name, *args))
|
201
|
+
command = "docker run -v #{@work_dir}/:/opt/ remore/virtual_module julia --depwarn=no -L /opt/virtualmodule-lib.jl /opt/virtualmodule-entrypoint.jl"
|
202
|
+
else
|
203
|
+
raise Exception.new("Either julia or docker command is required to run virtual_module")
|
204
|
+
end
|
205
|
+
out, err, status = Open3.capture3(command)
|
206
|
+
#byebug
|
207
|
+
# FIXME: Only For Debug
|
208
|
+
# printf out, err
|
209
|
+
MessagePack.unpack(File.read("#{@work_dir}/#{OUTPUT_ARGS}"))
|
210
|
+
end
|
211
|
+
|
212
|
+
def reset(source)
|
213
|
+
@lib_source = source
|
214
|
+
end
|
215
|
+
|
216
|
+
private
|
217
|
+
def is_installed?(command)
|
218
|
+
Open3.capture3("which #{command.to_s}")[0].size > 0
|
219
|
+
end
|
220
|
+
|
221
|
+
def generate_entrypoint(basedir, target, name, *args)
|
222
|
+
if args.count>0
|
223
|
+
script =<<EOS
|
224
|
+
using MsgPack
|
225
|
+
params = open( "#{basedir}/#{INPUT_ARGS}", "r" ) do fp
|
226
|
+
readall( fp )
|
227
|
+
end
|
228
|
+
result = #{name}(unpack(params)...)
|
229
|
+
EOS
|
230
|
+
else
|
231
|
+
script =<<EOS
|
232
|
+
using MsgPack
|
233
|
+
result = #{name}()
|
234
|
+
EOS
|
235
|
+
end
|
236
|
+
|
237
|
+
script + ";" + <<EOS
|
238
|
+
open( "#{target}", "w" ) do fp
|
239
|
+
write( fp, pack(result) )
|
240
|
+
end
|
241
|
+
EOS
|
242
|
+
end
|
243
|
+
|
244
|
+
end
|
245
|
+
|
246
|
+
class RpcIpcInterface < BaseIpcInterface
|
247
|
+
def initialize(config={})
|
248
|
+
init_connection
|
249
|
+
props = {:server=>"127.0.0.1", :port=>8746, :timeout=>10}
|
250
|
+
props.each do |k,v|
|
251
|
+
instance_variable_set("@#{k}", config[k] || v)
|
252
|
+
end
|
253
|
+
end
|
254
|
+
|
255
|
+
def call(name, *args)
|
256
|
+
restart_server_process @lib_source
|
257
|
+
while `echo exit | telnet #{@server} #{@port} 2>&1`.chomp[-5,5]!="host." do
|
258
|
+
sleep(0.05)
|
259
|
+
end
|
260
|
+
@client = MessagePack::RPC::Client.new(@server, @port) if @client.nil?
|
261
|
+
@client.timeout = @timeout
|
262
|
+
args.count>0 ? @client.call(name, *args) : @client.call(name)
|
263
|
+
end
|
264
|
+
|
265
|
+
def reset(source)
|
266
|
+
@lib_source = source
|
267
|
+
end
|
268
|
+
|
269
|
+
private
|
270
|
+
def init_connection
|
271
|
+
@pid = nil
|
272
|
+
@client.close if !@client.nil?
|
273
|
+
@client = nil
|
274
|
+
end
|
275
|
+
|
276
|
+
def restart_server_process(source)
|
277
|
+
Process.kill(:TERM, @pid) if !@pid.nil?
|
278
|
+
`lsof -wni tcp:#{@port} | cut -f 4 -d ' ' | sed -ne '2,$p' | xargs kill -9`
|
279
|
+
init_connection
|
280
|
+
compiled = <<EOS
|
281
|
+
import MsgPackRpcServer
|
282
|
+
module RemoteFunctions
|
283
|
+
#{source}
|
284
|
+
end
|
285
|
+
MsgPackRpcServer.run(#{@port}, RemoteFunctions)
|
286
|
+
EOS
|
287
|
+
File.write("#{@work_dir}/#{LIB_SCRIPT}", compiled)
|
288
|
+
@pid = Process.spawn("julia --depwarn=no #{@work_dir}/#{LIB_SCRIPT}")
|
289
|
+
end
|
290
|
+
|
291
|
+
at_exit do
|
292
|
+
@client.close if !@client.nil?
|
293
|
+
Process.kill(:TERM, @pid) if !@pid.nil?
|
294
|
+
end
|
295
|
+
end
|
296
|
+
|
297
|
+
end
|
metadata
ADDED
@@ -0,0 +1,104 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: virtual_module
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Kei Sawada(@remore)
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2016-08-24 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: msgpack
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - ">="
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '0'
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - ">="
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '0'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: msgpack-rpc
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - ">="
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '0'
|
34
|
+
type: :runtime
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - ">="
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '0'
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: binding_of_caller
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - ">="
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '0'
|
48
|
+
type: :runtime
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - ">="
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '0'
|
55
|
+
- !ruby/object:Gem::Dependency
|
56
|
+
name: julializer
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - ">="
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '0'
|
62
|
+
type: :runtime
|
63
|
+
prerelease: false
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - ">="
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: '0'
|
69
|
+
description: Born to make your Ruby Code 3x faster. Hopefully.
|
70
|
+
email:
|
71
|
+
- k@swd.cc
|
72
|
+
executables: []
|
73
|
+
extensions: []
|
74
|
+
extra_rdoc_files: []
|
75
|
+
files:
|
76
|
+
- README.md
|
77
|
+
- lib/virtual_module.rb
|
78
|
+
- lib/virtual_module/bridge.jl
|
79
|
+
- lib/virtual_module/version.rb
|
80
|
+
homepage: https://github.com/remore/virtual_module
|
81
|
+
licenses:
|
82
|
+
- MIT
|
83
|
+
metadata: {}
|
84
|
+
post_install_message:
|
85
|
+
rdoc_options: []
|
86
|
+
require_paths:
|
87
|
+
- lib
|
88
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
89
|
+
requirements:
|
90
|
+
- - ">="
|
91
|
+
- !ruby/object:Gem::Version
|
92
|
+
version: 1.9.1
|
93
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
94
|
+
requirements:
|
95
|
+
- - ">="
|
96
|
+
- !ruby/object:Gem::Version
|
97
|
+
version: '0'
|
98
|
+
requirements: []
|
99
|
+
rubyforge_project:
|
100
|
+
rubygems_version: 2.5.1
|
101
|
+
signing_key:
|
102
|
+
specification_version: 4
|
103
|
+
summary: Born to make your Ruby Code 3x faster. Hopefully.
|
104
|
+
test_files: []
|