Bagaimana Elixir Meletakkan Data Anda di Memori

Informasi

Bagaimana Elixir Meletakkan Data Anda di Memori – Elixir memudahkan untuk bekerja dengan struktur data yang canggih. Kami telah menempuh perjalanan jauh C, di mana Anda harus mengelola setiap detail representasi data Anda di memori. Meski begitu, cara Elixir meletakkan data Anda di memori dapat memiliki implikasi nyata pada kinerja aplikasi Anda. Dalam artikel ini, Sasha Fonseca memberi kita tur melalui struktur data internal Elixir dan menunjukkan kepada kita bagaimana mereka dapat memengaruhi kinerja aplikasi.

Bagaimana Elixir Meletakkan Data Anda di Memori

elixir-memory – Bahasa tingkat tinggi menyembunyikan detail untuk meningkatkan pengalaman dan produktivitas pengembang. Namun, ini menyebabkan hilangnya pemahaman tentang sistem yang mendasarinya, mencegah kami menekan tingkat efisiensi dan kinerja tertinggi dari program kami. Manajemen memori adalah salah satu aspek yang disarikan dari kami dalam pertukaran ini. Dalam seri ini, kita akan melihat bagaimana BEAM VM bekerja dalam hal ini dan belajar membuat keputusan yang lebih baik saat menulis kode Elixir.

Baca Juga : Tips for Choosing RAM Memory for Laptop PC Motherboards

Apa itu manajemen memori?

Program beroperasi di atas nilai atau kumpulan nilai. Nilai-nilai ini disimpan dalam memori sehingga mereka dapat dirujuk kembali kapan pun diperlukan untuk melakukan operasi tertentu. Memahami representasi memori yang mendasari memberi kita kemampuan untuk memilih secara memadai struktur data mana yang akan digunakan dalam kasus penggunaan tertentu.

Melakukannya dengan tangan

Berbicara tentang manajemen memori biasanya membuat beberapa programmer kembali ke saat mereka pertama kali belajar C. Bahasa tingkat rendah ini menawarkan beberapa fungsi, seperti mallocdan free, yang, pada gilirannya, memberdayakan pengembang untuk merebut dan membebaskan memori sesuka hati:

int *my_array;
my_array = (int *) malloc(sizeof(int) * 5);
free(my_array);

Namun, programmer bermata elang (atau mereka yang pernah jatuh ke dalam perangkap itu sebelumnya) mungkin bertanya apa yang terjadi jika kita tidak menelepon freeketika kita tidak lagi membutuhkan array? Ini membawa kita ke masalah yang biasa dikenal sebagai kebocoran memori .

Ingatan itu seperti sebotol paku. Saat program kita pertama kali dimulai, toples ini sudah penuh dan dapat digunakan sesuka hati. Namun, tidak memasukkan paku kembali ke dalam toples saat kita tidak lagi membutuhkannya pada akhirnya dapat menyebabkan tidak memiliki cukup paku untuk melakukan pekerjaan kita. Memori dalam sistem kita adalah toples kita, dan kita harus melacak kuku kita atau berisiko kehabisannya. Ini adalah premis untuk bahasa tingkat rendah dengan manajemen memori manual, seperti C.

Melepaskan roda pelatihan

Sebaliknya, kita dapat melihat bahasa tingkat tinggi, seperti Elixir, yang menangani masalah ini untuk kita:

input = [1, 2, 3, 4, 5]
my_list = Enum.map(input, fn(x) -> x * 2 end)

Dalam contoh di atas, kita dapat memetakan beberapa input dengan panjang yang tidak diketahui ke dalam daftar tanpa harus mengalokasikan memori my_listdengan cara apa pun. Sebenarnya, sulit untuk memberikan contoh tandingan langsung ke kode C yang ditunjukkan sebelumnya karena tidak ada cara untuk menangani memori di Elixir. Tapi, jika kita tidak bisa membebaskan variabel-variabel ini dari memori, bukankah itu berarti kita akan menyebabkan kebocoran? Untungnya, bahasa semacam ini juga menangani hal ini untuk kami melalui mekanisme yang disebut pengumpulan sampah .

