Showing posts with label luajit. Show all posts
Showing posts with label luajit. Show all posts

2023-02-05

Lua Tutorial, Example, Cheatsheet

Lua is one of the most popular embeddable language (other than Javascript that already embedded in browser), there's a lot of products that embed lua as it's scripting language (probably because the language and the C-API is simple, VM size is small, and there's a product called LuaJIT that is currently the fastest JIT implementation for scripting language. Current version of Lua is 5.4, but LuaJIT only support 5.1 and 5.2 partially. There's a lot of products that are built having Lua embedded inside:

Variables, Data Types, Comments, basic stdlib Functions

 
Today we're going to learn a bit about Lua syntax, to run a lua script just run lua yourscript.lua on terminal or use luajit also fine:

--[[ this is multiline comment
  types in Lua: boolean, number, string
    table, function, nil, thread, userdata
]]
-- this is single line comment, just like SQL

-- create variable
local foo = 'test' -- create a string, can escape \ same as with ""
local bar = 1 -- create a number

-- random value
math.randomseed(os.time()) -- set random table
math.random() -- get random float
math.random(10) -- get random int 0-9

-- print, concat
print(#foo) -- get length of string, same as string.len(foo)

foo .. 123 -- concat string, will convert non-string to string
print(1 + 2 ^ 3 * 4, 5) -- print 33 5 separated with tab

-- type and conversion
type(bar) -- get type (number)
tostring(1) -- convert to string
tonumber("123") -- convert to number

-- multiline string
print([[foo
bar]]) -- print a string, but preserve newlines, will not escape \

-- string functions
string.upper('abc') -- return uppercased string
string.sub('abcde', 2,4) -- get 2nd to 4th char (bcd)
string.char(65) -- return ascii of a byte
string.byte('A') -- return byte of a character
string.rep('x', 5, ' ') -- repeat x 5 times separated with space
string.format('%.2f %d %i', math.pi, 1, 4) -- smae as C's sprintf 
start, end = string.find('haystack', 'st') -- find index start-end of need on haystack, nil if not found, end is optional
string.gsub('abc','b','d') -- global substitution

Decision and Loop

There's one syntax for decision (if), and 3 syntax for loop (for, while-do, repeat-until) in Lua:

-- decision
if false == true then
  print(1)
elseif 15 then -- truthy
  print( 15 ~= 0 ) -- not equal, in another language: !=
else
  print('aaa')
end

-- can be on single line
if (x > 0) or (x < 0) then print(not true) end

-- counter loop
for i = 1, 5 do print(i) end -- 1 2 3 4 5
for i = 1, 4, 2 do print(i) end -- 1 3
for i = 4, 1, -2 do print(i) end -- 4 2

-- iterating array
local arr = {1, 4, 9} -- table
for i = 1, #arr do print(arr[i]) end

-- top-checked loop
while true do
  break -- break loop
end

-- bottom-checked loop
repeat
until false

IO, File, and OS

IO is for input output, OS is for operating system functions:

-- IO
local in = io.read() -- read until newline
io.write('foo') -- like print() but without newline
io.output('bla.txt') -- set bla.txt as stdout
io.close() -- make sure stdout is closed
io.input('foo.txt') -- set foo.txt as stdin
io.read(4) -- read 4 characters from stdin
io.read('*number') -- read as number
io.read('*line') -- read until newline
io.read('*all') -- read everything
local f = io.open('file.txt', 'w') -- create for write, a=append, r=read
f:write('bla') -- write to file
f:close() -- flush and close file

-- OS
os.time({year = 2023, month = 2, day = 4, hour = 12, min = 23,sec  = 34})
os.getenv('PATH') -- get environtment variables value
os.rename('a.txt','b.txt') -- rename a.txt to b.txt
os.rename('a.txt') -- erase file
os.execute('echo 1')  -- execute shell command
os.clock() -- get current second as float
os.exit() -- exit lua script

Table

Table is combination of list/dictionary/set/record/object, it can store anything, including a function.

-- as list
local arr = {1, true, "yes"} -- arr[4] is nil, #arr is 3

-- mutation functions
table.sort(arr) -- error, cannot sort if elements on different types
table.insert(arr, 2, 3.4) -- insert on 2nd position, shifting the rest
table.remove(arr, 3) -- remove 3rd element, shifting remaining left

-- non mutating
table.concat(arr, ' ') -- return string with spaces

-- nested
local mat = {
  {1,2,3},
  {4,5,6}, -- can end with comma
}

-- table as dictionary and record
local user = {
  name = 'Yui',
  age = 23,
}
user['salutations'] = 'Ms.' -- or user.salutations

Function

Function is block of code that contains logic.

-- function can receive parameter
local function bla(x)
  x = x or 'the default value'
  local y = 1 -- local scope variable
  return x
end

-- function can receive multivalue
local function foo()
  return 1,2
end
local x,y = foo()
local x = foo() -- only receive first one

-- function with internal state
local function lmd()
   local counter = 0
   return function()
     counter = counter + 1
    return counter
  end
end
local x = lmd()
print(x()) -- 1
print(x()) -- 2

-- variadic arguments
local function vargs(...)
  for k, v in pairs({...}) do print(k, v) end
end
vargs('a','b') --  1 a, 2 b 

Coroutine

Coroutine is resumable function

local rot1 = coroutine.create(function()
   for i = 1, 10 do
    print(i)
    if i == 5 then coroutine.yield() end -- suspend
   end
end)
if coroutine.status(rot1) == 'suspended' then
   -- running, [suspended], normal, dead
  coroutine.resume(rot1)  -- will resume after yield
end

Module

To organize a code, we can use module:

-- module, eg. mod1.lua
_G.mod1 = {}
function mod1.add(x,y)
  return x + y
end
return mod1

-- import a module
local mod1 = require('mod1')
local x = mod1.add(1,2)

Basic OOP

To simulate OOP-like features we can do things like in Javascript, just that to call a method of object we can use colon instead of dot (so self parameter will be passed):

-- constructor
local function NewUser(name, age)
  return {
    name = name,
    age = age,
    show = function(self) --
method example
      print(self.name, self.age)
    end
  }
end
local me = NewUser('Tzuyu', 21)
me:show() -- short version of: me.show(me)

-- inheritance, overriding
local function NewAdmin(name, age)
  local usr = NewUser(name, age)
  usr.isAdmin = true
  usr.setPerm = function(self, perms)
     self.perms = perms
  end
  local parent_
show = usr.show
  usr.show = function(self)
    -- override parent method
    -- parent_
show(self) to call parent
  end
  return usr
end
local adm = NewAdmin('Kis', 37)
adm:setPerm({'canErase','canAddNewAdmin'})
adm:
show() -- does nothing

-- override operator
local obj = {whatever = 1}
setmetatable(obj, {
   __add = function(x,y)  -- overrides + operator
    return x.whatever + tonumber(y)
   end,
  -- __sub, __mul, __div, __mod, __pow,
  -- __concat, __len, __eq, __lt, __le, __gt, __ge
})
print(obj + "123")

For more information about modules you can visit https://luarocks.org/ 

For more information about the syntax you can check their doc: https://devdocs.io/lua~5.2/ or https://learnxinyminutes.com/docs/lua/ or this 220 minutes video

But as usual, don't use dynamic typed language for large project, it would be pain to maintain in the long run, especially if the IDE cannot jump properly to correct method or cannot suggest correct type hints/methods to call

2020-12-22

String Associative Array and CombSort Benchmark 2020 Edition

5 Years later since last string associative benchmark and lesser string associative benchmark (measuring string concat operation and built-in associative array set and get), numeric comb sort benchmark and string comb sort benchmark (measuring basic array random access, string conversion, and array swap for number and string), this year's using newer processor: AMD Ryzen 3 3100 running on 64-bit Ubuntu 20.04. Now with 10x more data to hopefully make the benchmark runs 10x slower (at least 1 sec), best of 3 runs.

alias time='/usr/bin/time -f "\nCPU: %Us\tReal: %es\tRAM: %MKB"'

$ php -v
PHP 7.4.3 (cli) (built: Oct  6 2020 15:47:56) ( NTS )

$ time php assoc.php 
637912 641149 67002
3808703 14182513 2343937
CPU: 1.25s      Real: 1.34s     RAM: 190644KB

$ python3 -V
Python 3.8.5

$ time python3 dictionary.py
637912 641149 67002
3808703 14182513 2343937
CPU: 5.33s      Real: 5.47s     RAM: 314564KB

$ ruby3.0 -v
ruby 3.0.0p0 (2020-12-25 revision 95aff21468) [x86_64-linux-gnu]

$ time ruby3.0 --jit hash.rb 
637912 641149 67002
3808703 14182513 2343937
CPU: 6.50s      Real: 5.94s     RAM: 371832KB

$ go version
go version go1.14.7 linux/amd64

$ time go run map.go
637912 641149 67002
3808703 14182513 2343937
CPU: 1.79s      Real: 1.56s     RAM: 257440KB

$ node -v       
v14.15.2

$ time node object.js
637912 641149 67002
3808703 14182513 2343937
CPU: 2.24s      Real: 2.21s     RAM: 326636KB

$ luajit -v 
LuaJIT 2.1.0-beta3 -- Copyright (C) 2005-2017 Mike Pall. http://luajit.org/

$ time luajit table.lua
637912  641149  67002
3808703 14182513        2343937
CPU: 4.11s      Real: 4.22s     RAM: 250828KB

$ dart --version
Dart SDK version: 2.10.4 (stable) (Unknown timestamp) on "linux_x64"

$ time dart map.dart
637912 641149 67002
3808703 14182513 2343937
CPU: 2.99s      Real: 2.91s     RAM: 385496KB

$ v version
V 0.2 36dcace

$ time v run map.v
637912, 641149, 67002
3808703, 14182513, 2343937
CPU: 4.79s      Real: 5.28s     RAM: 1470668KB

$ tcc -v
tcc version 0.9.27 (x86_64 Linux)

$ time tcc -run uthash.c
637912 641149 67002
3808703 14182513 2343937
Command exited with non-zero status 25

CPU: 2.52s      Real: 2.61s     RAM: 291912KB

export GOPHERJS_GOROOT="$(go1.12.16 env GOROOT)"
$ npm install --global source-map-support

$ goperjs version
GopherJS 1.12-3

$ time gopherjs 
637912 641149 67002
3808703 14182513 2343937

CPU: 14.13s     Real: 12.01s    RAM: 597712KB

$ java -version
java version "14.0.2" 2020-07-14
Java(TM) SE Runtime Environment (build 14.0.2+12-46)
Java HotSpot(TM) 64-Bit Server VM (build 14.0.2+12-46, mixed mode, sharing)

$ time java hashmap.java
637912 641149 67002
3808703 14182513 2343937

CPU: 5.18s      Real: 1.63s     RAM: 545412KB

The result shows a huge improvement for PHP since the old 5.4. NodeJS also huge improvement compared to old 0.10. The rest is quite bit the same. Also please keep note that Golang and V includes build/compile time not just run duration, and it seems V performance really bad when it comes to string operations (the compile itself really fast, less than 1s for 36dcace -- using gcc 9.3.0). 
Next we're gonna benchmark comb sort implementation. But this time we use jit version of ruby 2.7, since it's far way faster (19s vs 26s and 58s vs 66s for string benchmark), for ruby 3.0 we always use jit version since it's faster than non-jit. In case for C (TCC) which doesn't have built-in associative array, I used uthash, because it's the most popular. TinyGo does not complete first benchmark after more than 1000s, sometimes segfault. XS Javascript engine failed to give correct result, engine262 also failed to finish within 1000s.

LanguageCommand FlagsVersionAssocRAMNum CombRAMStr CombRAMTotalRAM
Gogo run1.14.71.56257,4400.7382,8444.74245,4327.03585,716
Gogo run1.15.61.73256,6200.7882,8964.86245,4687.37584,984
Nimnim r -d:release --gc:arc1.4.21.56265,1720.7979,2845.77633,6768.12978,132
Nimnim r -d:release --gc:orc1.4.21.53265,1600.9479,3805.83633,6368.30978,176
Javascriptnode14.15.22.21327,0480.87111,9726.13351,5209.21790,540
Crystalcrystal run --release0.35.11.81283,6481.44146,7006.09440,7969.34871,144
Javascript~/.esvu/bin/v88.9.2011.77177,7480.89105,4166.71335,2369.37618,400
Ctcc -run0.9.272.61291,9121.4580,8326.40393,35210.46766,096
Javajava14.0.2 2020-07-141.63545,4121.50165,8647.69743,57210.821,454,848
Nimnim r -d:release1.4.21.91247,4560.9679,4768.381,211,11611.251,538,048
Dartdart2.10.42.91385,4961.61191,9167.31616,71611.831,194,128
Pythonpypy7.3.1+dfsg-22.19331,7762.83139,7408.04522,64813.06994,164
Javascript~/.esvu/bin/chakra1.11.24.02.73487,4001.27102,19211.27803,16815.271,392,760
Javascript~/.esvu/bin/jsc2711175.90593,6240.68111,9729.09596,08815.671,301,684
Vv -prod run0.2 32091dd gcc-10.24.781,469,9321.8679,37614.061,560,51620.703,109,824
Lualuajit2.1.0-beta34.11250,8283.76133,42412.91511,19620.78895,448
Javascript~/.esvu/bin/smJavaScript-C86.0a15.61378,0641.4096,48013.81393,37620.82867,920
Vv -prod run0.2 32091dd gcc-9.35.051,469,9362.1479,40814.621,560,48421.813,109,828
Javascript~/.esvu/bin/graaljsCE Native 20.3.07.78958,3804.45405,90014.31911,22026.542,275,500
Gogopherjs run1.12-3 (node 14.15.2)11.76594,8962.04119,60418.46397,39632.261,111,896
Nimnim r1.4.26.60247,4443.0579,33231.851,211,20841.501,537,984
PHPphp7.4.31.34190,64410.11328,45234.51641,66445.961,160,760
Rubytruffleruby21.1.0-dev-c1517c5514.542,456,1563.09453,15229.273,660,28446.906,569,592
Crystalcrystal run0.35.15.69284,32812.00153,82831.69441,74049.38879,896
Javascript~/.esvu/bin/quickjs2020-11-083.90252,48423.4880,77234.80471,62462.18804,880
Vv run0.2 36dcace gcc-9.35.281,470,6686.6080,23258.991,561,17670.873,112,076
Lualua5.3.35.98366,51627.26264,64846.05864,30079.291,495,464
Rubyruby2.7.0p06.31371,45619.29100,53658.82694,56084.421,166,552
Pythonpython33.8.55.47314,56433.96404,97647.79722,82087.221,442,360
Rubyjruby9.2.9.07.451,878,18434.111,976,84459.837,115,448101.3910,970,476
Rubyruby3.0.0p05.94371,83224.8792,84474.321,015,096105.131,479,772
Gotinygo run0.16.0999.99318,1483.68300,548252.34711,3401256.011,330,036

Golang still the winner (obviously, since it's compiled), then Nim (Compiled), next best JIT or interpreter is NodeJS, Crystal (Compiled, not JIT), v8, followed Java, by TCC (Compiled) Dart, PyPy, V (Compiled, not JIT), LuaJIT, PHP, Ruby, and Python3. The recap spreadsheet can be accessed here

FAQ:
1. Why you measure the compile duration too? because developer experience also important (feedback loop), at least for me.
2. Why not warming up the VM first? each implementation have it's own advantage and disadvantage.
3. Why there's no C++, VB.NET, C#, D, Object-Pascal? don't want to compile things (since there's no build and run command in one flag).  
4. Why there's no Kotlin, Scala, Rust, Pony, Swift, Groovy, Julia, Crystal, or Zig? Too lazy to add :3 you can contribute tho (create a pull request, then I'll run the benchmark again as preferabbly as there's precompiled binary/deb/apt/ppa repository for the compiler/interpreter).

Contributors
ilmanzo (Nim, Crystal, D)