Apa Perbedaan Antara LuaJIT dan Lua Standar?

API7.ai

September 23, 2022

OpenResty (NGINX + Lua)

Mari kita belajar tentang LuaJIT, salah satu pilar penting dari OpenResty, dan saya akan membahas bagian inti dari postingan hari ini tentang beberapa aspek penting dan kurang dikenal dari Lua dan LuaJIT.

Anda dapat mempelajari dasar-dasar Lua melalui mesin pencari atau buku-buku Lua, dan saya merekomendasikan buku Programming in Lua yang ditulis oleh pencipta Lua.

Tentu saja, ambang batas untuk menulis kode LuaJIT yang benar di OpenResty tidak terlalu tinggi. Namun, menulis kode LuaJIT yang efisien tidaklah mudah, dan saya akan membahas elemen-elemen kunci tersebut secara detail di bagian optimasi performa OpenResty nanti.

Mari kita lihat di mana posisi LuaJIT dalam arsitektur keseluruhan OpenResty.

Arsitektur OpenResty

Seperti yang disebutkan sebelumnya, proses Worker OpenResty diperoleh dengan melakukan fork dari proses Master. Mesin virtual LuaJIT di proses Master juga di-fork. Semua proses Worker dalam satu Worker berbagi mesin virtual LuaJIT ini, dan eksekusi kode Lua dilakukan di dalam mesin virtual ini.

Ini adalah dasar cara kerja OpenResty, yang akan kita bahas lebih detail di artikel selanjutnya. Hari ini kita akan mulai dengan menjelaskan hubungan antara Lua dan LuaJIT.

Hubungan antara Lua Standar dan LuaJIT

Mari kita bahas hal-hal penting terlebih dahulu.

Lua standar dan LuaJIT adalah dua hal yang berbeda. LuaJIT hanya kompatibel dengan sintaks Lua 5.1.

Versi terbaru dari Lua standar saat ini adalah 5.4.4, dan versi terbaru dari LuaJIT adalah 2.1.0-beta3. Di versi OpenResty yang lebih lama beberapa tahun lalu, Anda bisa memilih untuk menggunakan mesin virtual Lua standar atau LuaJIT saat kompilasi, tetapi sekarang dukungan untuk Lua standar telah dihapus, dan hanya LuaJIT yang didukung.

Sintaks LuaJIT kompatibel dengan Lua 5.1, dengan dukungan opsional untuk Lua 5.2 dan 5.3. Jadi, kita harus mempelajari sintaks Lua 5.1 terlebih dahulu dan kemudian mempelajari fitur-fitur LuaJIT. Di artikel sebelumnya, saya telah membawa Anda ke sintaks dasar Lua. Hari ini saya hanya akan menyebutkan beberapa fitur unik Lua.

Perlu dicatat bahwa OpenResty tidak langsung menggunakan versi resmi LuaJIT 2.1.0-beta3, tetapi memperluasnya dengan fork-nya sendiri: openresty-luajit2.

API unik ini ditambahkan selama pengembangan aktual OpenResty untuk alasan performa. Jadi, LuaJIT yang kita sebutkan nanti mengacu pada cabang LuaJIT yang dikelola oleh OpenResty sendiri.

Mengapa LuaJIT?

Setelah semua pembahasan tentang hubungan antara LuaJIT dan Lua, Anda mungkin bertanya-tanya mengapa tidak menggunakan Lua langsung, tetapi menggunakan LuaJIT. Faktanya, alasan utamanya adalah keunggulan performa LuaJIT.

Kode Lua tidak diinterpretasikan langsung, tetapi dikompilasi menjadi Byte Code oleh kompiler Lua dan kemudian dieksekusi oleh mesin virtual Lua.

Lingkungan runtime LuaJIT, selain memiliki implementasi assembler dari interpreter Lua, juga memiliki kompiler JIT yang dapat menghasilkan kode mesin secara langsung. Awalnya, LuaJIT berjalan seperti Lua standar, dengan kode Lua dikompilasi menjadi byte code, yang kemudian diinterpretasikan dan dieksekusi oleh interpreter LuaJIT.

Perbedaannya adalah interpreter LuaJIT mencatat beberapa statistik runtime saat mengeksekusi bytecode, seperti berapa kali setiap fungsi Lua dijalankan dan berapa kali setiap loop Lua dieksekusi. Ketika jumlah ini melebihi ambang batas tertentu, fungsi Lua atau loop tersebut dianggap cukup "panas" untuk memicu kompiler JIT mulai bekerja.

