16 Replies Latest reply on Mar 25, 2015 4:48 PM by Mark Nongkhlaw

    Why is ruby so slow vs. Javascript in Rhodes application ?

    Thierry Leloup

      Hello everybody,

       

      We are developing a Rhodes application with a lot of images processing and we had to do some calculations in Javascript because our graphics lib is a Javascript one.

      As these calculations took too much time for us, we tried to implement a basic benchmark test in Ruby in order to compare calculation performance.

       

      The benchmark consists in building 2 matrices (1000x1000) with random data and build a third one with a simple M3(x,y) = M1(x,y)*M2(x,y)/255.

       

      Here are the results we got with Rhodes 5.0.30 :

      Rhosimulator : Ruby : 194 ms, Javascript : 58 ms

      Android : Ruby : 871 ms, Javascript : 259 ms

      Windows 8.1 : Ruby : 1801 ms, Javascript : 426 ms

      iPhone6 : Ruby : 1700 ms (!!!), Javascript : 134 ms

       

      We really thought Ruby should be faster as, if we understand well, Ruby code is compiled into Ruby-bytecode and then executed by the Rhodes rubyVM interpreter.

       

      So my question is : is Javascript really always faster than Ruby in a Rhodes application and does it mean we should always prefer Javascript when it comes to massive calculation ?

       

      Thanks,

      Thierry

        • Re: Why is ruby so slow vs. Javascript in Rhodes application ?
          Robert Galvin

          Hi Thierry,

           

          Any chance you can share your app code? I am trying to better understand exactly how you are performing the test?  What version of Android were you using? Was RhoSim running on Mac or Windows?

            • Re: Re: Why is ruby so slow vs. Javascript in Rhodes application ?
              Thierry Leloup

              Hi Rob,

               

              We have tested with Android 4.4 and Rhosimulator was running on a Mac Book Pro (i7/8GB).

               

              Our javascript (size = 1,000,000):

              function testLoop(size) {

                var firstArray = [], secondArray = [];

                for (var i = 0; i < size; i++) {

                firstArray[i] = Math.round(Math.random() * 255);

                secondArray[i] = Math.round(Math.random() * 255);

                }

               

                var resultArray = [];

               

                var begin = Date.now();

                for (var i = 0; i < size; i++) {

                resultArray[i] = firstArray[i] * secondArray[i] / 255;

                }

               

                consoleLog("[JavaScript] Duration with array of " + size + " elements : " + (Date.now() - begin));

              }

               

              ======================================================================

              And Ruby code (size = 1,000,000) :

              def testLoop

                @size = @params['size'] || 100
                if @size.is_a? String
                @size = @size.to_i
                end

                firstArray = Array.new(@size) {|i| (rand()*255).round() }

                secondArray = Array.new(@size) {|i| (rand()*255).round() }

               

                resultArray = Array.new(@size)

               

                beginDur = Time.now

                for i in 0..@size-1
                resultArray[i] = firstArray[i] * secondArray[i] / 255
                end

                duration = Time.now - beginDur
                Rho::Log.info("[Ruby] Duration with array of #{@size} elements : #{duration}", "DEV")

               

              end

                • Re: Why is ruby so slow vs. Javascript in Rhodes application ?
                  Jon Tara

                  Did you test the Javascript in Rhodes, or in the system browser? The system browser on all of these platforms does JIT-compile to real machine code... So, make sure you do you JS tests inside of a Rhodes app to compare apples with apples!

                   

                  Have you tried any standard benchmark on both?

                   

                  Aside: On iOS, (8.0+) WKWebView will now JIT-compile JS as well. But Rhodes doesn't have it yet. It still uses only UIWebView, which does not JIT-compile. So, in the future, you may get even better math performance in JS on iOS.

                   

                  If you have a specific need that is worth the effort, you could drop down to C (which is available on every Rhodes platform) and write a Rhodes extension to do your math.

                  • Re: Why is ruby so slow vs. Javascript in Rhodes application ?
                    Jon Tara

                    Oh, I see from your first post, you are doing image processing. And you are using some JS graphics library, and finding the performance lacking, so hoping you might do it faster with Ruby...

                     

                    It's unclear what you mean by "some calculations". Is there some specific image-processing algorithm you need to use?

                     

                    All modern platforms have great native image-processing libraries built-in that will leverage the graphics processor chip. So, it might be worth-while for you to write a native extension to make use of these. But I don't know how many platforms you need to support - it might be a pain if you have to support several!

                     

                    This would likely give you hundreds of times your current performance.

                    • Re: Why is ruby so slow vs. Javascript in Rhodes application ?
                      Robert Galvin

                      Does this loop represent how your code is behaving? Or are just using it as a benchmark type of test. As Pietro and Jon mentioned - this may not necessarily indicate true performance. Is there a more specific example/scenario that is exhibited in the application?

                       

                      And actually, is JS is performing better then Ruby and you are using a series of JS libraries, why are you looking at Ruby? Are the JS libs not performing well for you and you want to look for alternatives in Ruby?

                      • Re: Why is ruby so slow vs. Javascript in Rhodes application ?
                        Jon Tara

                        If you need to do matrix operations, you might try Ruby Matrix. It's built-in to the Ruby library, but not included in Rhodes. But it appears to be just two pure Ruby files (unless I am missing some C part) and so it's a 5-minute Rhodes Extension!

                         

                        At least, then, you have some code for common matrix operations that is presumably optimized for Ruby.

                         

                            Class: Matrix (Ruby 1.9.2)

                         

                        I'd make sure to extract it from the same version Ruby used by Rhodes. (1.9.2-p290)

                         

                        I agree that loops might easily perform poorly in Ruby - especially 1.9.2 - vs Javascript. Were there more "meat" inside the loop, I think you will see better performance. 2.x Ruby VM is much better, but I don't know when/if Rhodes get an update.

                         

                        I have some experience with this sort of thing... It seems I'm good at identifying and fixing bottlenecks. Some 30 years ago, I wrote a little compiler and interpreter (the interpreter was written in Fortran!) for running simulations of build of mechanical assemblies. Sure, the interpreter was slow as molasses - literally reading opcodes out of Fortran arrays. And loops were slow! But... most of the real work in a real application was done inside of highly-optimized C code. An opcode, in fact, might do an operation on a matrix. Because it was "moving" (virtual) parts together to make an assembly. so, the fact that reading an opcode or performing a branch or loop was slow, it just didn't matter. Nobody wrote their own math routines in my specialized language. They just wanted to write "move fender to body joining plane ..."

                         

                        I made them hire a real mathematician... I helped him with the C part. He brought the magic heuristics. It wound-up running faster - even with the interpreter - on an IBM PC - than the pure-Fortran code previously ran on a mainframe. That software still exists today (Siemens sells it, and every car manufacturer, etc. uses it).

                         

                        So, the lesson is... figure out where your code really spends most of it's time, and see wha you can do to optimize that part.  And don't sweat the rest! Sometimes you might do some performance measurements to determine that, but in other cases, it is just intuitively obvious.  I've tried to keep that lesson in mind on many projects since then.

                         

                        I would give Ruby Matrix a try. It might give better performance than your own code, or at least give you ideas.

                         

                        Beyond that, if all you need are matrix operations, I would look to port some optimized C library as a Rhodes extension, and then you can use on every platform. Alternately, every platform will have a decent matrix library built-in, and you can expose that as a Rhodes extension, but then you might have some difficulty if the APIs are significantly different on the different platforms.

                         

                        I don't think that porting a C library for this is very scary or difficult, because it does not involve the native platform. You won't have to account for differences between devices. It's not like it's some device feature - it is just math! Rhodes supports C code in extensions on every platform.

                          • Re: Why is ruby so slow vs. Javascript in Rhodes application ?
                            Jon Tara

                            So, for example, here is the code for the Ruby Matrix multiply:

                             

                                def *(other)
                                  case other
                                  when Numeric
                                    Scalar.new(@value * other)
                                  when Vector, Matrix
                                    other.collect{|e| @value * e}
                                  else
                                    apply_through_coercion(other, __method__)
                                  end
                                end
                            
                            

                             

                            You can see it is using collect, rather than looping with a simple Ruby loop. collect() is almost certainly implemented natively in Ruby, using C code. So, there is no Ruby loop at all. That is, it doesn't have the burden of executing bytecode to run the loop.

                             

                            BTW, I see that Matrix got split into two files in 1.9.3. It's actually just a single file in 1.9.2. This is something where you might get away with using some newer code, since it is just pure math. I'd start with the 1.9.2-p290 version, though.

                            • Re: Why is ruby so slow vs. Javascript in Rhodes application ?
                              Thierry Leloup

                              Hi Jon,

                               

                              Thanks a lot for this long but still clear and detailed answer. It helps me to better understand.

                               

                              Speaking of "Rhodes supports C code in extensions on every platform.", I am not sure I get that. For instance, we have to support FFMPEG library which comes as a C lib but, as far as I understood, we still have to (re)compile it in Java for Android and Objectif-C for iOs, right ? Or is it a way to interface the C version in Rhodes whatever the platform (Android, iOs and Win32 in our case) without having to recompile it to a native version ?

                               

                              Thierry

                                • Re: Why is ruby so slow vs. Javascript in Rhodes application ?
                                  Jon Tara
                                  as far as I understood, we still have to (re)compile it in Java for Android and Objectif-C for iOs, right ?

                                   

                                  You mean "port"? Because you can't simply "compile" code in a different language! You would have to re-write it.

                                   

                                  Thankfully, you don't have to!

                                   

                                  You can use C on every platform. But it can be a round-about way to get there!

                                   

                                  If you are writing a native extension API, then the (exposed) interface between Ruby and your native API is, indeed, in the native language for the platform:

                                   

                                  • iOS - objective-C
                                  • Android - Java
                                  • Windows C++
                                  • Windows Phone C#

                                   

                                  OK, really, does each platform really have a "native" language? Well, now iOS has two! (Swift). I guess by "native" here, I mean the language used as interface for the platform's own APIs. And... the most popular one. (for iOS).

                                   

                                  So, yes, you need to write a bit of objective-C for iOS, Java for Android, etc.

                                   

                                  But then that code can call C or C++ code, and they are both supported on at least ios/android/windows (all variants). You get into weird stuff like Symbian (still supported?) or Blackberry, I dunno. Rhodes build is already setup to build those C/C++ files for you.

                                   

                                  Browse through the Rhodes source, and you will find that it has plenty of C/C++ under the hood! You build this when you build your project, because you build the entire Rhodes core.

                                   

                                  So, you will need to write a small "native" wrapper for FFMPEG for each platform you want to support.

                                   

                                  But I would urge you to consider the alternative of using platform-native video processing APIs instead of introducing your own FFMPEG. At least on selected platforms. (e.g. iOS). iPhone/iPad users are heavy video users. Apple has put a lot of effort into audio/video encoding/decoding and signal processing. It all makes use of the GPU.

                                   

                                  You certainly do, though, have the option of bringing-in any C library.

                                    • Re: Why is ruby so slow vs. Javascript in Rhodes application ?
                                      Jon Tara

                                      So, you would, somewhat awkwardly go:

                                       

                                         Ruby -> Objective-C/Java//C++ -> C

                                       

                                       

                                      And you actually have a C layer first.


                                      In old-style (pre-4.0) native extension (still supported), you used SWIG to create Ruby bindings in C. These went in your extension, and you aren't supposed to modify it. so, really, you go:

                                       

                                      Ruby -> C -> Objective-C/Java/C++ -> C

                                       

                                      old_extension.png

                                       

                                      You could color outside of the lines, though, and just stop at the first step.

                                       

                                      So, here, cee_wrap.c is the SWIG-generated Ruby binding. cee.i is the interface specification from which cee_wrap.c was generated. The wrapper acts as the common interface to all of the platform implementations.

                                       

                                      With 4.x+, now you write an XML file with the API interface, instead of a .i file. And you don't use SWIG any more, the Rhodes build generates the interface for you. (I don't think it uses SWIG, it uses something else.) I see now there is both C and C++ code in the common part:

                                       

                                      new_extension.png

                                      (I just recently converted this native extension from the old style to the new, so it's pretty fresh...)