Apakah kekekalan merusak kinerja dalam JavaScript?

88

Tampaknya ada tren terbaru dalam JavaScript untuk memperlakukan struktur data sebagai tidak berubah. Misalnya, jika Anda perlu mengubah properti tunggal objek, lebih baik buat saja objek baru dengan properti baru, dan cukup salin semua properti lainnya dari objek lama, dan biarkan objek lama menjadi sampah yang dikumpulkan. (Lagipula itu pemahaman saya.)

Reaksi awal saya adalah, sepertinya itu akan berdampak buruk untuk kinerja.

Tapi kemudian perpustakaan seperti Immutable.js dan Redux.js ditulis oleh orang-orang pintar dari saya, dan tampaknya memiliki kepedulian yang kuat untuk kinerja, sehingga membuat saya bertanya-tanya apakah pemahaman saya tentang sampah (dan dampaknya kinerjanya) adalah salah.

Apakah ada manfaat kinerja untuk kekekalan yang saya lewatkan, dan apakah itu lebih besar daripada kerugian dari menciptakan begitu banyak sampah?

callum
sumber
8
Mereka memiliki kepedulian yang kuat untuk kinerja sebagian karena kekekalan (kadang-kadang) memiliki biaya kinerja, dan mereka ingin meminimalkan biaya kinerja sebanyak mungkin. Kekekalan, dengan sendirinya, hanya memiliki manfaat kinerja dalam arti membuatnya lebih mudah untuk menulis kode multi-utas.
Robert Harvey
8
Dalam pengalaman saya, kinerja hanya masalah yang valid untuk dua skenario - satu, ketika tindakan dilakukan 30+ kali dalam satu detik, dan dua - ketika efeknya meningkat dengan setiap eksekusi (Windows XP pernah menemukan bug di mana waktu Pembaruan Windows mengambil O(pow(n, 2))untuk setiap pembaruan dalam sejarahnya .) Sebagian besar kode lainnya adalah respons langsung terhadap suatu peristiwa; klik, permintaan API, atau yang serupa, dan selama waktu eksekusi konstan, pembersihan sejumlah objek tidak menjadi masalah.
Katana314
4
Juga, pertimbangkan bahwa ada implementasi efisien dari struktur data yang tidak dapat diubah. Mungkin ini tidak seefisien yang bisa berubah, tetapi mungkin masih lebih efisien daripada implementasi yang naif. Lihat misalnya Struktur Data Murni Fungsional oleh Chris Okasaki
Giorgio
1
@ Katana314: 30+ kali bagi saya masih tidak cukup untuk membenarkan kekhawatiran tentang kinerja. Saya porting sebuah emulator CPU kecil yang saya tulis ke node.js dan node mengeksekusi CPU virtual sekitar 20MHz (itu 20 juta kali per detik). Jadi saya hanya akan khawatir tentang kinerja jika saya melakukan sesuatu 1000+ kali per detik (bahkan kemudian, saya tidak akan benar-benar khawatir sampai saya melakukan operasi 1000000 per detik karena saya tahu saya dapat dengan nyaman melakukan lebih dari 10 dari mereka sekaligus) .
slebetman
2
@RobertHarvey "Tidak dapat diubah, dengan sendirinya, hanya memiliki manfaat kinerja dalam arti membuatnya lebih mudah untuk menulis kode multi-utas." Itu tidak sepenuhnya benar, ketidakmampuan memungkinkan untuk berbagi yang sangat luas tanpa konsekuensi nyata. Yang sangat tidak aman di lingkungan yang bisa berubah. Ini memberi Anda berpikir seperti O(1)array mengiris dan O(log n)memasukkan ke pohon biner sementara masih bisa menggunakan yang lama secara bebas, dan contoh lain adalah tailsyang mengambil semua ekor daftar tails [1, 2] = [[1, 2], [2], []]hanya membutuhkan O(n)waktu dan ruang, tetapi O(n^2)dalam hitungan elemen
titik koma

Jawaban:

59

Misalnya, jika Anda perlu mengubah properti tunggal objek, lebih baik buat saja objek baru dengan properti baru, dan cukup salin semua properti lainnya dari objek lama, dan biarkan objek lama menjadi sampah yang dikumpulkan.

