Pertanyaan insinyur tingkat awal tentang manajemen memori

9

Sudah beberapa bulan sejak saya memulai posisi saya sebagai pengembang perangkat lunak entry level. Sekarang saya sudah melewati beberapa kurva belajar (misalnya bahasa, jargon, sintaks VB dan C #) saya mulai fokus pada topik yang lebih esoteris, untuk menulis perangkat lunak yang lebih baik.

Sebuah pertanyaan sederhana yang saya sampaikan kepada seorang rekan kerja dijawab dengan "Saya fokus pada hal-hal yang salah." Sementara saya menghormati rekan kerja ini, saya tidak setuju bahwa ini adalah "hal yang salah" untuk difokuskan.

Berikut adalah kode (dalam VB) dan diikuti oleh pertanyaan.

Catatan: Function GenerateAlert () mengembalikan bilangan bulat.

Dim alertID as Integer = GenerateAlert()
_errorDictionary.Add(argErrorID, NewErrorInfo(Now(), alertID))    

vs ...

 _errorDictionary.Add(argErrorID, New ErrorInfo(Now(), GenerateAlert()))

Saya awalnya menulis yang terakhir dan menulis ulang dengan "Dim alertID" sehingga orang lain mungkin merasa lebih mudah dibaca. Tapi di sini ada kekhawatiran dan pertanyaan saya:

Jika seseorang menulis ini dengan Dim AlertID, sebenarnya akan memakan lebih banyak memori; terbatas tetapi lebih, dan haruskah metode ini dipanggil berkali-kali mungkinkah menimbulkan masalah? Bagaimana .NET akan menangani objek ini AlertID. Di luar. NET harus satu secara manual membuang objek setelah digunakan (dekat akhir sub).

Saya ingin memastikan saya menjadi programmer yang berpengetahuan luas yang tidak hanya mengandalkan pengumpulan sampah. Apakah saya terlalu memikirkan ini? Apakah saya fokus pada hal-hal yang salah?

Sean Hobbs
sumber
1
Saya bisa dengan mudah menyatakan bahwa ia 100% karena versi pertama lebih mudah dibaca. Saya berani bertaruh kompiler bahkan dapat mengurus apa yang Anda khawatirkan. Bahkan jika tidak, Anda mengoptimalkan secara prematur.
Rig
6
Saya sama sekali tidak yakin itu akan benar-benar menggunakan lebih banyak memori dengan integer anonim vs integer bernama. Bagaimanapun, ini benar-benar optimasi prematur. Jika Anda perlu khawatir tentang efisiensi pada level ini (saya hampir yakin Anda tidak), Anda mungkin perlu C ++ daripada C #. Adalah baik untuk memahami masalah kinerja dan apa yang terjadi di bawah tenda, tetapi yang ini adalah pohon kecil di hutan besar.
psr
5
Integer bernama vs anonim tidak akan menggunakan lebih banyak memori, terutama karena integer anonim hanyalah integer bernama yang ANDA tidak beri nama (kompiler masih harus menamainya). Paling-paling, bilangan bulat bernama akan memiliki cakupan yang berbeda sehingga mungkin hidup lebih lama. Bilangan bulat anonim hanya akan hidup selama metode tersebut membutuhkannya, nama yang disebutkan akan hidup selama orang tuanya membutuhkannya.
Joel Etherton
Mari kita lihat ... Jika Integer adalah kelas, itu akan dialokasikan pada heap. Variabel lokal (kemungkinan besar di stack) akan menyimpan referensi untuk itu. Referensi akan diteruskan ke objek errorDictionary. Jika runtime melakukan penghitungan referensi (atau semacamnya), maka ketika tidak ada lagi referensi, itu (objek) akan dibatalkan alokasi dari heap. Apa pun yang ada di tumpukan secara otomatis "tidak dialokasikan" begitu metode keluar. Jika ini primitif, maka (kemungkinan besar) akan berakhir di tumpukan.
Paul
Rekan Anda benar: masalah yang diangkat oleh pertanyaan Anda seharusnya bukan tentang pengoptimalan, tetapi tentang keterbacaan .
haylem

Jawaban:

25

"Optimalisasi prematur adalah akar dari semua kejahatan (atau setidaknya sebagian besar) dalam pemrograman." - Donald Knuth

Ketika sampai pada pass pertama Anda, cukup tulis kode Anda sehingga benar dan bersih. Jika nanti ditentukan bahwa kode Anda kritis terhadap kinerja (ada alat untuk menentukan ini yang disebut profiler), maka dapat ditulis ulang. Jika kode Anda tidak dianggap kritis-kinerja, keterbacaan jauh lebih penting.

Apakah layak menggali topik kinerja dan pengoptimalan ini? Tentu saja, tetapi tidak pada dolar perusahaan Anda jika itu tidak perlu.

Christopher Berman
sumber
1
Pada dolar siapa lagi? Majikan Anda mendapat manfaat dari peningkatan keterampilan Anda lebih daripada Anda.
Marcin
Subjek yang tidak berkontribusi langsung ke tugas Anda saat ini? Anda harus mengejar hal-hal ini pada waktu Anda sendiri. Jika saya duduk dan meneliti setiap item CompSci yang menggelitik keingintahuan saya sepanjang hari, saya tidak akan mendapatkan apa-apa. Untuk itulah malam saya.
Christopher Berman
2
Aneh. Beberapa dari kita memiliki kehidupan pribadi, dan seperti yang saya katakan, majikan mendapat manfaat terutama dari penelitian. Kuncinya adalah untuk tidak benar-benar menghabiskan sepanjang hari untuk itu.
Marcin
6
Bagus untukmu. Itu tidak benar-benar membuatnya menjadi aturan universal. Plus, jika Anda mencegah pekerja Anda belajar di tempat kerja, semua yang Anda lakukan adalah mencegah mereka dari belajar, dan mendorong mereka untuk mencari majikan lain yang benar-benar membayar untuk pengembangan staf.
Marcin
2
Saya memahami pendapat yang tercantum dalam komentar di atas; Saya ingin mencatat bahwa saya bertanya saat istirahat makan siang. :) Sekali lagi, terima kasih atas masukan Anda di sini dan di seluruh situs Stack Exchange; ini sangat berharga!
Sean Hobbs
5

