From: takashikkbn@... Date: 2017-12-27T09:07:23+00:00 Subject: [ruby-core:84497] [Ruby trunk Feature#12589] VM performance improvement proposal Issue #12589 has been updated by k0kubun (Takashi Kokubun). Thank you for sharing your thoughts and support. > So I support your proposal but I guess you should get other ruby developer opinions, especially Koichi's one. I did not check your code. Today I got code review from Koichi-san and mame-san. We found potential bugs in exception handling and TracePoint support, but it's considered to be not so hard to fix in our discussion. And Koichi said: After fixing it and confirming that all tests pass on a mode that forces to synchronously compile all ISeqs and allows to compile infinite ISeqs, we can merge it. > I am not sure that MJIT in your pull request will work for all platforms (I used pthreads but to make it more portable MRI thread implementation should be used. Also how MJIT should work on Windows is a question for me). I fixed pthread part to use Windows native thread for Windows. So it can be compiled on mswin64. At initial merge I'm not going to support cl.exe (this is recognized by mswin64 maintainer). I'm going to fix mjit_init to disable MJIT if a compiler is not found especially for mswin64, but it'll be the only change for platforms before merge. I have an idea for supporting cl.exe and it'll be done in early stage after merge. I understand the mswin64 support allows us to cover all platforms that had been considered as tier1 in https://bugs.ruby-lang.org/projects/ruby-trunk/wiki/SupportedPlatforms (but tierX information is dropped recently). I believe tier2 ones work too (at least I confirmed it works on MinGW by https://github.com/vnmakarov/ruby/pull/4 work). So I think it should be sufficient for now. > Moreover YARV-MJIT might be a final solution. Although C code generated from RTL is better optimized by a C compiler and RTL is also more convenient for future optimizations in MRI itself, still stack insns can be optimized too although with more efforts and in a less effective way. So if I find that stack insns -> RTL translation has some serious problems, your approach can become a mainstream and I might switch to work on it too. I see. I'll continue to improve YARV-MJIT for the case to have serious problems in stack insns -> RTL, but I'm willing to see your new version of JIT compiler as I (and probably many Ruby users) want faster Ruby and I'm interested in technical differences between stack and RTL. I'll keep the mjit_compile function easy to replace. > Right now I see that possible potential problems with stack insn -> RTL approach are big compilation time and the interpretation speed. > > I am still working on RTL generation from stack insns. It is already a second version. It became a multipass algorithm because I need to provide the same state of emulated stack (depth and locations of the insn operands) on different CFG paths (it is a typical forward dataflow problem in compilers). So the translator might be slower than I originally thought. Interesting. Understanding your ideas and strategies from your code is always valuable experience, so I want to read it when it becomes ready to publish. Thank you for sharing the state. I hope merging the patch will help your MJIT development by reducing the cost to rebase against trunk. Once it's merged, let's use the same MJIT infrastructure and please send patches you want to include to upstream for "working step by step" anytime. ---------------------------------------- Feature #12589: VM performance improvement proposal https://bugs.ruby-lang.org/issues/12589#change-69023 * Author: vmakarov (Vladimir Makarov) * Status: Open * Priority: Normal * Assignee: * Target version: ---------------------------------------- Hello. I'd like to start a big MRI project but I don't want to disrupt somebody else plans. Therefore I'd like to have MRI developer's opinion on the proposed project or information if somebody is already working on an analogous project. Basically I want to improve overall MRI VM performance: * First of all, I'd like to change VM insns and move from **stack-based** insns to **register transfer** ones. The idea behind it is to decrease VM dispatch overhead as approximately 2 times less RTL insns are necessary than stack based insns for the same program (for Ruby it is probably even less as a typical Ruby program contains a lot of method calls and the arguments are passed through the stack). But *decreasing memory traffic* is even more important advantage of RTL insns as an RTL insn can address temporaries (stack) and local variables in any combination. So there is no necessity to put an insn result on the stack and then move it to a local variable or put variable value on the stack and then use it as an insn operand. Insns doing more also provide a bigger scope for C compiler optimizations. The biggest changes will be in files compile.c and insns.def (they will be basically rewritten). **So the project is not a new VM machine. MRI VM is much more than these 2 files.** The disadvantage of RTL insns is a bigger insn memory footprint (which can be upto 30% more) although as I wrote there are fewer number of RTL insns. Another disadvantage of RTL insns *specifically* for Ruby is that insns for call sequences will be basically the same stack based ones but only bigger as they address the stack explicitly. * Secondly, I'd like to **combine some frequent insn sequences** into bigger insns. Again it decreases insn dispatch overhead and memory traffic even more. Also it permits to remove some type checking. The first thing on my mind is a sequence of a compare insn and a branch and using immediate operands besides temporary (stack) and local variables. Also it is not a trivial task for Ruby as the compare can be implemented as a method. I already did some experiments. RTL insns & combining insns permits to speed the following micro-benchmark in more 2 times: ``` i = 0 while i<30_000_000 # benchmark loop 1 i += 1 end ``` The generated RTL insns for the benchmark are ``` == disasm: #@while.rb>====================================== == catch table | catch type: break st: 0007 ed: 0020 sp: 0000 cont: 0020 | catch type: next st: 0007 ed: 0020 sp: 0000 cont: 0005 | catch type: redo st: 0007 ed: 0020 sp: 0000 cont: 0007 |------------------------------------------------------------------------ local table (size: 2, temp: 1, argc: 0 [opts: 0, rest: -1, post: 0, block: -1, kw: -1@-1, kwrest: -1]) [ 2] i 0000 set_local_val 2, 0 ( 1) 0003 jump 13 ( 2) 0005 jump 13 0007 plusi , 2, 2, 1, -1 ( 3) 0013 btlti 7, , -1, 2, 30000000, -1 ( 2) 0020 local_ret 2, 0 ( 3) ``` In this experiment I ignored trace insns (that is another story) and a complication that a integer compare insn can be re-implemented as a Ruby method. Insn bflti is combination of LT immediate compare and branch true. A modification of fib benchmark is sped up in 1.35 times: ``` def fib_m n if n < 1 1 else fib_m(n-1) * fib_m(n-2) end end fib_m(40) ``` The RTL code of fib_m looks like ``` == disasm: #========================================== local table (size: 2, temp: 3, argc: 1 [opts: 0, rest: -1, post: 0, block: -1, kw: -1@-1, kwrest: -1]) [ 2] n 0000 bflti 10, , -1, 2, 1, -1 ( 2) 0007 val_ret 1, 16 0010 minusi , -2, 2, 1, -2 ( 5) 0016 simple_call_self , , -1 0020 minusi , -3, 2, 2, -3 0026 simple_call_self , , -2 0030 mult , -1, -1, -2, -1 0036 temp_ret -1, 16 ``` In reality, the improvement of most programs probably will be about 10%. That is because of very dynamic nature of Ruby (a lot of calls, checks for redefinition of basic type operations, checking overflows to switch to GMP numbers). For example, integer addition can not be less than about x86-64 17 insns out of the current 50 insns on the fast path. So even if you make the rest (33) insns 2 times faster, the improvement will be only 30%. A very important part of MRI performance improvement is to make calls fast because there are a lot of them in Ruby but as I read in some Koichi Sasada's presentations he pays a lot of attention to it. So I don't want to touch it. * Thirdly. I want to implement the insns as small inline functions for future AOT compiler, of course, if the projects described above are successful. It will permit easy AOT generation of C code which will be basically calls of the functions. I'd like to implement AOT compiler which will generate a Ruby method code, call a C compiler to generate a binary shared code and load it into MRI for subsequent calls. The key is to minimize the compilation time. There are many approaches to do it but I don't want to discuss it right now. C generation is easy and most portable implementation of AOT but in future it is possible to use GCC JIT plugin or LLVM IR to decrease overhead of C scanner/parser. C compiler will see a bigger scope (all method insns) to do optimizations. I think using AOT can give another 10% improvement. It is not that big again because of dynamic nature of Ruby and any C compiler is not smart enough to figure out aliasing for typical generated C program. The life with the performance point of view would be easy if Ruby did not permit to redefine basic operations for basic types, e.g. plus for integer. In this case we could evaluate types of operands and results using some data flow analysis and generate faster specialized insns. Still a gradual typing if it is introduced in future versions of Ruby would help to generate such faster insns. Again I wrote this proposal for discussion as I don't want to be in a position to compete with somebody else ongoing big project. It might be counterproductive for MRI development. Especially I don't want it because the project is big and long and probably will have a lot of tehcnical obstacles and have a possibilty to be a failure. -- https://bugs.ruby-lang.org/ Unsubscribe: