virtual_module 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
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,10 @@
1
+ function +(x::ASCIIString, y::ASCIIString)
2
+ string(x, y)
3
+ end
4
+
5
+ function *(x::ASCIIString, y::Int)
6
+ repeated = map(1:3) do i
7
+ x
8
+ end
9
+ join(repeated, "")
10
+ end
@@ -0,0 +1,3 @@
1
+ module VirtualModule
2
+ VERSION = "0.1.0"
3
+ end
@@ -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: []