Untuk rata-rata .NET program Anda, ya, terlalu banyak berpikir. Mungkin ada saat di mana Anda ingin turun ke mur dan baut dari apa yang terjadi di dalam. NET tetapi ini relatif jarang.

Salah satu transisi sulit yang saya miliki adalah beralih dari menggunakan C dan MASM ke pemrograman VB klasik di tahun 90-an. Saya terbiasa mengoptimalkan segalanya untuk ukuran dan kecepatan. Saya harus melepaskan pemikiran ini untuk sebagian besar dan membiarkan VB melakukan itu untuk menjadi efektif.

jfrankcarr
sumber
5

Seperti yang biasa dikatakan rekan kerja saya:

  1. Buat itu bekerja
  2. Perbaiki semua bug yang membuatnya berfungsi tanpa cacat
  3. Jadikan SOLID
  4. Terapkan pengoptimalan jika kinerjanya lambat

Dengan kata lain, selalu ingat KISS (tetap bodoh sederhana). Karena over-engineering, over-thinking beberapa kode logika mungkin menjadi masalah untuk mengubah logika lain kali. Namun, menjaga kode tetap bersih dan sederhana selalu merupakan praktik yang baik .

Namun, berdasarkan waktu dan pengalaman, Anda akan tahu lebih baik kode mana yang berbau dan perlu segera dioptimalkan.

Yusubov
sumber
3

Haruskah seseorang menulis ini dengan Dim AlertID

Keterbacaan itu penting. Dalam contoh Anda, saya tidak yakin Anda benar-benar membuat hal - hal yang lebih mudah dibaca. GenerateAlert () memiliki nama yang bagus dan tidak menambahkan banyak suara. Mungkin ada penggunaan waktu Anda yang lebih baik.