Kompiler JIT mencoba mengompilasi jalur kode Lua yang sesuai, mulai dari titik masuk fungsi yang panas atau lokasi loop yang panas. Proses kompilasi mengubah bytecode LuaJIT menjadi IR (Intermediate Representation) yang didefinisikan oleh LuaJIT sendiri, dan kemudian menghasilkan kode mesin untuk arsitektur target.

Jadi, optimasi performa LuaJIT pada dasarnya adalah tentang membuat sebanyak mungkin kode Lua dapat dihasilkan menjadi kode mesin oleh kompiler JIT, daripada kembali ke mode interpretasi oleh interpreter Lua. Setelah Anda memahami ini, Anda dapat memahami esensi dari optimasi performa OpenResty yang akan Anda pelajari nanti.

Fitur Khusus Lua

Seperti yang dijelaskan di artikel sebelumnya, bahasa Lua relatif sederhana. Bagi insinyur dengan latar belakang bahasa pemrograman lain, mudah untuk memahami logika kode begitu Anda memperhatikan beberapa aspek unik Lua. Selanjutnya, mari kita lihat beberapa aspek yang lebih tidak biasa dari bahasa Lua.

1. Indeks dimulai dari 1

Lua adalah satu-satunya bahasa pemrograman yang saya tahu yang memulai indeks dari 1. Hal ini, meskipun lebih mudah dipahami oleh mereka yang tidak memiliki latar belakang pemrograman, rentan terhadap bug program. Berikut adalah contohnya.

$ resty -e 't={100}; ngx.say(t[0])'

Anda mungkin mengharapkan program mencetak 100 atau melaporkan kesalahan bahwa indeks 0 tidak ada. Namun, yang mengejutkan, tidak ada yang dicetak, dan tidak ada kesalahan yang dilaporkan. Jadi mari kita tambahkan perintah type dan lihat apa yang dihasilkan.

$ resty -e 't={100};ngx.say(type(t[0]))' nil

Ternyata nilainya adalah nil. Sebenarnya, di OpenResty, penentuan dan penanganan nilai nil juga merupakan titik yang membingungkan, jadi kita akan membahasnya lebih lanjut nanti ketika kita membahas OpenResty.

2. Menggunakan .. untuk menggabungkan string

Tidak seperti kebanyakan bahasa yang menggunakan +, Lua menggunakan dua tanda titik untuk menggabungkan string.

$ resty -e "ngx.say('hello' .. ', world')" hello, world

Dalam pengembangan proyek aktual, kita umumnya menggunakan beberapa bahasa pengembangan, dan desain Lua yang tidak biasa akan selalu membuat pengembang berpikir ketika penggabungan string sedikit membingungkan.

3. Tabel adalah satu-satunya struktur data

Tidak seperti Python, yang kaya akan struktur data bawaan, Lua hanya memiliki satu struktur data, yaitu table, yang dapat mencakup array dan tabel hash.

local color = {first = "red", "blue", third = "green", "yellow"} print(color["first"]) --> output: red print(color[1]) --> output: blue print(color["third"]) --> output: green print(color[2]) --> output: yellow print(color[3]) --> output: nil

Jika Anda tidak secara eksplisit menetapkan nilai sebagai pasangan kunci-nilai, tabel secara default menggunakan angka sebagai indeks, dimulai dari 1. Jadi color[1] adalah blue.

Selain itu, mendapatkan panjang yang benar dalam tabel cukup sulit, jadi mari kita lihat contoh-contoh ini.

local t1 = { 1, 2, 3 } print("Test1 " .. table.getn(t1)) local t2 = { 1, a = 2, 3 } print("Test2 " .. table.getn(t2)) local t3 = { 1, nil } print("Test3 " .. table.getn(t3)) local t4 = { 1, nil, 2 } print("Test4 " .. table.getn(t4))

Hasil:

Test1 3 Test2 2 Test3 1 Test4

Seperti yang Anda lihat, kecuali untuk kasus uji pertama yang mengembalikan panjang 3, tes berikutnya semuanya di luar ekspektasi kita. Sebenarnya, untuk mendapatkan panjang tabel di Lua, penting untuk dicatat bahwa nilai yang benar hanya dikembalikan jika tabel tersebut adalah sequence.