Pengumpulan sampah di dunia BEAM membutuhkan artikel tersendiri. , singkatnya, VM tahu untuk melacak variabel mana yang akan digunakan di masa mendatang dan variabel mana yang dapat dihapus dari memori. Ada banyak implementasi berbeda untuk pengumpulan sampah di berbagai bahasa, masing-masing dengan kelebihan dan kekurangannya sendiri.

Pengetahuan adalah kekuatan

Meskipun Elixir menangani alokasi memori untuk kita, itu masih harus dilakukan, entah bagaimana, di bawah tenda. Jadi, memahami bagaimana ini dilakukan dan bagaimana tipe data BEAM dioperasikan (misalnya, dibuat, dibaca, dan dimodifikasi) dan direpresentasikan dalam memori dapat membawa kita ke pilihan yang berbeda dalam program kita.

Jejak memori dan kinerja sistem kami dipengaruhi oleh keputusan ini. Selain itu, memiliki wawasan tentang hubungan antara perangkat lunak dan perangkat keras memberi penghargaan kepada pengembang dengan keterampilan penalaran yang lebih baik. Ini mungkin berguna saat men-debug kesalahan aneh atau mengimplementasikan fitur mission-critical.

Informasi tentang VM BEAM Elixir langka dan dapat berbelit-belit dan sulit dipahami. Oleh karena itu, artikel ini bertujuan untuk menyajikan kepada pengembang dasar-dasar manajemen memori dan mengumpulkan semua pengetahuan yang tersebar tentang topik ini di satu tempat.

Kata memori adalah kata

Sebuah kata adalah unit data dasar untuk arsitektur komputer tertentu; dengan demikian, ini bervariasi dari 4 byte hingga 8 byte untuk sistem 32-bit dan 64-bit, masing-masing. Untuk mengetahui ukuran kata dari sistem yang sedang berjalan, Anda dapat menggunakan :erlang.system_info(:wordsize)fungsi.

iex(1)> :erlang.system_info(:wordsize)

Semua struktur data di Erlang diukur dalam unit ini, dan Anda dapat menemukan ukuran kata menggunakan fungsi :erts_debug.size/1and :erts_debug.flat_size/1. Namun, perlu diingat bahwa ini adalah API yang tidak berdokumen dan tidak cocok untuk penggunaan produksi.

Byte berukuran gigitan: literal dan langsung

Wadah memori paling dasar di dunia BEAM adalah segera . Ini adalah tipe yang cocok dengan satu kata mesin. Segera mungkin bilangan bulat kecil bilangan bulat besar menggunakan aritmatika bignum untuk representasi , pengidentifikasi proses lokal (PID), port, atom, atau nilai NIL (yang merupakan singkatan dari daftar kosong dan tidak boleh disamakan dengan atom nil).

atom

Atom adalah yang paling berbeda dari yang terdekat. Mereka mengisi tabel atom khusus dan tidak pernah mengumpulkan sampah. Ini berarti begitu sebuah atom dibuat, ia akan tinggal di memori sampai sistem keluar. 6 bit pertama atom digunakan untuk mengidentifikasi nilai sebagai tipe atom. Sisanya 26 bit mewakili nilai itu sendiri. Jadi, tersisa \(2^6 = 32M\) slot yang tersedia di tabel atom (untuk sistem 32-bit dan 64-bit).

Ketika atom tertentu dibuat lebih dari sekali, jejak memorinya tidak berlipat ganda. Alih-alih menyalin atom, referensi digunakan untuk mengakses slot masing-masing dalam tabel . Ini dapat dilihat saat menggunakan :erts_debug.size/1fungsi dengan argumen atom:

iex(1)> :erts_debug.size(:my_atom)

Ukuran kata adalah nol karena atom itu sendiri tinggal di tabel atom, dan kita hanya meneruskan referensi ke slot tertentu itu.

Sebuah gotcha terkenal di dunia Erlang adalah bahwa melelahkan tabel atom crash BEAM. Meskipun tabel memiliki slot hingga 32 juta, seperti yang kita lihat, tabel dapat rentan terhadap serangan eksternal. Mengurai input eksternal ke atom dapat menyebabkan VM yang berjalan lama mogok. Oleh karena itu, ini dianggap sebagai praktik terbaik untuk mengurai input eksternal ke string kapan pun sesuai (misalnya, parameter titik akhir HTTP).

Nilai kotak