sebenarnya akan memakan lebih banyak memori;

Saya kira tidak. Itu optimasi yang relatif lurus ke depan untuk membuat kompiler.

haruskah metode ini dipanggil berkali-kali mungkinkah menimbulkan masalah?

Menggunakan variabel lokal sebagai perantara tidak berdampak pada pengumpul sampah. Jika GenerateAlert () memori baru, maka itu penting. Tetapi itu akan menjadi masalah terlepas dari variabel lokal atau tidak.

Bagaimana .NET akan menangani objek ini AlertID.

AlertID bukan objek. Hasil dari GenerateAlert () adalah objek. AlertID adalah variabel, yang jika variabel lokal hanyalah ruang yang terkait dengan metode untuk melacak hal-hal.

Di luar. NET harus ada yang secara manual membuang objek setelah digunakan

Ini adalah pertanyaan tingkat yang tergantung pada konteks yang terlibat dan semantik kepemilikan dari contoh yang disediakan oleh GenerateAlert (). Secara umum, apa pun yang dibuat instance harus menghapusnya. Program Anda mungkin akan terlihat sangat berbeda jika dirancang dengan manajemen memori manual.

Saya ingin memastikan saya menjadi programmer yang berpengetahuan luas yang tidak hanya menyampaikan pengumpulan sampah. Apakah saya terlalu memikirkan ini? Apakah saya fokus pada hal-hal yang salah?

Seorang programmer yang baik menggunakan alat yang tersedia untuk mereka, termasuk pengumpul sampah. Lebih baik memikirkan hal-hal daripada hidup tanpa sadar. Anda mungkin berfokus pada hal-hal yang salah, tetapi karena kami di sini, Anda mungkin juga mempelajarinya.

Telastyn
sumber
2

Buat itu berfungsi, bersihkan, PADAT, MAKA membuatnya bekerja secepat yang dibutuhkan untuk bekerja .

Itu seharusnya urutan yang normal. Prioritas pertama Anda adalah membuat sesuatu yang akan lulus tes penerimaan yang menghilangkan persyaratan. Ini adalah prioritas pertama Anda karena ini adalah prioritas utama klien Anda; memenuhi persyaratan fungsional dalam tenggat waktu pengembangan. Prioritas berikutnya adalah menulis kode yang bersih dan mudah dibaca yang mudah dipahami dan dengan demikian dapat dipertahankan oleh anak cucu Anda tanpa WTF saat diperlukan (hampir tidak pernah menjadi pertanyaan "jika"; Anda atau seseorang setelah Anda AKAN harus pergi kembali dan ubah / perbaiki sesuatu). Prioritas ketiga adalah membuat kode mematuhi metodologi SOLID (atau GRASP jika Anda suka), yang menempatkan kode ke bongkahan modular, dapat digunakan kembali, dan diganti yang lagi membantu pemeliharaan (tidak hanya mereka dapat memahami apa yang Anda lakukan dan mengapa, tetapi ada garis-garis bersih di mana saya bisa dengan operasi menghapus dan mengganti potongan-potongan kode). Prioritas terakhir adalah kinerja; jika kode cukup penting sehingga harus sesuai dengan spesifikasi kinerja, itu hampir pasti cukup penting untuk dibuat benar, bersih dan SOLID terlebih dahulu.

Echoing Christopher (dan Donald Knuth), "optimasi prematur adalah akar dari semua kejahatan". Selain itu, jenis optimisasi yang Anda pertimbangkan sama-sama minor (referensi ke objek baru Anda akan dibuat di stack apakah Anda memberinya nama dalam kode sumber atau tidak) dan tipe yang tidak menyebabkan perbedaan dalam kompilasi IL. Nama variabel tidak dimasukkan ke dalam IL, jadi karena Anda mendeklarasikan variabel tepat sebelum penggunaannya yang pertama (dan mungkin hanya), saya berani bertaruh sejumlah uang bir bahwa IL identik antara dua contoh Anda. Jadi, rekan kerja Anda 100% benar; Anda mencari di tempat yang salah jika Anda mencari instantiation variabel vs inline untuk sesuatu untuk dioptimalkan.