Tanpa kekekalan, Anda mungkin harus melewati objek di antara berbagai cakupan, dan Anda tidak tahu sebelumnya apakah dan kapan objek akan diubah. Jadi untuk menghindari efek samping yang tidak diinginkan, Anda mulai membuat salinan lengkap objek "berjaga-jaga" dan meneruskan salinan itu, bahkan jika ternyata tidak ada properti yang harus diubah sama sekali. Itu akan meninggalkan lebih banyak sampah daripada dalam kasus Anda.

Apa yang diperlihatkan ini adalah - jika Anda membuat skenario hipotetis yang tepat, Anda dapat membuktikan apa pun, terutama ketika menyangkut kinerja. Contoh saya, bagaimanapun, tidak begitu hipotesis seperti kedengarannya. Saya bekerja bulan lalu di sebuah program di mana kami menemukan masalah itu karena kami awalnya memutuskan untuk tidak menggunakan struktur data yang tidak dapat diubah, dan ragu-ragu untuk memperbaiki ini nanti karena sepertinya tidak ada gunanya.

Jadi, ketika Anda melihat kasus-kasus seperti ini dari posting SO yang lebih lama , jawaban atas pertanyaan Anda mungkin jelas - tergantung . Untuk beberapa kasus ketidakmampuan akan melukai kinerja, untuk beberapa hal sebaliknya mungkin benar, untuk banyak kasus itu akan tergantung pada seberapa pintar implementasi Anda, dan untuk lebih banyak kasus perbedaannya akan diabaikan.

Catatan terakhir: masalah dunia nyata yang mungkin Anda temui adalah Anda harus memutuskan lebih awal untuk atau menentang ketidakmampuan untuk beberapa struktur data dasar. Kemudian Anda membangun banyak kode di atasnya, dan beberapa minggu atau bulan kemudian Anda akan melihat apakah keputusan itu baik atau buruk.

Aturan praktis saya untuk situasi ini adalah:

  • Jika Anda mendesain struktur data dengan hanya beberapa atribut berdasarkan tipe primitif atau tipe tidak tetap lainnya, cobalah immutability terlebih dahulu.
  • Jika Anda ingin mendesain tipe data di mana array dengan ukuran besar (atau tidak ditentukan), akses acak dan mengubah konten yang terlibat, gunakan mutabilitas.

Untuk situasi di antara kedua ekstrem ini, gunakan penilaian Anda. Tapi YMMV.

Doc Brown
sumber
8
That will leave a lot more garbage than in your case.dan untuk membuat keadaan menjadi lebih buruk, runtime Anda mungkin tidak akan dapat mendeteksi duplikasi yang tidak ada gunanya, dan karenanya (tidak seperti objek abadi yang tidak digunakan oleh siapa pun), ia bahkan tidak akan memenuhi syarat untuk dikoleksi.
Jacob Raihle
37

Pertama-tama, karakterisasi struktur data Anda yang tidak dapat diubah tidak tepat. Secara umum, sebagian besar struktur data tidak disalin, tetapi dibagikan , dan hanya bagian yang diubah disalin. Ini disebut struktur data persisten . Sebagian besar implementasi dapat mengambil keuntungan dari struktur data yang persisten sebagian besar waktu. Kinerja cukup dekat dengan struktur data yang bisa berubah yang umumnya programmer fungsional menganggapnya dapat diabaikan.

Kedua, saya menemukan banyak orang memiliki ide yang cukup tidak akurat tentang umur objek yang khas dalam program imperatif yang khas. Mungkin ini karena popularitas bahasa yang dikelola memori. Duduk dan lihatlah berapa banyak objek sementara dan salinan defensif yang Anda buat dibandingkan dengan struktur data yang benar-benar berumur panjang. Saya pikir Anda akan terkejut dengan rasionya.

Saya memiliki orang-orang berkomentar di kelas pemrograman fungsional yang saya ajarkan tentang berapa banyak sampah yang diciptakan oleh suatu algoritma, kemudian saya menunjukkan versi imperatif khas dari algoritma yang sama yang hanya menghasilkan sebanyak itu. Hanya karena alasan tertentu orang tidak memperhatikannya lagi.