Jadi apa itu sequence? Pertama-tama, sequence adalah subset dari array. Artinya, elemen tabel dapat diakses dengan indeks bilangan bulat positif dan tidak ada pasangan kunci-nilai. Dalam kode di atas, semua tabel adalah array kecuali t2.

Kedua, sequence tidak mengandung lubang, yaitu nil. Menggabungkan kedua poin ini, tabel t1 di atas adalah sequence, sedangkan t3 dan t4 adalah array tetapi bukan sequence.

Sampai titik ini, Anda mungkin masih memiliki pertanyaan, mengapa panjang t4 adalah 1? Ini karena ketika nil ditemui, logika untuk mendapatkan panjang tidak terus berjalan tetapi langsung kembali.

Saya tidak tahu apakah Anda sepenuhnya memahaminya. Bagian ini memang cukup rumit. Jadi, apakah ada cara untuk mendapatkan panjang tabel yang kita inginkan? Tentu saja ada. OpenResty memperluas ini, dan saya akan membahasnya nanti di bab yang khusus membahas tabel, jadi mari kita tinggalkan teka-teki di sini.

4. Semua variabel bersifat global secara default

Saya ingin menekankan bahwa kecuali Anda cukup yakin, Anda harus selalu mendeklarasikan variabel baru sebagai variabel local.

local s = 'hello'

Ini karena, di Lua, variabel bersifat global secara default dan ditempatkan dalam tabel bernama _G. Variabel yang tidak bersifat lokal akan dicari di tabel global, yang merupakan operasi yang mahal. Kesalahan pengetikan nama variabel dapat menyebabkan bug yang sulit diidentifikasi dan diperbaiki.

Jadi, di OpenResty, saya sangat menyarankan agar Anda selalu mendeklarasikan variabel menggunakan local, bahkan saat Anda memerlukan modul.

-- Direkomendasikan local xxx = require('xxx') -- Hindari require('xxx')

LuaJIT

Dengan mengingat empat fitur khusus Lua ini, mari kita lanjutkan ke LuaJIT.

LuaJIT, selain kompatibel dengan Lua 5.1 dan mendukung JIT, juga terintegrasi erat dengan FFI (Foreign Function Interface), memungkinkan Anda memanggil fungsi C eksternal dan menggunakan struktur data C langsung dalam kode Lua Anda. Berikut adalah contoh paling sederhana.

local ffi = require("ffi") ffi.cdef[[ int printf(const char *fmt, ...); ]] ffi.C.printf("Hello %s!", "world")

Hanya dalam beberapa baris kode, Anda dapat memanggil fungsi printf dari C langsung dari Lua dan mencetak Hello world! Anda dapat menggunakan perintah resty untuk menjalankannya dan melihat apakah itu berfungsi.

Demikian pula, kita dapat menggunakan FFI untuk memanggil fungsi C dari NGINX dan OpenSSL untuk melakukan lebih banyak hal. Pendekatan FFI memiliki performa yang lebih baik daripada pendekatan Lua/C API tradisional, itulah sebabnya proyek lua-resty-core ada. Di bagian selanjutnya, kita akan membahas FFI dan lua-resty-core.

Selain itu, untuk alasan performa, LuaJIT memperluas fungsi tabel: table.new dan table.clear, dua fungsi optimasi performa penting yang sering digunakan di pustaka lua-resty OpenResty. Namun, sedikit pengembang yang familiar dengan mereka, karena dokumentasinya intens dan tidak ada contoh kode. Kita akan menyimpannya untuk bagian optimasi performa.

Ringkasan

Mari kita tinjau kembali konten hari ini.

OpenResty memilih LuaJIT daripada Lua standar untuk alasan performa dan mempertahankan cabang LuaJIT-nya sendiri. LuaJIT didasarkan pada sintaks Lua 5.1 dan secara selektif kompatibel dengan beberapa sintaks Lua 5.2 dan Lua 5.3 untuk membentuk sistemnya. Adapun sintaks Lua yang perlu Anda kuasai, ia memiliki fitur khas dalam indeks, penggabungan string, struktur data, dan variabel, yang harus Anda perhatikan saat menulis kode.

Apakah Anda pernah mengalami kesulitan saat mempelajari Lua dan LuaJIT? Jangan ragu untuk berbagi pendapat Anda dengan kami, dan saya telah menulis postingan untuk berbagi kesulitan yang saya alami. Anda juga dipersilakan untuk membagikan postingan ini dengan rekan dan teman Anda untuk belajar dan berkembang bersama.