Return By Reference / Return By Value ?
Benarkah return by reference (pointer) lebih bagus dibandingkan return by value ?
Banyak programmer golang yang ketika membuat fungsi atau method selalu menggunakan return by reference. Ketika saya tanya "kenapa menggunakan return by reference ?", kebanyakan dari mereka menjawab "lebih bagus, karena kita menggunakan alamat memory yang sama". Apakah benar seperti itu?
Jika saya diberi pertanyaan seperti itu, maka jawaban saya adalah Tergantung cara penggunaan
.
Golang memiliki memory manajement yang bisa dibilang sangat bagus. Golang memiliki gerbage collector (GC), tetapi penggunaan GC disini tidaklah gratis seperti yang kita banyangkan. Biaya yang kita bayarkan seperti resource, latency dan sebagainya.
Secara default, GC di golang running setiap 2 menit sekali (source bisa dibaca disini). Jadi selama GC itu belum dijalankan semua alamat memory akan terus bertambah di heap.
Lalu apakah semua alamat memory akan masuk di heap untuk dibersikhan oleh GC?. Di golang tidak semua alamat memory masuk kedalam heap, golang akan memvalidate lifetime dari setiap alamat memory tersebut apakah melebihi lifetime dari fungsi yang membuatnya atau tidak, bila lebih dari fungsi yang membuatnya maka akan masuk kedalam heap.
example:
code pertama
func Hello() string {
s := "hello"
return s
}
func main() {
print(Hello())
}
Dari contoh kode di atas, kita dapat melihat bahwa variable s s := "hello
lifetimenya tidak melebihi dari fungsi Hello().
code kedua
func Hello() *string {
s := "hello"
return &s
}
func main() {
print(Hello())
}
Jika kita lihat dari contoh kode di atas, kita dapat melihat bahwa variable s s := "hello
lifetimenya melebihi dari fungsi Hello() maka alamat memory s tersebut akan disimpan kedalam heap.
Apakah benar seperti itu ?, oke.., mari kita berbicara dengan compiler.
Kita dapat tahu bahwa pada command go build
memiliki options -gcflags
-gcflags '[pattern=]arg list'
arguments to pass on each go tool compile invocation.
Sekarang pada command go tool compile
memiliki option -l dan -m
-m print optimization decisions
-l disable inlining
lalu kita jalan kan perintah
go build -gcflags "-l -m" .
Pada kode pertama kita akan mendapatkan output:
# command-line-arguments
./main.go:14:13: ... argument does not escape
./main.go:14:19: Hello() escapes to heap
Dan pada kode kedua mendapatkan output:
# command-line-arguments
./main.go:9:6: moved to heap: s
./main.go:14:13: ... argument does not escape
Dari sini kita dapat melihat pada hasil kode ke 2, bahwa alamat memory dari s
dipindahkan kedalam heap untuk selanjutnya di bersihkan oleh GC bila tidak lagi digunakan, sementara pada kode pertama golang akan langsung menghapus alamat memory tersebut setelah fungsi Hello()
selesai.
Oke kita coba lihat kode assemblynya,
pada kode pertama kita akan menghasilkan output:
main.Hello STEXT nosplit size=13 args=0x0 locals=0x0 funcid=0x0 align=0x0
0x0000 TEXT main.Hello(SB), NOSPLIT|ABIInternal, $0-0
0x0000 FUNCDATA $0, gclocals·g2BeySu+wFnoycgXfElmcg==(SB)
0x0000 FUNCDATA $1, gclocals·g2BeySu+wFnoycgXfElmcg==(SB)
0x0000 LEAQ go.string."hello"(SB), AX
0x0007 MOVL $5, BX
0x000c RET
0x0000 48 8d 05 00 00 00 00 bb 05 00 00 00 c3 H............
rel 3+4 t=14 go.string."hello"+0
main.main STEXT size=71 args=0x0 locals=0x18 funcid=0x0 align=0x0
0x0000 TEXT main.main(SB), ABIInternal, $24-0
0x0000 CMPQ SP, 16(R14)
0x0004 PCDATA $0, $-2
0x0004 JLS 62
0x0006 PCDATA $0, $-1
0x0006 SUBQ $24, SP
0x000a MOVQ BP, 16(SP)
0x000f LEAQ 16(SP), BP
0x0014 FUNCDATA $0, gclocals·g2BeySu+wFnoycgXfElmcg==(SB)
0x0014 FUNCDATA $1, gclocals·g2BeySu+wFnoycgXfElmcg==(SB)
0x0014 PCDATA $1, $0
0x0014 CALL runtime.printlock(SB)
0x0019 LEAQ go.string."hello"(SB), AX
0x0020 MOVL $5, BX
0x0025 CALL runtime.printstring(SB)
0x002a CALL runtime.printnl(SB)
0x002f CALL runtime.printunlock(SB)
0x0034 MOVQ 16(SP), BP
0x0039 ADDQ $24, SP
0x003d RET
0x003e NOP
0x003e PCDATA $1, $-1
0x003e PCDATA $0, $-2
0x003e NOP
0x0040 CALL runtime.morestack_noctxt(SB)
0x0045 PCDATA $0, $-1
0x0045 JMP 0
Bisa kita lihat pada output diatas, main.Hello(SB), NOSPLIT|ABIInternal, $0-0
. NOSPLIT adalah sebuah opcode atau instruksi pada assembly yang digunakan untuk menghindari pembuatan stack frame pada saat fungsi dipanggil. Pembuatan stack frame dapat memakan waktu dan ruang yang cukup besar pada memori, sehingga NOSPLIT digunakan untuk mengoptimalkan kinerja program dengan menghindari pembuatan stack frame.
pada kode kedua kita akan menghasilkan output :
main.Hello STEXT size=72 args=0x0 locals=0x18 funcid=0x0 align=0x0
0x0000 TEXT main.Hello(SB), ABIInternal, $24-0
0x0000 CMPQ SP, 16(R14)
0x0004 PCDATA $0, $-2
0x0004 JLS 65
0x0006 PCDATA $0, $-1
0x0006 SUBQ $24, SP
0x000a MOVQ BP, 16(SP)
0x000f LEAQ 16(SP), BP
0x0014 FUNCDATA $0, gclocals·g2BeySu+wFnoycgXfElmcg==(SB)
0x0014 FUNCDATA $1, gclocals·g2BeySu+wFnoycgXfElmcg==(SB)
0x0014 LEAQ type.string(SB), AX
0x001b PCDATA $1, $0
0x001b NOP
0x0020 CALL runtime.newobject(SB)
0x0025 MOVQ $5, 8(AX)
0x002d LEAQ go.string."hello"(SB), CX
0x0034 MOVQ CX, (AX)
0x0037 MOVQ 16(SP), BP
0x003c ADDQ $24, SP
0x0040 RET
0x0041 NOP
0x0041 PCDATA $1, $-1
0x0041 PCDATA $0, $-2
0x0041 CALL runtime.morestack_noctxt(SB)
0x0046 PCDATA $0, $-1
0x0046 JMP 0
main.main STEXT size=86 args=0x0 locals=0x20 funcid=0x0 align=0x0
0x0000 TEXT main.main(SB), ABIInternal, $32-0
0x0000 CMPQ SP, 16(R14)
0x0004 PCDATA $0, $-2
0x0004 JLS 79
0x0006 PCDATA $0, $-1
0x0006 SUBQ $32, SP
0x000a MOVQ BP, 24(SP)
0x000f LEAQ 24(SP), BP
0x0014 FUNCDATA $0, gclocals·J5F+7Qw7O7ve2QcWC7DpeQ==(SB)
0x0014 FUNCDATA $1, gclocals·u97U1TPSCOlmat2W1oBl9Q==(SB)
0x0014 FUNCDATA $2, main.main.stkobj(SB)
0x0014 (<unknown line number>) NOP
0x0014 LEAQ go.string."hello"(SB), AX
0x001b MOVQ AX, main.s+8(SP)
0x0020 MOVQ $5, main.s+16(SP)
0x0029 PCDATA $1, $1
0x0029 CALL runtime.printlock(SB)
0x002e LEAQ main.s+8(SP), AX
0x0033 PCDATA $1, $0
0x0033 CALL runtime.printpointer(SB)
0x0038 CALL runtime.printnl(SB)
0x003d NOP
0x0040 CALL runtime.printunlock(SB)
0x0045 MOVQ 24(SP), BP
0x004a ADDQ $32, SP
0x004e RET
0x004f NOP
0x004f PCDATA $1, $-1
0x004f PCDATA $0, $-2
0x004f CALL runtime.morestack_noctxt(SB)
0x0054 PCDATA $0, $-1
0x0054 JMP 0
Dari output diatas kita dapat melihat kode, CALL runtime.newobject(SB)
yang berfungsi untuk mengalokasikan memory pada heap untuk objek yang baru dibuat.
Sekian artikel kali ini, bila bermanfaat silahkan share.
Subscribe to my newsletter
Read articles from chandra agung rizky directly inside your inbox. Subscribe to the newsletter, and don't miss out.
Written by