Dengan mendorong berbagi dan mengecilkan membuat variabel sampai Anda memiliki nilai yang valid untuk dimasukkan ke dalamnya, ketidakmampuan cenderung mendorong praktik pengkodean yang lebih bersih dan struktur data yang lebih lama. Ini sering mengarah pada tingkat sampah yang sebanding atau tidak lebih rendah, tergantung pada algoritma Anda.

Karl Bielefeldt
sumber
8
"... lalu aku menunjukkan versi imperatif tipikal dari algoritma yang sama yang menciptakan sebanyak itu." Ini. Juga, orang yang baru dengan gaya ini, dan terutama jika mereka baru dengan gaya fungsional secara umum, pada awalnya dapat menghasilkan implementasi fungsional yang kurang optimal.
wberry
1
"discouraging create variabel" Bukankah itu hanya berlaku untuk bahasa di mana perilaku default disalin pada tugas / konstruksi implisit? Dalam JavaScript, variabel hanyalah pengidentifikasi; itu bukan objek dalam dirinya sendiri. Masih menempati ruang di suatu tempat, tapi itu dapat diabaikan (terutama karena sebagian besar implementasi JavaScript, setahu saya, masih menggunakan stack untuk pemanggilan fungsi, artinya kecuali jika Anda memiliki banyak rekursi Anda hanya akan menggunakan kembali ruang stack yang sama untuk sebagian besar variabel sementara). Kekekalan tidak ada hubungannya dengan aspek itu.
JAB
33

Terlambat datang ke Q&A ini dengan jawaban yang sudah bagus, tapi saya ingin mengganggu karena orang asing terbiasa melihat hal-hal dari sudut pandang bit dan byte tingkat bawah dalam memori.

Saya sangat senang dengan desain yang tidak berubah, bahkan datang dari perspektif C, dan dari perspektif menemukan cara baru untuk secara efektif memprogram perangkat keras yang sangat buruk yang kita miliki saat ini.

Lebih lambat / lebih cepat

Mengenai pertanyaan apakah hal itu membuat segalanya lebih lambat, jawaban robot akan yes. Pada tingkat konseptual yang sangat teknis seperti ini, kekekalan hanya bisa membuat segalanya lebih lambat. Perangkat keras berfungsi paling baik ketika tidak mengalokasikan memori secara sporadis dan hanya dapat memodifikasi memori yang ada (mengapa kita memiliki konsep seperti temporal locality).

Namun jawaban praktisnya adalah maybe. Kinerja sebagian besar masih merupakan metrik produktivitas dalam basis kode non-sepele. Kami biasanya tidak menemukan basis kode mengerikan untuk mempertahankan kondisi ras sebagai yang paling efisien, bahkan jika kami mengabaikan bug. Efisiensi sering kali merupakan fungsi keanggunan dan kesederhanaan. Puncak optimasi mikro agak dapat bertentangan, tetapi biasanya disediakan untuk bagian kode terkecil dan paling kritis.

Mengubah Bit dan Byte yang Tidak Berubah

Berasal dari sudut pandang tingkat rendah, jika kita menggunakan konsep x-ray seperti objectsdan stringsdan sebagainya, intinya hanyalah bit dan byte dalam berbagai bentuk memori dengan berbagai karakteristik kecepatan / ukuran (kecepatan dan ukuran perangkat keras memori biasanya menjadi saling eksklusif).

masukkan deskripsi gambar di sini

Hirarki memori komputer menyukainya ketika kita berulang kali mengakses potongan memori yang sama, seperti pada diagram di atas, karena ia akan menjaga potongan memori yang sering diakses itu dalam bentuk memori tercepat (L1 cache, misalnya, yang hampir secepat register). Kami mungkin berulang kali mengakses memori yang sama persis (menggunakannya kembali beberapa kali) atau berulang kali mengakses bagian chunk yang berbeda (misal: perulangan elemen-elemen dalam chunk yang berdekatan yang berulang kali mengakses berbagai bagian dari chunk of memory itu).

Kami akhirnya melemparkan kunci pas dalam proses itu jika memodifikasi memori ini akhirnya ingin membuat blok memori yang sama sekali baru di samping, seperti:

