virtual_module 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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: []
|