Mikro-optimasi di. NET hampir tidak pernah sepadan (saya berbicara tentang 99,99% kasus). Dalam C / C ++, mungkin, JIKA Anda tahu apa yang Anda lakukan. Ketika bekerja di lingkungan .NET, Anda sudah cukup jauh dari logam perangkat keras sehingga ada overhead yang signifikan dalam eksekusi kode. Jadi, mengingat bahwa Anda sudah berada di lingkungan yang menunjukkan Anda menyerah pada kecepatan terik dan bukannya mencari untuk menulis kode "benar", jika sesuatu dalam lingkungan .NET benar-benar tidak bekerja cukup cepat, baik kompleksitasnya adalah terlalu tinggi, atau Anda harus mempertimbangkan untuk memparalelkannya. Berikut adalah beberapa petunjuk dasar untuk diikuti untuk pengoptimalan; Saya jamin produktivitas Anda dalam optimasi (kecepatan yang diperoleh untuk waktu yang dihabiskan) akan meroket:

  • Mengubah bentuk fungsi lebih penting daripada mengubah koefisien - WRT Kompleksitas Oh, Anda dapat mengurangi jumlah langkah yang harus dijalankan dalam algoritma N 2 hingga setengahnya, dan Anda masih memiliki algoritme kompleksitas kuadrat meskipun dijalankan di separuh dari dulu. Jika itu adalah batas bawah kompleksitas untuk jenis masalah ini, maka jadilah itu, tetapi jika ada solusi NlogN, linear atau logaritmik untuk masalah yang sama, Anda akan mendapatkan lebih banyak dengan beralih algoritma untuk mengurangi kompleksitas daripada dengan mengoptimalkan yang Anda miliki.
  • Hanya karena Anda tidak dapat melihat kerumitannya bukan berarti itu tidak merugikan Anda - Banyak pelapis satu kata paling elegan dalam kata tersebut berperforma sangat buruk (misalnya, pemeriksa utama Regex adalah fungsi kompleksitas eksponensial, sementara efisien evaluasi utama yang melibatkan membagi angka dengan semua bilangan prima kurang dari akar kuadratnya ada di urutan O (Nlog (sqrt (N))) .Linq adalah perpustakaan yang hebat karena menyederhanakan kode, tetapi tidak seperti mesin SQL, .Net kompiler tidak akan mencoba menemukan cara yang paling efisien untuk mengeksekusi permintaan Anda. Anda harus tahu apa yang akan terjadi ketika Anda menggunakan metode, dan karenanya mengapa suatu metode mungkin lebih cepat jika ditempatkan lebih awal (atau lebih baru) dalam rantai, sambil memproduksi hasil yang sama.
  • OTOH, hampir selalu ada tradeoff antara kompleksitas sumber dan kompleksitas runtime - SelectionSort sangat mudah diimplementasikan; Anda mungkin bisa melakukannya dalam 10LOC atau kurang. MergeSort sedikit lebih kompleks, Quicksort lebih, dan RadixSort lebih. Tetapi, ketika algoritma meningkatkan kompleksitas pengkodean (dan dengan demikian waktu pengembangan "di muka"), mereka mengurangi kompleksitas runtime; MergeSort dan QuickSort adalah NlogN, dan RadixSort umumnya dianggap linier (secara teknis itu adalah NlogM di mana M adalah angka terbesar dalam N).
  • Buka puasa - Jika ada pemeriksaan yang dapat dilakukan dengan biaya murah yang kemungkinan besar benar dan berarti bahwa Anda dapat melanjutkan, lakukan pemeriksaan itu terlebih dahulu. Jika algoritme Anda, misalnya, hanya peduli dengan angka yang diakhiri dengan 1, 2, atau 3, kasus yang paling mungkin (diberikan data yang benar-benar acak) adalah angka yang diakhiri dengan angka lain, jadi uji bahwa angka tersebut TIDAK berakhir dengan 1, 2, atau 3, sebelum melakukan pengecekan untuk melihat apakah angka tersebut diakhiri dengan 1, 2, atau 3. Jika sepotong logika membutuhkan A&B, dan P (A) = 0,9 sementara P (B) = 0,1, maka periksa B pertama, kecuali jika! A lalu! B (suka if(myObject != null && myObject.someProperty == 1)), atau B membutuhkan lebih dari 9 kali lebih lama dari A untuk mengevaluasi ( if(myObject != null && some10SecondMethodReturningBool())).
  • Jangan ajukan pertanyaan yang sudah Anda ketahui jawabannya - Jika Anda memiliki serangkaian kondisi "gagal", dan satu atau lebih dari kondisi tersebut bergantung pada kondisi yang lebih sederhana yang juga harus diperiksa, jangan pernah memeriksa kedua ini secara mandiri. Misalnya, jika Anda memiliki cek yang memerlukan A, dan cek yang membutuhkan A&& B, Anda harus memeriksa A, dan jika benar Anda harus memeriksa B. Jika! A, lalu! A && B, jadi jangan repot-repot.
  • Semakin sering Anda melakukan sesuatu, semakin Anda harus memperhatikan bagaimana hal itu dilakukan - Ini adalah tema umum dalam pengembangan, di banyak tingkatan; dalam pengertian pengembangan umum, "jika tugas umum memakan waktu atau fiddly, terus lakukan sampai Anda berdua frustrasi dan cukup berpengetahuan untuk menghasilkan cara yang lebih baik". Dalam istilah kode, semakin sering algoritma tidak efisien dijalankan, semakin Anda akan mendapatkan keseluruhan kinerja dengan mengoptimalkannya. Ada alat profil yang dapat mengambil rakitan biner dan simbol debug dan menunjukkan kepada Anda, setelah menjalankan beberapa kasus penggunaan, baris kode apa yang paling banyak dijalankan. Garis-garis itu, dan garis-garis yang menjalankan garis-garis itu, adalah apa yang harus Anda perhatikan, karena setiap peningkatan efisiensi yang Anda capai di sana akan berlipat ganda.
  • Algoritma yang lebih kompleks terlihat seperti algoritme yang tidak terlalu rumit jika Anda melemparkan cukup banyak perangkat keras padanya . Ada beberapa saat di mana Anda hanya perlu menyadari bahwa algoritma Anda mendekati batas teknis sistem (atau bagian dari itu) yang Anda jalankan; sejak saat itu jika perlu lebih cepat, Anda akan mendapatkan lebih banyak dengan hanya menjalankannya pada perangkat keras yang lebih baik. Ini juga berlaku untuk paralelisasi; N 2 algoritma -complexity, ketika berjalan di N core, penampilan linier. Jadi, jika Anda yakin telah mencapai batas kompleksitas yang lebih rendah untuk jenis algoritma yang Anda tulis, cari cara untuk "membagi dan menaklukkan".
  • Ini cepat ketika cukup cepat - Kecuali jika Anda mengemas tangan untuk menargetkan chip tertentu, selalu ada sesuatu yang bisa diperoleh. Namun, kecuali jika Anda INGIN perakitan tangan-packing, Anda harus selalu ingat apa yang klien sebut "cukup baik". Sekali lagi, "optimasi prematur adalah akar dari semua kejahatan"; ketika klien Anda menyebutnya cukup cepat, Anda selesai sampai dia tidak berpikir itu cukup cepat lagi.
KeithS
sumber
0

Satu-satunya waktu untuk khawatir tentang pengoptimalan sejak dini adalah ketika Anda tahu Anda berurusan dengan sesuatu yang sangat besar atau sesuatu yang Anda tahu akan mengeksekusi sejumlah besar kali.

Definisi "besar" jelas bervariasi berdasarkan seperti apa sistem target Anda.

Loren Pechtel
sumber
0

Saya lebih suka versi dua baris hanya karena lebih mudah untuk melangkah dengan debugger. Garis dengan beberapa panggilan tertanam membuatnya lebih sulit.

Tamparan
sumber