masukkan deskripsi gambar di sini

... dalam hal ini, mengakses blok memori baru bisa memerlukan kesalahan halaman wajib dan kesalahan cache untuk memindahkannya kembali ke bentuk memori tercepat (sampai pada register). Itu bisa menjadi pembunuh kinerja nyata.

Ada beberapa cara untuk mengurangi ini, bagaimanapun, dengan menggunakan cadangan memori yang sudah dialokasikan sebelumnya, sudah tersentuh.

Agregat Besar

Masalah konseptual lain yang muncul dari pandangan tingkat yang sedikit lebih tinggi adalah hanya melakukan salinan agregat yang sangat besar dalam jumlah besar yang tidak perlu.

Untuk menghindari diagram yang terlalu rumit, mari kita bayangkan blok memori sederhana ini entah bagaimana mahal (mungkin UTF-32 karakter pada perangkat keras yang luar biasa terbatas).

masukkan deskripsi gambar di sini

Dalam hal ini, jika kita ingin mengganti "BANTUAN" dengan "KILL" dan blok memori ini tidak dapat diubah, kita harus membuat blok baru secara keseluruhan untuk membuat objek baru yang unik, meskipun hanya sebagian saja yang telah berubah :

masukkan deskripsi gambar di sini

Membentangkan imajinasi kita sedikit, salinan mendalam dari segala hal lain hanya untuk membuat satu bagian kecil menjadi unik mungkin cukup mahal (dalam kasus dunia nyata, blok memori ini akan jauh, jauh lebih besar untuk menimbulkan masalah).

Namun, terlepas dari biaya seperti itu, desain semacam ini akan cenderung jauh lebih rentan terhadap kesalahan manusia. Siapa pun yang telah bekerja dalam bahasa fungsional dengan fungsi murni mungkin dapat menghargai ini, dan terutama dalam kasus multithread di mana kita dapat melakukan multithread kode seperti itu tanpa peduli di dunia. Secara umum, programmer manusia cenderung tersandung perubahan negara, terutama yang menyebabkan efek samping eksternal untuk menyatakan di luar lingkup fungsi saat ini. Bahkan memulihkan dari kesalahan eksternal (pengecualian) dalam kasus seperti itu bisa sangat sulit dengan perubahan keadaan eksternal yang bisa berubah dalam campuran.

Salah satu cara untuk mengurangi pekerjaan penyalinan yang berlebihan ini adalah dengan membuat blok memori ini menjadi kumpulan pointer (atau referensi) ke karakter, seperti:

Maaf, saya gagal menyadari kita tidak perlu membuat Lunik saat membuat diagram.

Biru menunjukkan data yang disalin dangkal.

masukkan deskripsi gambar di sini

... sayangnya, ini akan menjadi sangat mahal untuk membayar biaya penunjuk / referensi per karakter. Selain itu, kami dapat menyebarkan konten karakter di seluruh ruang alamat dan pada akhirnya membayarnya dalam bentuk kapal yang berisi kesalahan halaman dan cache yang hilang, dengan mudah membuat solusi ini lebih buruk daripada menyalin seluruh hal secara keseluruhan.

Bahkan jika kita berhati-hati untuk mengalokasikan karakter ini secara bersamaan, katakanlah mesin dapat memuat 8 karakter dan 8 pointer ke karakter ke dalam garis cache. Kami akhirnya memuat memori seperti ini untuk melintasi string baru:

masukkan deskripsi gambar di sini

Dalam hal ini, kita pada akhirnya membutuhkan 7 baris cache berbeda dari memori yang berdekatan untuk dimuat untuk melintasi string ini, ketika idealnya kita hanya membutuhkan 3.

Memotong Data

Untuk mengurangi masalah di atas, kita dapat menerapkan strategi dasar yang sama tetapi pada tingkat 8 karakter yang lebih kasar, misalnya

masukkan deskripsi gambar di sini

Hasilnya membutuhkan 4 baris cache senilai data (1 untuk 3 pointer, dan 3 untuk karakter) untuk dimuat untuk melintasi string ini yang hanya 1 pendek dari optimum teoritis.

