Peningkatan Performa LINQ di .NET 9.0
(blog.ndepend.com)-.NET 9.0 secara signifikan mengurangi waktu eksekusi dibanding .NET 8 dalam berbagai skenario LINQ umum, dan pada beberapa benchmark bahkan menghilangkan alokasi
- Saat mengiterasi array atau
List<T>, salah satu peningkatan utamanya adalah memperolehReadOnlySpan<T>melaluiTryGetSpan()untuk menurunkan biaya iterasi TryGetSpan()mengidentifikasiTSource[]danList<TSource>lewat perbandingan tipe, tetapi cara mengambil span dari array internalList<T>adalah optimasi keluarga Unsafe yang bisa menjadi tidak valid saat kapasitas berubah -.LINQ di .NET 9 mengenali rantai pemanggilan umum untuk membuat iterator khusus, dan menerapkan optimasi tambahan pada metode terminal sepertiCount(),First(),Last(),ElementAt(),Sum()- Hanya dengan migrasi sederhana dan kompilasi ulang, sebagian peningkatan performa LINQ bisa diperoleh; optimasi seperti pemanfaatan SIMD dan deteksi dini sekuens kosong juga disertakan
Alasan iterasi array dan list menjadi lebih cepat
- Benchmark pertama menyimpan
Enumerable.Range(1, 10_000).ToArray()sebagaiIEnumerable<int>, lalu menjalankanCount,All,Any,First,Single,Lastuntuk membandingkan .NET 8 dan .NET 9 - Benchmark ini menggunakan BenchmarkDotNet, dan proyek harus menargetkan
net8.0;net9.0serta dikompilasi dalam mode Release - Di .NET 9, waktu eksekusi banyak metode turun drastis dan alokasi juga hilang
LinqCount: dari 16,198.490 ns menjadi 3,043.563 ns, dari alokasi 32 B menjadi tanpa alokasiLinqAny: dari 17,096.735 ns menjadi 2,483.927 ns, dari alokasi 32 B menjadi tanpa alokasiLinqFirst: dari 15,289.747 ns menjadi 2,243.341 ns, dari alokasi 32 B menjadi tanpa alokasiLinqSingle: dari 21,684.114 ns menjadi 4,884.329 ns, dari alokasi 32 B menjadi tanpa alokasiLinqAll: dari 10.588 ns menjadi 2.562 ns, dari alokasi 32 B menjadi tanpa alokasiLinqLast: dari 15.967 ns menjadi 6.918 ns
Perbedaan yang dibuat oleh TryGetSpan()
- Penyebab utama peningkatan performa adalah penggunaan
TryGetSpan() - Jika enumerable yang diiterasi adalah array atau list,
TryGetSpan()mengembalikanReadOnlySpan<T>, sehingga iterasi bisa lebih cepat - Kode percabangan inti memeriksa
source.GetType() == typeof(TSource[])atausource.GetType() == typeof(List<TSource>), lalu memperoleh span- Array ditangani dengan
Unsafe.As<TSource[]>(source) - List memperoleh span dari array internal melalui
CollectionsMarshal.AsSpan(Unsafe.As<List<TSource>>(source))
- Array ditangani dengan
- Dalam kode,
source.GetType()dipanggil dua kali dan tidak ditangani dengan pemeriksaan null setelah casting, tetapi ini adalah pendekatan yang dipilih para pakar performa .NET dengan mempertimbangkan optimasi compiler C# dan JIT - Dalam stack .NET yang sangat dioptimalkan, optimasi mikro bisa menghasilkan efek yang berbeda dari tampilan sekilas
Batasan CollectionsMarshal.AsSpan()
List<TSource>secara internal merujuk ke sebuah array, dan saat kapasitas list harus bertambah atau berkurang, array baru dibuat lalu dirujukCollectionsMarshal.AsSpan(Unsafe.As<List<TSource>>(source))memperolehSpan<TSource>dari array internal ini- Jika kapasitas list berubah dengan cara apa pun, array yang diperoleh lewat cara ini bisa menjadi tidak valid
- Karena batasan ini, sebagian operasi
Enumerableyang melibatkan iterasi tertunda sepertiyieldsulit bergantung pada optimasi ini - Nama
System.Runtime.CompilerServices.Unsafesendiri menunjukkan risiko semacam ini
Cakupan pemanggilan TryGetSpan()
- Dengan memindai
System.Linq.dllmenggunakan NDepend, pemanggil langsung dan tidak langsung dariTryGetSpan()diperiksa - Path assembly yang dianalisis adalah
C:\Program Files\dotnet\shared\Microsoft.NETCore.App\9.0.0-rc.1.24431.7\System.Linq.dll - Dari
TryGetSpan(), code query dibuat untuk mengidentifikasi pemanggil, dan 56 metode yang cocok diekspor sebagai graf dependensi - Banyak metode standar
Enumerablemencoba iterasi span saat koleksinya berupa array atau list - Namun karena cara menahan array internal list tidak aman, masih ada batasan untuk operasi yang memerlukan eksekusi tertunda
Optimasi berbasis iterator khusus
- Benchmark kedua menggunakan contoh dari PR Consolidate LINQ’s internal IIListProvider/IPartition into base Iterator class
- Target pengujian mencakup
Distinct().First(),Append().Select().Last(),Reverse().Count(),DefaultIfEmpty().Select().ElementAt(),Skip().Take().ElementAt(),Union().First(),Select().Where().Select().Sum(), dan lainnya - Di .NET 9, sebagian rantai pemanggilan menjadi sangat jauh lebih cepat
DistinctFirst: dari 65.318 ns menjadi 11.192 ns, dari alokasi 328 B menjadi tanpa alokasiAppendSelectLast: dari 4,122.007 ns menjadi 2.661 ns, dari alokasi 144 B menjadi tanpa alokasiDefaultIfEmptySelectElementAt: dari 4,090.818 ns menjadi 5.724 ns, dari alokasi 144 B menjadi tanpa alokasiRangeUnionFirst: dari 66.309 ns menjadi 6.193 ns, dari alokasi 344 B menjadi tanpa alokasiListSkipTakeElementAt: dari 6.268 ns menjadi 2.916 nsRangeReverseCount: dari 11.024 ns menjadi 6.134 ns
- Sebaliknya,
SelectWhereSelectSummelambat dari 3,959.622 ns di .NET 8 menjadi 4,460.008 ns di .NET 9, dan alokasi 112 B juga tetap ada
Pengenalan rantai LINQ umum
- Tim performa .NET merancang kode agar mengenali rantai pemanggilan LINQ yang umum
- Saat rantai tertentu terdeteksi, iterator khusus yang menangani alur kerja dengan lebih efisien dibuat
- Jika rantai berakhir dengan metode seperti
Count(),First(),Last(),ElementAt(),Sum(), optimasi tambahan dapat dilakukan - Sebagai contoh,
OrderBy(criteria).First()dapat dioptimalkan agar dieksekusi sepertiMin(criteria)
Struktur Iterator<T> dan kelas turunannya
- Di internal LINQ terdapat kelas dasar abstrak
Iterator<T>dan 40 kelas turunan - Semua kelas ini bersarang di dalam kelas
Enumerable Iterator<T>adalah kelas abstrak, tetapi metodenya bersifat virtual, sehingga kelas turunan hanya meng-override metode yang diperlukan- Struktur ini menjadi dasar untuk menampung perilaku khusus per rantai pemanggilan
Contoh ListWhereSelectIterator<TSource, TResult>
ListWhereSelectIterator<TSource, TResult>menangani rantaiWhere(...).Select(...)pada list sebagai satu iterator- Iterator ini dibuat dari
Select()override milikListWhereIterator<TSource, TResult> ListWhereIterator<TSource>dibuat saatEnumerable.Where()memeriksa apakah sumbernya adalahList<TSource>ListWhereSelectIterator<TSource, TResult>tidak meng-override metode sepertiTryGetFirst()atauTryGetLast()- Inti peningkatan performanya adalah menggabungkan rantai
Where(...).Select(...)yang sangat umum pada list menjadi satu iterator, bukan dua iterator- Di dalam
MoveNext(), dua delegate_predicatedan_selectordipanggil bersama
- Di dalam
Contoh IListSkipTakeIterator<TSource>
IListSkipTakeIterator<TSource>adalah iterator khusus yang dibuat saat bisa diterapkanMoveNext()menggunakan_state - 1sebagai indeks berbasis 0 pada list- Memiliki field indeks terpisah akan lebih mudah dibaca, tetapi bias disimpan di
_stateuntuk mengurangi ukuran field iterator - Optimasi iterator ini terletak pada tidak mengiterasi elemen yang tidak perlu di luar rentang
_minIndexInclusivedan_maxIndexInclusive
Optimasi tambahan hanya dengan migrasi
- Di .NET 9, berbagai skenario LINQ umum menjadi lebih cepat
- Pekerjaan yang diperlukan untuk mendapatkan peningkatan dari versi .NET baru adalah migrasi dan kompilasi ulang
- LINQ juga dioptimalkan dengan cara lain
- Untuk kasus seperti penjumlahan sekuens integer, SIMD digunakan bila memungkinkan
- Sekuens kosong dideteksi lebih awal sehingga biaya enumerasi turun
- Video DeepDotnet dapat dilihat sebagai materi pembelajaran .NET yang menampilkan Scott Hanselman dan Stephen Toub
1 komentar
Opini Hacker News
Menurut saya bagian paling berguna dari LINQ bukanlah struktur ekstensi berbasis pohon sintaks pada
IQueryable, juga bukan sintaks bawaan bahasa, melainkan metode ekstensiIEnumerableDulu ini agak membingungkan disebut “LINQ to Objects”, dan memungkinkan C# ditulis secara ringkas dengan gaya fungsional
Artikel aslinya terutama membahas optimisasi metode-metode ekstensi ini
Baru setelah mempelajari Haskell, pendekatan ini terasa masuk akal bagi saya, dan pendekatan ini juga berbagi sebagian kelebihan serta jebakan Haskell seperti evaluasi malas
Jika dipakai sembarangan, kodenya bisa menjadi sulit dipahami dan lambat, jadi saya tidak akan merekomendasikannya jika di tim tidak ada orang yang memahami idiom fungsional dasar dan evaluasi malas
IEnumerabledanIQueryableLebih mudah ditalar, dan di tempat seperti Entity Framework memang tidak selalu menjadi pilihan tercepat, tetapi umumnya merupakan opsi yang cukup bagus
Saya juga lebih suka memakai Dapper daripada EF
Namun proyek C# cenderung memiliki lapisan abstraksi yang kelewat banyak, dan pengembangan “enterprise” umumnya menyakitkan untuk dilihat
Ada beberapa bagian penamaannya yang agak tidak standar, tetapi semua yang dibutuhkan sudah ada
Eric Lippert menulis seri artikel yang sangat bagus tentang monad dalam kaitannya dengan LINQ: https://ericlippert.com/2013/04/02/monads-part-twelve/
Saya tidak suka ada bahasa “bawaan” lain di dalam bahasa host, dan hasil akhirnya pada akhirnya juga harus kembali ke C#
Bahkan ketika tidak memakai ORM seperti Entity Framework atau Dapper, logika akses data termasuk SQL sebaiknya ditempatkan di proyek terabstraksi yang terpisah
Dengan begitu ia tidak menyebar ke seluruh aplikasi, dan bisa diganti ketika membutuhkan RDBMS lain
Dalam 20 tahun, hal itu memang hanya terjadi sekali
Saat developer junior memakai LINQ, memasangkan profiler dan debugger membantu mereka memahami apa yang terjadi di dalamnya
Kadang juga berguna meminta mereka menulisnya terlebih dahulu dengan loop
fordan logika C# biasa, lalu membandingkannya dengan implementasi LINQ, agar melihat kelebihan dan kekurangan kedua pendekatanSintaks kueri tidak di-hardcode khusus untuk
IEnumerable; hanya saja perilaku default-nya demikian, dan bisa dipakai hampir di mana sajaCara kerjanya agak mirip operator overloading
[1]: https://github.com/acple/ParsecSharp/blob/da8d0cb9ec39e28dd9...
Saya tidak mengerti mengapa tim dotnet tidak menginvestasikan lebih banyak sumber daya dan waktu pada tooling
Kita membutuhkan doctest dan pembuatan dokumentasi, kemampuan menulis unit test yang lebih baik dan cepat di samping kode sebenarnya, aksesibilitas source code, lingkungan tempat menekan F12 tidak perlu mendekompilasi DLL, serta hub terpusat untuk paket dan dokumentasi seperti
pkg.go.devataudocs.rsSebagian besar paket NuGet sama sekali tidak punya dokumentasi, atau hanya README GitHub, atau wiki singkat
Ekosistem lain seperti Rust, Go, Java, dan Python berjarak beberapa tahun cahaya di depan dalam hal ini
Namun yang mengerikan, rasanya itu mendekati benar
Contoh: https://learn.microsoft.com/en-us/dotnet/api/system.string.s...
Source paket NuGet juga bisa dibuat mudah dengan mengaktifkan Source Link, tetapi ini masih fitur yang relatif baru sehingga belum semua paket menerapkannya
Saya rasa sebagian besar kode C# masih ditulis sebagai closed source di perusahaan
Jika Microsoft terus bergerak ke arah yang lebih terbuka seperti beberapa tahun terakhir, saya pikir ini akan membaik seiring waktu
Beberapa fitur seperti ini disediakan oleh tool seperti Resharper, dan saya penasaran apakah ada kesepakatan eksplisit atau implisit untuk tidak saling memasuki wilayah masing-masing
Sejujurnya, sebagian besar dokumentasi yang saya lihat di proyek C# berkualitas rendah, sehingga akhirnya saya membaca source code
Pengalaman saya, meskipun ada banyak tool autocomplete, itu tidak banyak membantu untuk membaca, hanya membantu untuk menulis
https://github.com/EWSoftware/SHFB
Saya tidak yakin akan merekomendasikannya
Saya pernah mencobanya sendiri lalu membatalkannya, karena test tampaknya berjalan lebih lama, mungkin karena caching artefak build menjadi lebih buruk
Daripada “peningkatan performa LINQ”, lebih tepat disebut “peningkatan performa implementasi
Listmereka sendiri”Microsoft tampaknya lebih banyak menghabiskan waktu untuk memperbaiki bagian yang mereka butuhkan sendiri ketimbang peningkatan yang umum
LINQ, khususnya sintaks kueri dan bukan sekadar ekstensi metode, membutuhkan investasi
Terutama diperlukan pengurangan alokasi lambda, dan jika memungkinkan penyusutan lambda pada waktu kompilasi
Diperlukan strategi agar lambda lokal bertipe nilai, atau alokasi lambda, tidak menjadi overhead seperti sekarang
Variabel LINQ juga seharusnya sudah mendukung wildcard (
_) sekarang, tetapi hal itu sepenuhnya diabaikan saat diperkenalkan ke lambdaSelain itu, sebagai item terakhir dalam ekspresi LINQ, seharusnya bisa memakai tipe yang di-lift seperti
IEnumerableatauOption, alih-alihselect ...Pada kasus penggunaan tertentu,
selectmenciptakan overhead yang tidak perlu, dan juga membatasi hal-hal seperti ekspresi LINQ rekursif ekorLibrary yang habis-habisan memakai LINQ seperti library saya, tetapi tidak memakai
IEnumerable,IQueryable, atau ekstensi LINQ, terus diabaikanItu karena Microsoft hanya berfokus pada peningkatan performa proyek mereka sendiri
Contoh yang bagus adalah inferensi lambda yang ditingkatkan
Itu dipercepat karena dibutuhkan oleh Minimal API di ASP.NET Core
Tampaknya cukup banyak fitur bahasa dan framework digerakkan oleh kebutuhan internal Microsoft, bukan kebutuhan komunitas
Yang terburuk adalah kumpulan metode ajaib seperti ekstensi LINQ
Select,SelectMany,Where, dan jugaGetAwaiter, terus bertambahAlih-alih memasukkan fitur higher-kinded typeclass yang benar-benar dibutuhkan untuk menghapus keajaiban ini, Microsoft menambahkan fitur untuk kepentingan mereka sendiri, terutama compiler
Akibatnya semuanya tetap bertipe lemah, dan compiler hanya bisa menemukannya secara kira-kira
LINQ adalah salah satu pembeda inti antarbahasa, tetapi hampir dibiarkan begitu saja sejak C# 3
Sangat disayangkan bahwa LINQ masih dilihat hanya berguna untuk iterasi list, khususnya iterasi implementasi list milik mereka sendiri
Peningkatan performanya sendiri patut diapresiasi dan akan membantu banyak pengguna, tetapi fokusnya selalu terlalu sempit sehingga membatasi potensinya
[1] https://github.com/louthy/language-ext/
dotnet/runtimeBanyak peningkatan performa LINQ yang dibahas di artikel masuk dengan cara seperti itu
Ada banyak pernyataan
using, dan itu bukan masalah besar bagi orang yang memahami cara membagi proyek menjadi unit-unit yang diperlukan dan memisahkan concernNamun sebagian besar developer tidak menstrukturkan proyek seperti itu, dan detail kecil semacam ini bisa menjadi penghalang bagi developer rata-rata
Developer junior sering kali sudah kesulitan dengan sintaks dan metode LINQ standar, terutama juga soal performa
Bagus bahwa README menyebutkan hal seperti ini
Biasanya orang sibuk “menjual” library, jadi saya suka bahwa di sana benar-benar dituliskan di mana kekuatannya dan apa tujuannya
Penyebutan bahwa ini tidak idiomatis juga bisa menjadi masalah bagi orang yang belajar C#/.NET
Microsoft tampaknya ingin tooling dan bahasanya mengikuti praktik tertentu, dan penamaan yang secara alami disesuaikan dengan pemrograman fungsional bisa dianggap sebagai hambatan cukup besar saat Microsoft mempertimbangkan peningkatan
Saya sudah memberi bintang pada repositorinya, dan sangat tertarik dengan apa yang dibuat
Di beberapa aplikasi besar yang baru-baru ini saya buat, saya memakai tipe
Resultyang tampaknya kurang lebih mirip denganOptionNamun setelah melihat lagi library ini, saya merasa meskipun saya mengira cukup paham pemrograman fungsional, ternyata sebenarnya tidak
Saya cukup mahir C# dan pernah mengerjakan hal-hal kompleks, tetapi pemrograman fungsional tetap sulit dikuasai meski sudah banyak membaca, dan F# For Fun And Profit sejauh ini paling mudah saya pahami
Pada akhirnya, itu bukan berarti Anda melakukan sesuatu yang salah
Microsoft akan menargetkan developer rata-rata atau pemula yang merupakan mayoritas ekosistemnya
Semoga library ini bisa mendapatkan peningkatan internal
Terlihat jelas ada waktu yang sangat besar dicurahkan, dan dari jumlah bintang GitHub saja sudah cukup menjadi sinyal bahwa ada orang yang benar-benar memakai dan terbantu olehnya
Maaf jika terdengar seperti meremehkan, tetapi karya yang dibuat ini menarik dan dokumentasinya tampak cukup lengkap untuk mempelajari konsep-konsep yang belum saya ketahui secara perlahan
Semakin banyak C# meminjam dari F#, semakin baik
Saya menunggu discriminated union akhirnya masuk ke C# agar domain modeling bisa dilakukan dengan benar
Setiap kali begitu, muncul pertanyaan “kenapa tidak pakai F# saja?”
C# selama bertahun-tahun terus mengejar ketertinggalan
Jika F# mendorong banyak inovasi di ekosistem .NET dan sudah beberapa tahun lebih maju dari sisi fitur, saya penasaran kenapa upaya itu tidak dihargai lewat penggunaan
Jika ada arah pengembangan bahasa yang diinginkan, seharusnya itu didorong lewat pilihan nyata
Di ekosistem Java, cara seperti itu berhasil, dan sekarang Java juga membaik
Jika pasarnya membesar, bisa muncul loop umpan balik positif yang meningkatkan upaya engineering juga
Setelah membaca forum selama bertahun-tahun, kesannya kubu C# sangat ingin “tetap saja” berada di kubunya sendiri dan menunggu
Terlihat agak seperti tribalisme, seolah timnya adalah “C#”
Saya jarang melihat budaya seperti ini di ekosistem bahasa lain, dan saya mendapat kesan bahwa jika F# berada di ekosistem selain .NET, mungkin sudah berkembang pesat sejak lama
Pemeliharaan kode rekayasa atau sains akan jauh lebih mudah
Contoh penggunaan nyata: https://chrlschn.dev/blog/2024/07/csharp-discriminated-union...
[0] https://github.com/mcintyre321/OneOf
[1] https://github.com/domn1995/dunet
Hal yang paling saya rindukan saat bekerja di bahasa atau ekosistem lain adalah LINQ
Sangat menyenangkan ada fitur seperti ini di standard library, dan ia dirancang dengan indah dalam batasan yang ada
Ada bagian terkait dalam tulisan tahunan setebal satu buku yang membahas semua peningkatan performa di .NET 9
https://devblogs.microsoft.com/dotnet/performance-improvemen...
Anehnya HN tidak mengizinkan pengajuan ulang, jadi tulisannya tidak sempat naik ke front page dan terkubur
Setelah terbiasa dengan LINQ, dan biasanya bekerja di domain tempat LINQ benar-benar bersinar, rasanya tidak ingin kembali ke cara lain
LINQ akan mencengkeram kalian, dan kalian akan membenci lingkungan yang tidak punya LINQ
Saya penasaran apakah ada buku atau tutorial yang komprehensif untuk belajar pengembangan web end-to-end dengan dotnet
Yang saya temukan kebanyakan terlalu dasar, sudah usang, atau kualitasnya rendah
Secara pribadi, saya melihatnya akan menempuh jalan yang sama seperti Silverlight
Teknologi lama juga masih ada di .NET 9, berfungsi, dan dipelihara
Pengembangan web dengan .NET saat ini umumnya berarti membuat HTTP/JSON/REST API lalu menghubungkannya dengan framework frontend pilihan
Dalam kasus saya, saya memakai React atau NextJS
Kata kunci pencarian yang bagus adalah ASP.NET WebApi, atau yang lebih modern ASP.NET Minimal API
Server-side rendering .NET MVC dengan Razor juga masih bisa dilakukan
Karena itu bahasa markup untuk ASP.NET MVC, carilah dengan “ASP.NET MVC Razor”
Baik atau buruk, cara membuat aplikasi web di .NET tampaknya pada dasarnya adalah ASP.NET
Kurangnya alternatif memang sedikit mencurigakan
Saya mendengarkan podcast yang menghadirkan Andrew Lock, penulis “ASP.NET Core in Action”, dan ia terdengar seperti orang yang benar-benar memahami materinya
Saya belum membaca bukunya, tetapi mungkin itulah buku yang dicari
1: https://dotnetcore.show/season-6/navigating-the-aspnet-core-...
2: https://www.manning.com/books/asp-net-core-in-action-third-e...
Di server, Anda bisa menjalankan Giraffe di atas ASP.NET, sebuah lapisan pemrograman fungsional dengan performa yang mirip C#
Di frontend, Anda bisa menulis React dengan bahasa pemrograman fungsional sungguhan
Tentu saja Anda juga bisa berbagi kode F# antara frontend dan backend
Untuk buku, ada “C# 12 and .NET 8 - Modern Cross-Platform Development Fundamentals - Eighth Edition: Start building websites and services with ASP.NET Core 8, Blazor, and EF Core 8” karya Mark J Price, dan “Web API Development with ASP.NET Core 8: Learn techniques, patterns, and tools for building high-performance, robust, and scalable web APIs” karya Xiaodi Yan
Untuk tutorial, ada seri IAmTimCorey dan Shawn Wildermuth di YouTube
Jika kombinasinya backend .NET dan frontend JS, carilah materi yang memakai Minimal API
MVC juga bagus, tetapi beban kompatibilitas mundurnya besar, sehingga Minimal API muncul
Pasti ada cara yang lebih baik daripada tumpukan mi komentar ini
Setiap kali melihat kode .NET modern, mata saya sakit
Kode unit test dan benchmarking biasanya memang cenderung terlihat agak seperti spaghetti
Meski begitu, saya tidak akan meloloskan PR yang seperti itu di business logic sungguhan
Kalau benar-benar tidak suka, bahkan sesuatu seperti AspNetCore bisa dipakai tanpa menyentuh atribut sama sekali
Saya hampir tidak pernah memakai atribut
Bagian bahwa lebih banyak optimisasi bisa dilakukan ketika chain berakhir dengan metode seperti
Count(),First(),Last(),ElementAt(),Sum(), dan misalnyaOrderBy(criteria).First()bisa dioptimalkan agar berjalan sepertiMin(criteria), mungkin bergunaTetapi sejak awal, seharusnya tulislah kode yang lebih baik
Ini menarik untuk chain yang dibuat secara dinamis, tetapi jika operasi seperti ini ada di kode yang ditulis langsung, rasanya seperti penguatan positif yang agak menyimpang
Library seolah mengenali pola yang tidak efisien lalu memperbaikinya
Setidaknya saya berharap ada umpan balik yang menyarankan perbaikan pada kode dasarnya