Tidak semua nilai cocok dengan satu kata mesin. BEAM VM menggunakan konsep nilai kotak untuk mengakomodasi nilai yang lebih besar. Ini terbuat dari pointer yang mengacu pada tiga paket:

Header: 1 kata untuk merinci jenis nilai kotak.
Arity: 26 bit, menunjukkan ukuran istilah Erlang berikut.
Array kata: panjangnya sama dengan arity yang didefinisikan dalam paket sebelumnya. Kata-kata ini berisi data untuk struktur data yang ada.

Nilai kotak mencakup tipe data yang tidak tercakup oleh segera: daftar, tupel, peta, binari, PID dan port jarak jauh, float, bilangan bulat besar, fungsi, dan ekspor. Dalam artikel ini, kita akan fokus pada empat jenis pertama.

Daftar

Dalam VM BEAM, daftar diimplementasikan sebagai daftar tertaut tunggal dan terdiri dari sel. Setiap sel dibagi menjadi dua bagian:

Nilai elemen daftar tertentu. Misalnya, nilai elemen pertama [1, 2, 3]adalah 1.
Ekor, yang menunjuk ke elemen berikutnya dalam daftar. Setiap ekor terus menunjuk ke elemen berikutnya sampai ekor terakhir menunjuk ke NILnilai, yang mewakili daftar kosong.

Dalam arti, daftar Erlang dapat dilihat sebagai pasangan di mana bagian pertama adalah nilai elemen daftar, dan bagian kedua adalah daftar lain. Gambar berikut mengilustrasikan konsep ini untuk daftar [1, 2, 3]: Pilihan implementasi ini bukannya tanpa manfaat dan kompromi. Karena ini adalah daftar tertaut tunggal, itu hanya dapat dilalui ke depan. Ini rumit ketika menemukan panjang daftar karena semua elemen harus dilalui. Dengan demikian, operasi ini O(n)dalam Notasi Big-O .

Namun, ini menguntungkan ketika menambahkan elemen baru ke kepala daftar – dicapai dengan sintaks ini [1 | [2, 3, 4]][^Ini disebut consing , atau operator kontra di dunia pemrograman fungsional]. Karena ekor menunjuk ke sisa daftar, seperti pada contoh sebelumnya, VM hanya perlu membuat sel baru dengan nilai 1dan ekor menunjuk ke daftar yang ada, [2, 3, 4].

Hanya kepala baru yang harus dibuat dalam memori, dan sisa daftar dapat digunakan kembali. Ini meninggalkan kita dengan O(1)waktu yang konstan. Sebaliknya, menambahkan daftar ( [1, 2, 3] ++ [4, 5, 6]) adalah O(N), dengan Npanjang daftar pertama. VM harus melintasi Nelemennya untuk menemukan yang terakhir dan mengarahkannya ke daftar kedua alih-alih nilai NIL.

Demikian pula, operasi sebaliknya (misalnya, :lists.reverse/1atau Enum.reverse/1) juga O(N). Sekali lagi, BEAM hanya melintasi daftar asli dan menyalin setiap elemen ke kepala daftar baru. Operasi lain, seperti menghitung perpotongan antara dua daftar ( [1, 2, 3] — [4, 5, 6]), menimbulkan hukuman yang lebih berat dalam hal kompleksitas waktu. Dalam operasi ini, untuk setiap elemen pada daftar pertama, yang kedua harus dilalui untuk memeriksa apakah itu ada dan menambahkannya ke daftar baru jika hal ini terjadi. Oleh karena itu, kita harus mempertimbangkan O(N) * O(M)kompleksitas. Dalam situasi di mana persimpangan dua daftar harus dihitung, menggunakan struktur data lain, seperti Set, dapat menghasilkan kinerja atau efisiensi memori yang lebih baik.

Aspek terakhir yang perlu dipertimbangkan dalam daftar adalah lokalitas referensi dalam memori, terutama secara spasial. Singkatnya, seberapa dekat setiap elemen daftar dalam memori. Ketika datang ke daftar, elemen mereka memiliki kemungkinan besar untuk berdekatan saat daftar dibuat dalam satu lingkaran. Ini bermanfaat dan diinginkan karena, sebagian besar waktu, elemen harus diakses secara berurutan.

Leave a Reply