Topik ini memperkenalkan Julia sebagai bahasa pemrograman untuk komputasi numerik, mulai dari asal dan tujuannya, cara kerja kompilasinya hingga menghasilkan kode mesin, sampai ciri notasinya yang menyerupai tulisan matematis. Beberapa contoh kode disertakan beserta keluaran aslinya, sehingga klaim tentang spesialisasi tipe dan notasi dapat diverifikasi langsung. Setelah topik ini dibaca, posisi Julia di antara bahasa pemrograman lain dan alasan di balik kecepatannya dapat dijelaskan secara runtut.
Asal dan tujuan Julia#
Julia adalah bahasa pemrograman tingkat tinggi untuk komputasi numerik yang dirancang di Massachusetts Institute of Technology dan dirilis secara terbuka pada tahun 2012. Perancangnya, yaitu Jeff Bezanson, Stefan Karpinski, Viral Shah, dan Alan Edelman, menyatakan tujuan desain bahasa ini dalam artikel berjudul Why We Created Julia. Julia didistribusikan sebagai perangkat lunak bebas dengan lisensi MIT, sehingga kode sumbernya dapat dibaca, diubah, dan digunakan ulang tanpa biaya lisensi. Rilis stabil terbarunya berasal dari seri 1.13, dan jalur ini menggunakan seri tersebut.
Sebelum Julia muncul, pekerjaan komputasi ilmiah lazimnya terbagi ke dalam dua bahasa berbeda. Prototipe ditulis dalam bahasa yang mudah seperti Python, R, atau MATLAB, sedangkan bagian yang lambat ditulis ulang dalam C atau Fortran agar cepat. Pembagian kerja ini dikenal sebagai two-language problem, yaitu kebutuhan menulis satu program dengan dua bahasa sekaligus. Tujuan utama Julia adalah menghapus kebutuhan itu, sehingga notasi yang sama untuk bereksperimen tetap layak digunakan untuk perhitungan yang berat.
Lingkup Julia saat ini tidak berhenti pada komputasi numerik. Ekosistemnya mencakup pengolahan data, pemodelan, dan pengembangan package, tetapi komputasi numerik tetap menjadi bidang yang paling diuntungkan oleh desainnya. Hal itu karena kecepatan eksekusi dan kepraktisan penulisan berada dalam satu bahasa yang sama, dan bagian berikutnya menjelaskan mekanisme yang mewujudkannya.
Kompilasi ke kode mesin melalui LLVM#
Julia mengeksekusi fungsi dengan mengompilasinya menjadi kode mesin melalui LLVM, yaitu pustaka kompiler yang juga menjadi dasar Clang dan Rust. Alurnya bertahap: kode sumber diurai menjadi pohon sintaks, tipe setiap operasi disimpulkan dari tipe argumennya, lalu LLVM menghasilkan instruksi native, yakni instruksi yang langsung dipahami prosesor tanpa lapisan penafsiran. Kompilasi berlangsung secara just-in-time, yaitu saat fungsi pertama kali dipanggil dengan kombinasi tipe argumen tertentu, dan hasilnya disimpan untuk pemanggilan berikutnya. Satu fungsi Julia dengan demikian dapat memiliki beberapa versi kode mesin, satu versi untuk setiap kombinasi tipe yang pernah digunakan.
Alur tersebut dirangkum pada diagram berikut. Diagram ini membedakan Julia dari bahasa yang menafsirkan setiap baris berulang kali, karena pekerjaan berat pada Julia selesai satu kali per kombinasi tipe.
Efek spesialisasi tipe ini dapat dilihat langsung. Skrip berikut menampilkan LLVM IR, yaitu representasi antara sebelum menjadi instruksi mesin, untuk sebuah fungsi penjumlahan yang dipanggil dengan dua bilangan Float64. Hasilnya hanya satu instruksi penjumlahan titik mengambang, seperti kode mesin yang ditulis manual dalam C.
# menampilkan LLVM IR hasil kompilasi fungsi tambah untuk dua Float64
using InteractiveUtils
tambah(x, y) = x + y
io = IOBuffer()
code_llvm(io, tambah, (Float64, Float64); debuginfo=:none)
ir = String(take!(io))
# akhiran angka pada nama fungsi bisa berubah antar sesi, sehingga dinormalkan
println(replace(ir, r"_[0-9]+" => "_N")); Function Signature: tambah(Float64, Float64)
define double @julia_tambah_N(double %"x::Float64", double %"y::Float64") local_unnamed_addr #0 {
top:
%0 = fadd double %"x::Float64", %"y::Float64"
ret double %0
}Blok IR di atas memuat baris fadd double, yaitu instruksi penjumlahan titik mengambang untuk bilangan pecahan pada arsitektur x86-64. Apabila fungsi yang sama dipanggil dengan dua bilangan Int64, LLVM menghasilkan add i64, instruksi penjumlahan bilangan bulat. Keduanya berbeda karena Julia mengompilasi ulang fungsi untuk setiap kombinasi tipe, dan itulah yang memungkinkan satu definisi fungsi tetap abstrak tanpa mengorbankan kecepatan. Akhiran angka pada nama fungsi diganti menjadi _N oleh skrip karena nilainya dapat berubah antarsesi.
Notasi matematis yang utuh di kode#
Notasi Julia dirancang agar rumus tetap dekat dengan bentuk aslinya. Bilangan π tersedia sebagai konstanta π, pangkat ditulis dengan ^, dan nama variabel boleh menggunakan karakter Unicode. Konstanta π dikenali Julia tanpa pemuatan pustaka apa pun, sehingga rumus luas lingkaran dapat ditulis apa adanya, termasuk interpolasi nilai r ke dalam string dengan notasi $r.
# luas lingkaran berjari-jari 10
r = 10.0
luas = π * r^2
println("Luas lingkaran jari-jari $r: ", luas)Luas lingkaran jari-jari 10.0: 314.1592653589793Pola yang sama berlaku untuk rumus yang lebih panjang. Akar persamaan kuadrat berikut dicari dengan rumus ABC.
Skrip kuadrat.jl menerapkan rumus tersebut pada persamaan x^2 - 5x + 6 = 0. Skrip dijalankan dari terminal dengan perintah julia kuadrat.jl, dan seluruh baris keluaran diproduksi oleh println.
# akar persamaan kuadrat x^2 - 5x + 6 = 0
a = 1.0
b = -5.0
c = 6.0
diskriminan = b^2 - 4 * a * c
x1 = (-b + sqrt(diskriminan)) / (2 * a)
x2 = (-b - sqrt(diskriminan)) / (2 * a)
println("Akar pertama: ", x1)
println("Akar kedua: ", x2)
println("Cek: x1^2 - 5x1 + 6 = ", x1^2 - 5 * x1 + 6)Akar pertama: 3.0
Akar kedua: 2.0
Cek: x1^2 - 5x1 + 6 = 0.0Keluarannya berupa 3.0 dan 2.0, bukan bilangan bulat, karena koefisiennya ditulis sebagai Float64 sehingga seluruh perhitungan berjalan pada bilangan pecahan. Baris pemeriksaan terakhir mengevaluasi kembali persamaannya pada x1 dan menghasilkan 0.0, yang menandakan akar yang ditemukan memenuhi persamaan. Notasi seperti π, ^, dan $r akan muncul berulang kali pada jalur ini karena memang menjadi cara standar menulis kode Julia.
Kesalahan umum#
Kesalahan pertama adalah menilai kecepatan Julia dari pemanggilan pertama sebuah fungsi. Pemanggilan itu mencakup waktu kompilasi just-in-time, sehingga terasa jauh lebih lambat daripada pemanggilan kedua yang sudah menggunakan kode mesin yang tersimpan. Perbaikannya, panggil fungsi sekali untuk menghangatkan, kemudian ukur pada pemanggilan-pemanggilan berikutnya.
Kesalahan kedua adalah menulis perulangan panjang di tingkat teratas skrip dengan variabel global. Tipe variabel global dapat berubah kapan saja, sehingga inferensi tipe tidak dapat mengunci satu instruksi mesin untuk seluruh perulangan, dan perhitungan berjalan jauh lebih lambat dari kemampuan Julia. Perbaikannya, bungkus perhitungan dalam fungsi dan terima datanya melalui argumen, karena kode di dalam fungsi memberi inferensi tipe ruang kerja yang jauh lebih baik.
Kesalahan ketiga adalah mengira setiap fungsi harus diberi anotasi tipe agar cepat. Spesialisasi berlangsung otomatis dari tipe argumen yang dikirim, sehingga fungsi tanpa anotasi tipe pun menghasilkan kode mesin yang sama cepatnya. Anotasi tipe tetap berguna untuk kejelasan kontrak fungsi, tetapi bukan syarat kecepatan pada kasus umum.
Ringkasan#
- Julia adalah bahasa tingkat tinggi bebas dan terbuka dengan lisensi MIT untuk komputasi numerik, dirilis pertama kali pada tahun 2012.
- Fungsi dikompilasi secara just-in-time melalui LLVM, dengan satu versi kode mesin untuk setiap kombinasi tipe argumen.
- Hasil kompilasi disimpan dan digunakan ulang, sehingga notasi yang abstrak tidak mengorbankan kecepatan eksekusi.
- Konstanta
π, pangkat^, dan interpolasi string$rmenjaga rumus tetap dekat dengan bentuk matematisnya.
Referensi#
- Why We Created Julia — The Julia Language — Artikel perancang Julia yang menyatakan tujuan desain bahasa ini.
- Getting Started — Julia Documentation — Panduan resmi memulai Julia, termasuk menjalankan REPL dan skrip.
- Julia Language — The Julia Language — Situs resmi Julia yang memuat rilis, dokumentasi, dan komunitasnya.