📜 ⬆️ ⬇️

Lua + FFI vs. Javascript

Smallpic

This small article does not claim to be an article.

Last time I compared LuaJIT 2.0 Beta 5 and JavaScript in various browsers using the example of a simple Raytracer. The result of the comparison: JavaScript in Chrome scored 20,000 RPS and took the 1st place, and LuaJIT - 5,000 RPS and the last place.
')
With the release of LuaJIT 2.0 Beta 6, the situation has changed: Lua easily came out on top ahead of Chrome. Let's see how it happened.



Imagine that you have a large array that needs to be filled with numbers. How do you do this? Here is an example implementation on Lua:

a = {} for i = 1, n do a[i] = i*i - 0.5 end 


For large n, this works very slowly: Lua does not know in advance what size the array will be and therefore has to increase the size of this array dynamically. Lua does not even know that the array indices are numbers in the range 1..n, and the values ​​are integers, so he has to rely on the worst case when one day they write to the array like this:

 a['qqq'] = {red = 1, green = 0.5, blue = 0.8} 


This versatility inhibits the program. I would like to tell Lua that we have an array of the form “double a [n]”. You cannot do this using standard Lua tools, but you can add an extension to Lua — the language allows it — and get what you need. This extension is called FFI. This is how the array problem is solved:

 ffi = require'ffi' a = ffi.new('double[?]', 1 + n) for i = 1, n do a[i] = i*i end 


This simple change of the code at times increases the speed and at times reduces memory. Just what you need for raytracer.

The previous raytracer kept in his memory a table consisting of flowers - small tables with three fields. Through each pixel a beam was launched, its color was calculated and this color fell into the table. It looked like this:

 pixels = {} for x = 1, width do for y = 1, height do local color = raytrace(x, y) pixels[y*width + x] = color end end 


During operation, this table of pixels grew, the time to add a new element also grew, and the raytracer's speed dropped. The result is 5,000 RPS (rays per second) and the last place.

With the advent of FFI, it became possible to present a table of pixels as an array, pre-allocating memory. The algorithm became as follows:

 ffi = require'ffi' pixels = ffi.new('float[?]', width*height*3) i = 0 for y = 1, height do for x = 1, width do local color = raytrace(x, y) pixels[i + 0] = color[1] pixels[i + 1] = color[2] pixels[i + 2] = color[3] i = i + 3 end end 


The code has become a little longer than before, but in other places the code has become simpler: for example, saving such an array to a BMP file is easier. This simple optimization gives you three things:

  1. The amount of memory is reduced to 25 megabytes and does not grow during operation.
  2. The speed of the raytracer does not depend on the size of the resulting image.
  3. Speed ​​rises to 40,000 RPS


For comparison: the best result of the last comparison - JavaScript + Chrome - received 20,000 RPS and spent 150 MB of memory.

Below the test results are partially taken from the previous comparison. Raytracy programs or the same scene on a screen of 1000 × 1000 pixels passing 3 beams through each pixel.

LuaJIT40,000 RPS25 Mb
Chrome20,400 RPS150 Mb
Opera15.700 RPS
Firefox9,300 RPS
Explorer9,000 RPS


It remains to say that the reystraiser on Lua I wrote in a straightforward way, and with each operation on vectors (addition, multiplication by a number) he creates a new vector with the result. This bunch of constantly-created vectors creates a job for the garbage collector. If you do not create extra vectors, then the Ray Tracer speed will increase.

The ray tracer of which I spoke lies here . Run with the command "luajit main.lua". The luajit version is at least 2.0 Beta 6.

Source: https://habr.com/ru/post/113804/


All Articles