Jadi itu tidak buruk sama sekali. Ada beberapa pemborosan memori tetapi memori berlimpah dan menggunakan lebih banyak tidak memperlambat segalanya jika memori tambahan hanya akan menjadi data dingin tidak sering diakses. Ini hanya untuk data yang panas dan berdekatan di mana penggunaan dan kecepatan memori yang berkurang sering berjalan seiring di mana kami ingin memasukkan lebih banyak memori ke dalam satu halaman atau garis cache dan mengakses semuanya sebelum penggusuran. Representasi ini cukup ramah terhadap cache.

Kecepatan

Jadi menggunakan representasi seperti di atas dapat memberikan keseimbangan kinerja yang cukup baik. Mungkin penggunaan struktur data abadi yang paling kritis terhadap kinerja akan mengubah sifat potongan data yang tebal dan membuatnya unik dalam prosesnya, sementara menyalin bagian yang tidak dimodifikasi dengan dangkal. Ini juga menyiratkan beberapa overhead dari operasi atom untuk referensi potongan dangkal disalin dengan aman dalam konteks multithreaded (mungkin dengan beberapa penghitungan referensi atom terjadi).

Namun selama data yang tebal ini diwakili pada tingkat yang cukup kasar, banyak overhead ini berkurang dan bahkan mungkin diremehkan, sementara masih memberi kita keamanan dan kemudahan pengkodean dan multithreading lebih banyak fungsi dalam bentuk murni tanpa sisi eksternal efek.

Menyimpan Data Baru dan Lama

Di mana saya melihat kekekalan sebagai berpotensi paling membantu dari sudut pandang kinerja (dalam arti praktis) adalah ketika kita dapat tergoda untuk membuat seluruh salinan data besar untuk menjadikannya unik dalam konteks yang bisa berubah di mana tujuannya adalah untuk menghasilkan sesuatu yang baru dari sesuatu yang sudah ada dengan cara di mana kita ingin tetap baru dan lama, ketika kita bisa membuat potongan-potongan kecil itu unik dengan desain abadi yang hati-hati.

Contoh: Undo System

Contohnya adalah sistem undo. Kami mungkin mengubah sebagian kecil dari struktur data dan ingin mempertahankan bentuk asli yang dapat kami batalkan, dan bentuk yang baru. Dengan jenis desain permanen yang hanya membuat bagian struktur data yang kecil dan dimodifikasi menjadi unik, kami dapat menyimpan salinan data lama dalam entri undo dan hanya membayar biaya memori dari data bagian unik yang ditambahkan. Ini memberikan keseimbangan produktivitas yang sangat efektif (menjadikan penerapan sistem undo menjadi bagian dari kue) dan kinerja.

Antarmuka Tingkat Tinggi

Namun sesuatu yang aneh muncul dengan kasus di atas. Dalam konteks fungsi lokal, data yang bisa berubah-ubah sering kali paling mudah dan paling mudah untuk dimodifikasi. Lagi pula, cara termudah untuk memodifikasi array adalah dengan hanya memutarnya dan memodifikasi satu elemen pada satu waktu. Kita dapat akhirnya meningkatkan overhead intelektual jika kita memiliki sejumlah besar algoritma tingkat tinggi untuk memilih untuk mengubah array dan harus memilih yang sesuai untuk memastikan bahwa semua salinan dangkal tebal ini dibuat sementara bagian-bagian yang dimodifikasi adalah dibuat unik.

Mungkin cara termudah dalam kasus tersebut adalah dengan menggunakan buffer yang bisa berubah secara lokal di dalam konteks fungsi (di mana mereka biasanya tidak membuat kita tersandung) yang melakukan perubahan secara atomis ke struktur data untuk mendapatkan salinan yang tidak dapat diubah baru (saya percaya beberapa bahasa memanggil "transien" ini) ...

... atau kita mungkin hanya memodelkan fungsi transformasi tingkat yang lebih tinggi dan lebih tinggi di atas data sehingga kita dapat menyembunyikan proses memodifikasi buffer yang bisa berubah dan memasukkannya ke struktur tanpa melibatkan logika yang bisa diubah. Bagaimanapun, ini belum merupakan wilayah yang dieksplorasi secara luas, dan kami memiliki pekerjaan kami memotong jika kita merangkul desain yang lebih abadi untuk menghasilkan antarmuka yang bermakna untuk bagaimana mengubah struktur data ini.

