Kebocoran memori pada program tanpa pengumpul sampah hanya dapat ditemukan dengan mencatat setiap alokasi, dan paket core:mem menyediakan alatnya dalam bentuk Tracking_Allocator. Alokator pelacak membungkus alokator lain, mencatat setiap permintaan ke dalam map, dan menyisakan laporan berupa entri yang belum dilepaskan ketika program hampir berakhir. Topik ini membahas cara kerjanya, program yang sengaja membiarkan satu alokasi bocor beserta laporannya, versi yang sudah diperbaiki, dan cara memasang pola ini pada program nyata.
Cara Tracking_Allocator membungkus alokator#
Tracking_Allocator adalah struct yang menyimpan alokator penopang dan map bernama allocation_map yang berkunci pointer hasil alokasi. Setiap permintaan diteruskan ke alokator penopang, lalu entri dicatat berisi ukuran, alignment, mode permintaan, error, dan lokasi sumber berupa berkas serta baris pemanggil. Field location bertipe Source_Code_Location inilah yang membuat laporan kebocoran menunjuk ke baris make yang bersalah. Statistik agregat juga ikut tercatat, antara lain total_memory_allocated, total_allocation_count, total_memory_freed, total_free_count, dan current_memory_allocated. Pemasangannya mengikuti pola empat langkah yang selalu sama.
- Deklarasikan
ta: mem.Tracking_Allocatordi awal proc yang dipantau. - Pasangkan dengan alokator penopang melalui
mem.tracking_allocator_init(&ta, context.allocator). - Jadikan alokator aktif melalui
context.allocator = mem.tracking_allocator(&ta)di dalam lingkup yang disimpan dan dipulihkan seperti pada topik sistem context. - Di akhir program, iterasi
ta.allocation_mapuntuk melaporkan entri yang tersisa.
Satu perbedaan perilaku perlu diwaspadai: pelepasan pointer yang tidak pernah dialokasikan dari pelacak ini memanggil callback panic bawaan, sehingga free yang keliru langsung terdeteksi saat program berjalan. Kebutuhan internal pelacak, yaitu memori untuk map-nya sendiri, dialokasikan dari alokator yang dipasang saat inisialisasi dan tidak ikut tercatat dalam map.
Program yang sengaja bocor#
Program berikut mengikuti pola empat langkah itu, dengan satu alokasi yang sengaja dibiarkan tanpa delete dan satu alokasi yang dilepaskan melalui defer. Laporan di akhir program hanya menampilkan entri yang tersisa, sehingga kebocoran yang benar-benar ada menjadi mudah dibaca.
package main
import "core:fmt"
import "core:mem"
import "core:strings"
import "base:runtime"
// proc ini sengaja membiarkan satu alokasi tidak dilepaskan
proses_pengukuran :: proc() {
// alokasi ini bocor: tidak ada delete yang menyertainya
suhu := make([]f32, 3)
suhu[0], suhu[1], suhu[2] = 28.4, 29.1, 30.2
// alokasi ini dilepaskan lewat defer
cadangan := make([]f32, 2)
defer delete(cadangan)
cadangan[0], cadangan[1] = 28.4, 29.1
}
// potong jalur berkas sehingga laporan hanya menampilkan nama berkasnya
nama_berkas :: proc(lokasi: runtime.Source_Code_Location) -> string {
if i := strings.last_index_byte(lokasi.file_path, '/'); i >= 0 {
return lokasi.file_path[i + 1 :]
}
return lokasi.file_path
}
main :: proc() {
ta: mem.Tracking_Allocator
mem.tracking_allocator_init(&ta, context.allocator)
defer mem.tracking_allocator_destroy(&ta)
ctx_awal := context
context.allocator = mem.tracking_allocator(&ta)
proses_pengukuran()
// setiap entri yang masih tersisa di allocation_map adalah kebocoran
for _, entri in ta.allocation_map {
fmt.printf("kebocoran %v byte pada %v:%v\n", entri.size, nama_berkas(entri.location), entri.location.line)
}
if len(ta.allocation_map) == 0 {
fmt.println("tidak ada alokasi yang tersisa")
}
context = ctx_awal
}# sumberkan lingkungan sandbox jalur odin bila belum tersedia
[ -f /home/kal/my-project/tools/env.sh ] && source /home/kal/my-project/tools/env.sh
# jalankan program dari dalam folder topik
odin run laporan-kebocoran.odin -filekebocoran 12 byte pada laporan-kebocoran.odin:11Laporan itu terbaca langsung. Ukuran 12 byte berasal dari tiga nilai f32 pada alokasi suhu, sedangkan lokasinya menunjuk baris 11, tepat pada pemanggilan make yang tanpa pasangan delete. Angka setelah nama berkas adalah nomor baris, sehingga penelusuran dimulai dari baris itu, bukan dari pencarian teks ke seluruh program. Program melaporkan nama berkas tanpa jalur foldernya, sehingga keluaran yang sama muncul di mesin mana pun program dijalankan. Alokasi cadangan tidak muncul karena defer melepaskannya sebelum proc berakhir, dan perbedaan antara dua alokasi yang hampir identik itulah yang menunjukkan kekuatan laporan berbasis lokasi sumber.
Versi yang sudah diperbaiki#
Perbaikannya menambah satu baris defer delete(suhu) pada titik alokasi yang bocor. Versi berikut juga menampilkan statistik agregat yang ikut tercatat oleh pelacak.
package main
import "core:fmt"
import "core:mem"
proses_pengukuran :: proc() {
// setiap make kini disandingkan dengan delete
suhu := make([]f32, 3)
defer delete(suhu)
suhu[0], suhu[1], suhu[2] = 28.4, 29.1, 30.2
cadangan := make([]f32, 2)
defer delete(cadangan)
cadangan[0], cadangan[1] = 28.4, 29.1
}
main :: proc() {
ta: mem.Tracking_Allocator
mem.tracking_allocator_init(&ta, context.allocator)
defer mem.tracking_allocator_destroy(&ta)
ctx_awal := context
context.allocator = mem.tracking_allocator(&ta)
proses_pengukuran()
// laporan kebocoran
for _, entri in ta.allocation_map {
fmt.printf("kebocoran %v byte pada %v\n", entri.size, entri.location)
}
if len(ta.allocation_map) == 0 {
fmt.println("tidak ada alokasi yang tersisa")
}
// statistik yang ikut dicatat tracking allocator
fmt.printf("total alokasi %v byte dalam %v kali permintaan\n", ta.total_memory_allocated, ta.total_allocation_count)
fmt.printf("total pelepasan %v byte dalam %v kali pelepasan\n", ta.total_memory_freed, ta.total_free_count)
context = ctx_awal
}# sumberkan lingkungan sandbox jalur odin bila belum tersedia
[ -f /home/kal/my-project/tools/env.sh ] && source /home/kal/my-project/tools/env.sh
# jalankan program dari dalam folder topik
odin run laporan-bersih.odin -filetidak ada alokasi yang tersisa
total alokasi 20 byte dalam 2 kali permintaan
total pelepasan 20 byte dalam 2 kali pelepasanAngka 20 byte adalah jumlah dua alokasi, yaitu 12 byte untuk suhu dan 8 byte untuk cadangan, dan total alokasi sama dengan total pelepasan karena keduanya sudah berpasangan. Selisih antara total alokasi dan total pelepasan di akhir program adalah indikator cepat yang tidak memerlukan iterasi map. Field current_memory_allocated menyediakan angka yang sama secara langsung, sedangkan peak_memory_allocated mencatat pemakaian tertinggi sepanjang pemantauan. Satu perbedaan penting pada versi ini terletak pada penempatan baris: defer delete(suhu) ditempelkan tepat di bawah make, sehingga pelepasan tetap berjalan walaupun proc keluar melalui jalur mana pun.
Memasang laporan pada program nyata#
Pola ini dipasang pada proc main ketika program baru dikembangkan, dan dilepas setelah laporan bersih stabil karena pencatatan per alokasi menambah biaya pada map yang tumbuh terus. Pada program yang berjalan dalam putaran kerja, laporan dibaca per titik pemeriksaan yang dipilih, misalnya di setiap akhir sesi pemrosesan batch, alih-alih menunggu akhir program. Pelacak juga menerima alokator penopang apa pun, sehingga arena atau pool yang dibangun dengan tangan dapat dilaporkan kebocorannya dengan cara yang sama.
Dua batasan perlu diingat saat pemasangan. Pelacak pada contoh hanya membungkus context.allocator, sehingga alokasi dari context.temp_allocator tidak masuk laporan, dan pemilihan penopang menentukan apa yang terpantau. Field bad_free_array beserta callback pelepasan liar menyediakan deteksi kedua, yaitu pointer yang dilepaskan tanpa pernah dialokasikan melalui pelacak. Perilaku bawaan deteksi ini adalah menghentikan program agar kesalahan tidak menyebar.
Kesalahan umum#
Kesalahan pertama adalah menghapus pelacak padahal context masih memakainya. Pemanggilan tracking_allocator_destroy membebaskan map-nya, sehingga alokasi yang datang setelahnya menulis pada map yang sudah dilepaskan. Pola pemasangan pada topik ini menghindari urutan itu dengan menyimpan ctx_awal dan memulihkan context sebelum defer penghapusan berjalan di akhir proc.
Kesalahan kedua adalah memeriksa len(ta.allocation_map) di tengah program dan menyimpulkan bahwa programnya bersih. Alokasi yang bocor biasanya terjadi di proc yang belum dipanggil pada titik pemeriksaan itu, sehingga laporan hanya berlaku untuk lingkup yang sudah selesai dijalankan. Pemeriksaan ditempatkan setelah lingkup pemantauan berakhir atau pada titik pemeriksaan yang sengaja dipilih setelah seluruh proc diuji.
Ringkasan#
Tracking_Allocatormembungkus alokator penopang dan mencatat setiap alokasi diallocation_mapbeserta ukuran dan lokasi sumbernya.- Iterasi
allocation_mappada akhir program menghasilkan laporan kebocoran yang menunjuk barismakeyang tanpa pasangandelete. - Statistik
total_memory_allocateddantotal_memory_freedmemberi indikator selisih alokasi dan pelepasan tanpa membaca map. - Pelepasan pointer yang tidak tercatat memicu panic bawaan, sehingga free yang keliru terdeteksi segera.
Referensi#
- Paket core:mem — Referensi pustaka Odin
— Dokumentasi
Tracking_Allocatorbeserta proc pemasangannya. - Overview — Odin Documentation — Panduan resmi dengan bagian Allocators sebagai dasar pola pelacakan.