Struktur data

Hal lain yang muncul di sini adalah bahwa ketidakmampuan yang digunakan dalam konteks kinerja-kritis mungkin akan ingin struktur data dipecah menjadi data yang tebal di mana potongan tidak terlalu kecil tetapi juga tidak terlalu besar.

Daftar tertaut mungkin ingin mengubah sedikit untuk mengakomodasi ini dan berubah menjadi daftar yang belum dibuka. Besar, array berdekatan mungkin berubah menjadi array pointer menjadi potongan berdekatan dengan pengindeksan modulo untuk akses acak.

Ini berpotensi mengubah cara kita melihat struktur data dengan cara yang menarik, sambil mendorong fungsi modifikasi dari struktur data ini agar menyerupai sifat yang lebih besar untuk menyembunyikan kompleksitas ekstra dalam menyalin beberapa bit di sini dan membuat bit lainnya unik di sana.

Performa

Ngomong-ngomong, ini adalah pandangan kecil saya yang lebih rendah tentang topik itu. Secara teoritis, ketidakmampuan dapat memiliki biaya mulai dari yang sangat besar hingga yang lebih kecil. Tetapi pendekatan yang sangat teoritis tidak selalu membuat aplikasi berjalan cepat. Mungkin membuat mereka terukur, tetapi kecepatan dunia nyata sering membutuhkan merangkul pola pikir yang lebih praktis.

Dari perspektif praktis, kualitas seperti kinerja, pemeliharaan dan keamanan cenderung berubah menjadi satu kekaburan besar, terutama untuk basis kode yang sangat besar. Sementara kinerja dalam arti absolut terdegradasi dengan kekekalan, sulit untuk memperdebatkan manfaatnya terhadap produktivitas dan keselamatan (termasuk keselamatan benang). Dengan peningkatan ini sering kali terjadi peningkatan kinerja praktis, jika hanya karena pengembang memiliki lebih banyak waktu untuk menyempurnakan dan mengoptimalkan kode mereka tanpa dikerumuni oleh bug.

Jadi saya pikir dari pengertian praktis ini, struktur data yang tidak berubah sebenarnya dapat membantu kinerja dalam banyak kasus, seaneh kedengarannya. Dunia yang ideal mungkin mencari campuran dari keduanya: struktur data yang tidak berubah dan yang bisa berubah, dengan yang bisa berubah biasanya sangat aman untuk digunakan dalam lingkup yang sangat lokal (mis: lokal ke suatu fungsi), sedangkan yang tidak berubah dapat menghindari sisi eksternal efek langsung dan mengubah semua perubahan pada struktur data menjadi operasi atom menghasilkan versi baru tanpa risiko kondisi ras.

ChrisF
sumber
11

ImmutableJS sebenarnya cukup efisien. Jika kita ambil contoh:

var x = {
    Foo: 1,
    Bar: { Baz: 2 }
    Qux: { AnotherVal: 3 }
}

Jika objek di atas dibuat tidak berubah maka Anda mengubah nilai properti 'Baz' yang akan Anda dapatkan adalah:

var y = x.setIn('/Bar/Baz', 3);
y !== x; // Different object instance
y.Bar !== x.Bar // As the Baz property was changed, the Bar object is a diff instance
y.Qux === y.Qux // Qux is the same object instance

Ini menciptakan beberapa peningkatan kinerja yang sangat keren untuk model objek dalam, di mana Anda hanya perlu menyalin tipe nilai pada objek di jalur ke root. Semakin besar model objek dan semakin kecil perubahan yang Anda buat, semakin baik kinerja memori dan CPU dari struktur data yang tidak dapat diubah karena pada akhirnya berbagi banyak objek.

Seperti jawaban lain katakan, jika Anda membandingkan ini dengan mencoba memberikan jaminan yang sama dengan menyalin secara defensif xsebelum meneruskannya ke fungsi yang dapat memanipulasinya, maka kinerjanya jauh lebih baik.

Bringer128
sumber
4

Dalam garis lurus, kode tidak berubah memiliki overhead penciptaan objek, yang lebih lambat. Namun, ada banyak situasi di mana kode yang bisa berubah menjadi sangat sulit untuk dikelola secara efisien (menghasilkan banyak penyalinan defensif, yang juga mahal), dan ada banyak strategi pintar untuk mengurangi biaya 'menyalin' suatu objek , sebagaimana disebutkan oleh orang lain.

Jika Anda memiliki objek seperti penghitung, dan semakin bertambah beberapa kali dalam satu detik, memiliki penghitung yang tidak dapat diubah mungkin tidak sebanding dengan penalti kinerja. Jika Anda memiliki objek yang sedang dibaca oleh berbagai bagian aplikasi Anda, dan masing-masing dari mereka ingin memiliki kloning objek yang sedikit berbeda, Anda akan memiliki waktu yang jauh lebih mudah mengaturnya dengan cara yang baik dengan menggunakan yang baik. implementasi objek abadi.

Anjing
sumber
4

Untuk menambahkan pertanyaan ini (sudah dijawab dengan sangat baik):

Jawaban singkatnya adalah ya ; itu akan merusak kinerja karena Anda hanya pernah membuat objek alih-alih memutasikan yang sudah ada, menghasilkan lebih banyak overhead penciptaan objek.


Namun, jawaban panjangnya tidak juga .

Dari sudut pandang runtime aktual, dalam JavaScript Anda sudah membuat cukup banyak objek runtime - fungsi dan objek literal ada di mana-mana dalam JavaScript dan tidak ada yang berpikir dua kali untuk menggunakannya. Saya berpendapat bahwa pembuatan objek sebenarnya cukup murah, meskipun saya tidak memiliki kutipan untuk ini sehingga saya tidak akan menggunakannya sebagai argumen yang berdiri sendiri.

Bagi saya, peningkatan 'kinerja' terbesar bukan pada kinerja runtime tetapi pada kinerja pengembang . Salah satu hal pertama yang saya pelajari ketika bekerja pada aplikasi Dunia Nyata (tm) adalah bahwa sifat berubah-ubah sangat berbahaya dan membingungkan. Saya telah kehilangan banyak waktu mengejar utas (bukan tipe konkurensi) dari eksekusi mencoba untuk mencari tahu apa yang menyebabkan bug tidak jelas ketika ternyata merupakan mutasi dari sisi lain aplikasi sialan!

Menggunakan immutability membuat banyak hal lebih mudah untuk dipikirkan. Anda dapat segera mengetahui bahwa objek X tidak akan berubah selama masa pakainya, dan satu-satunya cara untuk mengubahnya adalah mengkloningnya. Saya menghargai ini lebih banyak (terutama di lingkungan tim) daripada optimasi mikro apa pun yang mungkin membawa perubahan.

Ada pengecualian, terutama struktur data seperti disebutkan di atas. Saya jarang menemukan skenario di mana saya ingin mengubah peta setelah pembuatan (meskipun diakui saya berbicara tentang peta pseudo-objek-literal daripada peta ES6), sama untuk array. Saat Anda berhadapan dengan struktur data yang lebih besar, kemampuan berubah mungkin memberikan hasil. Ingatlah bahwa setiap objek dalam JavaScript diberikan sebagai referensi alih-alih nilai.


Yang mengatakan, satu poin yang dikemukakan di atas adalah GC dan ketidakmampuannya untuk mendeteksi duplikasi. Ini adalah kekhawatiran yang sah, tetapi menurut saya itu hanya masalah ketika memori menjadi masalah, dan ada cara yang jauh lebih mudah untuk mengkodekan diri Anda menjadi sudut - misalnya, referensi melingkar pada penutup.


Pada akhirnya, saya lebih suka memiliki basis kode yang tidak dapat diubah dengan sangat sedikit (jika ada) bagian yang bisa berubah dan sedikit lebih berkinerja daripada memiliki mutabilitas di mana pun. Anda selalu dapat mengoptimalkan nanti jika ketidakmampuan, untuk beberapa alasan, menjadi perhatian untuk kinerja.

Dan Pantry
sumber