Spanned adalah perpustakaan. NET-alokasi.
Untuk memulai, pertama -tama tambahkan paket yang direntang ke proyek Anda. Anda dapat melakukan ini dengan menjalankan perintah berikut:
dotnet add package SpannedAtau, Anda dapat menginstalnya melalui Paket Manajer Konsol dengan perintah ini:
Install-Package SpannedPerhatikan bahwa .NET telah mengumpulkan banyak rute optimasi dari waktu ke waktu, baik bergantung pada kerangka kerja dan runtime. Akibatnya, ini menjadi sangat menantang bagi tetangga kode yang sangat dioptimalkan untuk kerangka kerja yang berbeda dalam satu basis kode. Oleh karena itu, saya membuat keputusan yang sulit untuk mendukung hanya satu kerangka kerja per versi perpustakaan ini. Anda dapat menemukan daftar versi perpustakaan dan versi kerangka kerja yang didukung yang sesuai dalam tabel di bawah ini:
| .NET Standard 2.0 | .NET Standard 2.1 | .Net 8+ | |
|---|---|---|---|
Meskipun mungkin tergoda untuk menggunakan v0.0.1 untuk semua kebutuhan Anda, mengingat dukungannya untuk .NET Standard 2.0 dan adopsi luas .NET Standard 2.0 saat ini, saya sangat merekomendasikan untuk tidak melakukannya. Hampir tidak ada optimisasi yang dapat dilakukan oleh kerangka kerja warisan ini. Bahkan Span tercinta kami, yang dapat diakses di .NET Standard 2.0 melalui System.Memory Paket Nuget Memory, dikenal sebagai "rentang lambat", karena Span itu tidak lebih dari ArraySegment yang diciptakan kembali, tidak memiliki dukungan yang tepat di sisi runtime/jit. Karena itu, pilih versi paket terbaik untuk lingkungan Anda, bukan yang tampaknya cocok untuk semuanya.
Sebelum kita masuk ke detailnya, mari kita bahas beberapa kasus tepi umum yang mungkin Anda temui saat menggunakan perpustakaan ini, dan menjawab pertanyaan: "Mengapa X tidak menjadi bagian dari .net?" Singkatnya, semua yang dapat Anda temukan di sini mudah digunakan dan mudah disalahgunakan.
Mari kita mulai dengan poin yang jelas. Perpustakaan ini dirancang khusus untuk skenario di mana Anda bertarung dengan gigi dan paku untuk setiap byte yang dialokasikan dan setiap nanodetik waktu eksekusi di jalur yang sangat kritis. Ini tidak dimaksudkan untuk menjadi solusi satu ukuran untuk semua untuk seluruh basis kode Anda. Menggabungkan tipe dari perpustakaan ini dalam setiap skenario yang mungkin dapat menurunkan kinerja keseluruhan aplikasi Anda, daripada meningkatkannya.
Ingat, jangan menyelam terlebih dahulu ke samudera nano-optimisasi sampai Anda yakin itu perlu.
Kesalahan umum yang harus dihindari adalah melewati salah satu jenis yang disediakan oleh perpustakaan ini berdasarkan nilai (ya, ini saja akan membantu Anda memahami mengapa hal seperti ini seharusnya tidak dan tidak akan pernah menjadi bagian dari BCL) . Misalnya, sementara kode berikut mungkin terlihat bagus, itu sebenarnya bencana:
ValueStringBuilder sb = new ValueStringBuilder ( 16 ) ;
sb . AppendUserName ( user ) ;
// ...
public static void AppendUserName ( this ValueStringBuilder sb , User user )
{
sb . Append ( user . FirstName ) ;
sb . Append ( ' ' ) ;
sb . Append ( user . LastName ) ;
} Menambahkan pembangun string dapat memperbesar buffer internalnya. Namun, karena kami melewati ValueStringBuilder kami dengan nilai (yaitu, disalin) , ValueStringBuilder asli yang asli tidak akan menyadarinya dan akan terus menggunakan buffer yang sudah dibuang.
Sementara pendekatan ini mungkin tampaknya bekerja dengan input yang disanitasi selama pengujian, itu kadang -kadang akan gagal, melanggar tidak hanya kode Anda tetapi juga beberapa bagian acak dari runtime aplikasi Anda dengan mengganggu buffer bahwa salinan ValueStringBuilder yang telah kembali ke kolam, sehingga dapat digunakan kembali oleh sesuatu yang lain.
Anda mungkin mencoba untuk menjadi pintar tentang hal itu dan mengatasi masalah ini dengan menulis ulang metode ekstensi yang bermasalah sebagai berikut:
public static void AppendUserName ( this in ValueStringBuilder sb , User user )
{
sb . Append ( user . FirstName ) ;
sb . Append ( ' ' ) ;
sb . Append ( user . LastName ) ;
} Sekarang, ValueStringBuilder disahkan dengan referensi, jadi seharusnya tidak ada masalah, bukan? Nah, tidak. in ada untuk mengurangi biaya penyalinan keseluruhan instance tipe nilai dengan meneruskan referensi ke metode sambil melestarikan semantik seolah -olah disahkan oleh nilai. Ini berarti setiap modifikasi negara dari penilai yang disediakan ValueStringBuilder tidak akan disebarkan ke instance asli. Jadi, kami masih memiliki masalah yang sama di tangan kami. Satu -satunya cara yang benar untuk mengimplementasikan metode yang dapat memodifikasi keadaan internal instance jenis nilai adalah dengan benar -benar melewatkannya dengan referensi:
ValueStringBuilder sb = new ValueStringBuilder ( 16 ) ;
AppendUserName ( ref sb , user ) ;
// ...
public static void AppendUserName ( ref ValueStringBuilder sb , User user )
{
sb . Append ( user . FirstName ) ;
sb . Append ( ' ' ) ;
sb . Append ( user . LastName ) ;
}Meskipun tidak sedingin yang diinginkan beberapa orang, solusi ini memiliki manfaat benar -benar bekerja.
Sebagian besar jenis yang disediakan oleh perpustakaan ini menentukan metode Dispose() , memungkinkan penggunaannya dengan kata kunci using , seperti yang dapat dilihat di bawah ini:
using ValueStringBuilder sb = new ValueStringBuilder ( stackalloc char [ 16 ] ) ;
Foo ( ref sb ) ;
return sb . ToString ( ) ; Namun, ini tidak berarti bahwa mereka harus digunakan dengan kata kunci using . Sangat penting untuk mengingat bagaimana kode di atas sebenarnya diturunkan:
ValueStringBuilder sb = new ValueStringBuilder ( stackalloc char [ 16 ] ) ;
try
{
Foo ( ref sb ) ;
return sb . ToString ( ) ;
}
finally
{
sb . Dispose ( ) ;
} Membuat dan mengelola daerah yang dilindungi tidak gratis. Mempertimbangkan fokus kami pada optimasi nano, dampaknya di sini terlihat. Oleh karena itu, lebih baik secara manual memanggil Dispose() :
ValueStringBuilder sb = new ValueStringBuilder ( stackalloc char [ 16 ] ) ;
Foo ( ref sb ) ;
string result = sb . ToString ( ) ;
sb . Dispose ( ) ;
return result ;Atau, periksa apakah metode terakhir yang Anda hubungi pada jenis yang diberikan memiliki kelebihan beban yang melakukan pembersihan secara otomatis:
ValueStringBuilder sb = new ValueStringBuilder ( stackalloc char [ 16 ] ) ;
Foo ( ref sb ) ;
return sb . ToString ( dispose : true ) ;Dalam .net modern, adalah umum untuk menghadapi pola berikut:
Atau, mengekspresikan konsep yang sama dalam kode:
const int StackAllocByteLimit = 1024 ;
T [ ] ? spanSource ;
scoped Span < T > span ;
if ( sizeof ( T ) * length > StackAllocByteLimit )
{
spanSource = ArrayPool < T > . Shared . Rent ( length ) ;
span = spanSource . AsSpan ( 0 , length ) ;
}
else
{
spanSource = null ;
span = stackalloc T [ length ] ;
}
DoSomeWorkWithSpan ( span ) ;
if ( spanSource is not null )
{
ArrayPool < T > . Shared . Return ( spanSource ) ;
} Bukan bagian terbaik dari boilerplate, bukan? Logika yang sebenarnya sering akhirnya terkubur olehnya, yang jauh dari ideal. Ini adalah masalah yang tepat yang ingin dipecahkan SpanOwner . Inilah logika yang sama, tetapi semua boilerplate telah disembunyikan di belakang SpanOwner :
SpanOwner < T > owner = SpanOwner < T > . ShouldRent ( length ) ? SpanOwner < T > . Rent ( length ) : stackalloc T [ length ] ;
Span < T > span = owner . Span ;
DoSomeWorkWithSpan ( span ) ;
owner . Dispose ( ) ; Jauh lebih mudah untuk ditulis, jauh lebih mudah dibaca, dan, yang paling penting, pendekatan ini memberikan kinerja yang sama persis karena SpanOwner dirancang untuk sepenuhnya tidak dapat dilewati. Ini dapat sepenuhnya dihilangkan dari kode Anda dengan JIT:
| Metode | Berarti | Stddev | Perbandingan | Ukuran kode |
|---|---|---|---|---|
| Tanpa_spanowner_int32 | 5.134 ns | 0,0425 ns | 1.00 | 315 b |
| Dengan_spanowner_int32 | 4.908 ns | 0,0168 ns | 0.96 | 310 b |
ValueStringBuilder adalah implementasi ulang dari StringBuilder yang dirancang untuk mendukung buffer yang dialokasikan dengan tumpukan. Ini mampu memanfaatkan kumpulan array bersama untuk memperluas buffer internalnya bila perlu. ValueStringBuilder sangat cocok untuk membangun string kompak yang bisa muat di tumpukan; Namun, itu tidak boleh digunakan untuk hal lain, karena operasi pada string yang lebih besar dapat dan akan menurunkan kinerja aplikasi Anda secara keseluruhan. Kecemerlangan sejati dari ValueStringBuilder muncul ketika Anda perlu membuat urutan karakter pendek yang tidak perlu terwujud sebagai string sama sekali.
ValueStringBuilder mencerminkan semua fitur StringBuilder dan berhasil melewati set tes unit yang sama, memungkinkannya untuk berfungsi sebagai pengganti drop-in di sebagian besar skenario.
// Note that providing a capacity instead of a buffer will force
// the builder to rent an array from `ArrayPool<char>.Shared`.
ValueStringBuilder sb = new ( stackalloc char [ 256 ] ) ;
// `ValueStringBuilder` provides a custom interpolated string handler,
// ensuring such operations do not allocate any new strings.
sb . Append ( $ "Hello, { user . Name } ! Your ID is: { user . id } " ) ;
// Unlike `StringBuilder`, `ValueStringBuilder` can be represented
// as a readonly span. Thus, you don't need to actually materialize
// the string you've built in lots of cases.
DisplayWelcome ( ( ReadOnlySpan < char > ) sb ) ;
// Remember to dispose of the builder to return
// a rented buffer, if any, back to the pool.
sb . Dispose ( ) ; ValueList<T> adalah implementasi ulang dari List<T> yang dirancang untuk mendukung buffer yang dialokasikan dengan tumpukan. Ini mampu memanfaatkan kumpulan array bersama untuk memperluas buffer internalnya bila perlu. ValueList<T> sangat cocok untuk memproses sejumlah kecil data yang dapat sesuai dengan tumpukan; Namun, itu tidak boleh digunakan untuk hal lain, karena operasi pada dataset yang lebih besar dapat dan akan menurunkan kinerja keseluruhan aplikasi Anda.
ValueList<T> mencerminkan semua fitur List<T> dan berhasil melewati set tes unit yang sama, memungkinkannya untuk berfungsi dengan mulus sebagai pengganti drop-in di sebagian besar skenario.
// Note that providing a capacity instead of a buffer will force
// the list to rent an array from `ArrayPool<T>.Shared`.
ValueList < int > list = new ( stackalloc int [ 10 ] ) ;
list . Add ( 0 ) ;
list . Add ( 1 ) ;
list . Add ( 2 ) ;
DoSomethingWithIntegers ( ( ReadOnlySpan < int > ) list ) ;
// Remember to dispose of the list to return
// a rented buffer, if any, back to the pool.
list . Dispose ( ) ; ValueStack<T> adalah implementasi ulang dari Stack<T> yang dirancang untuk mendukung buffer yang dialokasikan oleh stack. Ini mampu memanfaatkan kumpulan array bersama untuk memperluas buffer internalnya bila perlu. ValueStack<T> sangat cocok untuk memproses sejumlah kecil data yang dapat sesuai dengan tumpukan; Namun, itu tidak boleh digunakan untuk hal lain, karena operasi pada dataset yang lebih besar dapat dan akan menurunkan kinerja keseluruhan aplikasi Anda.
ValueStack<T> mencerminkan semua fitur Stack<T> dan berhasil melewati set tes unit yang sama, memungkinkannya untuk berfungsi sebagai pengganti drop-in di sebagian besar skenario.
// Note that providing a capacity instead of a buffer will force
// the stack to rent an array from `ArrayPool<T>.Shared`.
ValueStack < int > stack = new ( stackalloc int [ 10 ] ) ;
stack . Push ( 0 ) ;
stack . Push ( 1 ) ;
stack . Push ( 2 ) ;
DoSomethingWithIntegers ( ( ReadOnlySpan < int > ) stack ) ;
// Remember to dispose of the stack to return
// a rented buffer, if any, back to the pool.
stack . Dispose ( ) ; ValueQueue<T> adalah implementasi ulang dari Queue<T> yang dirancang untuk mendukung buffer yang dialokasikan dengan tumpukan. Ini mampu memanfaatkan kumpulan array bersama untuk memperluas buffer internalnya bila perlu. ValueQueue<T> sangat cocok untuk memproses sejumlah kecil data yang dapat sesuai dengan tumpukan; Namun, itu tidak boleh digunakan untuk hal lain, karena operasi pada dataset yang lebih besar dapat dan akan menurunkan kinerja keseluruhan aplikasi Anda.
ValueQueue<T> mencerminkan semua fitur Queue<T> dan berhasil melewati set tes unit yang sama, memungkinkannya untuk berfungsi sebagai pengganti drop-in di sebagian besar skenario.
// Note that providing a capacity instead of a buffer will force
// the queue to rent an array from `ArrayPool<T>.Shared`.
ValueQueue < int > queue = new ( stackalloc int [ 10 ] ) ;
queue . Enqueue ( 0 ) ;
queue . Enqueue ( 1 ) ;
queue . Enqueue ( 2 ) ;
DoSomethingWithIntegers ( ( ReadOnlySpan < int > ) queue ) ;
// Remember to dispose of the queue to return
// a rented buffer, if any, back to the pool.
queue . Dispose ( ) ; ValueSet<T> adalah implementasi ulang HashSet<T> yang dirancang untuk mendukung buffer yang dialokasikan stack. Ini mampu memanfaatkan kumpulan array bersama untuk memperluas buffer internalnya bila perlu. ValueSet<T> sangat cocok untuk memproses sejumlah kecil data yang dapat sesuai dengan tumpukan; Namun, itu tidak boleh digunakan untuk hal lain, karena operasi pada dataset yang lebih besar dapat dan akan menurunkan kinerja keseluruhan aplikasi Anda.
ValueSet<T> mencerminkan semua fitur HashSet<T> dan berhasil melewati set tes unit yang sama, memungkinkannya untuk berfungsi dengan mulus sebagai pengganti drop-in di sebagian besar skenario.
// Note that providing a capacity instead of a buffer will force
// the set to rent an array from `ArrayPool<T>.Shared`.
ValueSet < int > set = new ( stackalloc int [ 10 ] ) ;
set . Add ( 0 ) ;
set . Add ( 1 ) ;
set . Add ( 2 ) ;
DoSomethingWithIntegers ( ( ReadOnlySpan < int > ) set ) ;
// Remember to dispose of the set to return
// a rented buffer, if any, back to the pool.
set . Dispose ( ) ; ValueDictionary<TKey, TValue> adalah implementasi ulang Dictionary<TKey, TValue> yang dirancang untuk mendukung buffer yang dialokasikan stack. Ini mampu memanfaatkan kumpulan array bersama untuk memperluas buffer internalnya bila perlu. ValueDictionary<TKey, TValue> sangat cocok untuk memproses sejumlah kecil data yang dapat sesuai dengan tumpukan; Namun, itu tidak boleh digunakan untuk hal lain, karena operasi pada dataset yang lebih besar dapat dan akan menurunkan kinerja keseluruhan aplikasi Anda.
ValueDictionary<TKey, TValue> mencerminkan semua fitur Dictionary<TKey, TValue> dan berhasil lulus set unit tes yang sama, memungkinkannya untuk berfungsi dengan mulus sebagai pengganti drop-in di sebagian besar skenario.
// Note that providing a capacity instead of a buffer will force
// the dictionary to rent an array from `ArrayPool<T>.Shared`.
ValueDictionary < int , string > dictionary = new ( 10 ) ;
dictionary . Add ( 0 , "zero" ) ;
dictionary . Add ( 1 , "one" ) ;
dictionary . Add ( 2 , "two" ) ;
DoSomethingWithPairs ( ( ReadOnlySpan < KeyValuePair < int , string > > ) dictionary ) ;
// Remember to dispose of the dictionary to return
// a rented buffer, if any, back to the pool.
dictionary . Dispose ( ) ; .Min() dan .Max() adalah metode ekstensi yang dapat membantu Anda menemukan nilai minimum/maksimum dalam suatu rentang. Mereka divektor untuk semua jenis yang didukung, tidak seperti Enumerable.Min() dan Enumerable.Max() , yang tidak memberikan optimasi apa pun untuk bilangan titik mengambang.
Namun, ada sedikit masalah dengan bilangan titik mengambang (yaitu, float dan double ), dan nama masalah ini adalah NaN . Seperti yang mungkin Anda ketahui, NaN tidak lebih besar atau kurang dari angka apa pun, dan itu tidak sama dengan angka apa pun, bahkan untuk dirinya sendiri. Dengan demikian, jika NaN hadir dalam urutan yang disediakan, ia dapat mengganggu implementasi naif yang hanya bergantung pada hasil operasi perbandingan reguler. Dengan demikian, jika NaN hadir dalam urutan yang disediakan, ia dapat mengganggu implementasi naif yang hanya bergantung pada hasil operasi perbandingan reguler. Oleh karena itu, tidak memperhitungkan kutukan perbandingan titik mengambang ini bukanlah suatu pilihan.
Spanned berhasil menggunakan semua cek terkait NaN dengan cara yang sangat efisien, memberikan peningkatan kinerja yang signifikan dibandingkan solusi yang tidak dioptimalkan. Namun, kinerjanya bisa lebih baik jika kita tidak perlu memperhitungkan NaN . Inilah sebabnya .UnsafeMin() dan .UnsafeMax() ada. Metode-metode ini khusus untuk rentang yang mengandung bilangan titik mengambang, dan mereka melakukan operasi perbandingan tanpa mengakui keberadaan NaN , menghilangkan semua pemeriksaan terkait. Jadi, jika Anda benar-benar yakin rentang bilangan titik mengambang disanitasi dan tidak dapat mengandung NaN S, Anda dapat memeras lebih banyak kinerja dari .Min() dan .Max() operasi.
Sedangkan perbedaan antara .Min() dan .UnsafeMin() mungkin tidak terlalu terlihat:
| Metode | Berarti | Stddev | Perbandingan | Ukuran kode |
|---|---|---|---|---|
| Min_loop_single | 3.919.5 ns | 15.75 ns | 1.00 | 207 b |
| Min_linq_single | 4.030.3 ns | 37.38 ns | 1.03 | 570 b |
| Min_span_single | 611.1 ns | 8,55 ns | 0.16 | 534 b |
| Unsafemin_span_single | 569.0 ns | 1.82 ns | 0,15 | 319 b |
Kesenjangan kinerja menjadi cukup substansial antara .Max() dan .UnsafeMax() :
| Metode | Berarti | Stddev | Perbandingan | Ukuran kode |
|---|---|---|---|---|
| Max_loop_single | 3.849.2 ns | 36.97 ns | 1.00 | 215 b |
| Max_linq_single | 3.936.4 ns | 53.51 ns | 1.02 | 643 b |
| Max_span_single | 901.7 ns | 7.12 ns | 0.23 | 606 b |
| Unsafemax_span_single | 551.8 ns | 3.06 ns | 0.14 | 321 b |
.Sum() adalah metode ekstensi yang dapat membantu Anda menghitung jumlah semua nilai dalam suatu rentang. Ini di vektor untuk semua jenis yang didukung, tidak seperti Enumerable.Sum() , yang tidak hanya kekurangan vektorisasi, tetapi tidak memberikan kelebihan beban untuk sebagian besar tipe numerik di luar kotak sama sekali.
Mirip dengan .Min() dan .Max() , .Sum() memiliki kembar jahat yang sesuai dengan nama .UnsafeSum() . Metode dasar akan melempar OverflowException jika jumlah perhitungannya menghasilkan integer overflow/underflow. Pengawal yang meluap, tentu saja, datang dengan biaya, dan itu bukan yang bisa diabaikan. Oleh karena itu, jika input Anda disanitasi dan tidak dapat menyebabkan overflow, atau jika integer overflow adalah perilaku yang diharapkan dalam konteks kerja Anda, jangan ragu untuk menggunakan .UnsafeSum() . Ini dua kali lebih cepat dari .Sum() , 34 kali lebih cepat daripada menghitung jumlah dalam satu loop, dan santai 130 kali lebih cepat daripada menghitung jumlah melalui linq:
| Metode | Berarti | Stddev | Perbandingan | Ukuran kode |
|---|---|---|---|---|
| Sum_loop_int16 | 3.820.0 ns | 7.04 ns | 1.00 | 128 b |
| Sum_linq_int16 | 14.472.6 ns | 281.83 ns | 3.80 | 732 b |
| Sum_span_int16 | 214.6 ns | 2.43 ns | 0,06 | 413 b |
| Unsfesum_span_int16 | 111.8 ns | 1,00 ns | 0,03 | 200 b |
.LongSum() adalah metode ekstensi yang dapat membantu Anda menghitung jumlah semua nilai dalam rentang menggunakan akumulator 64-bit (yaitu, long untuk bilangan bulat yang ditandatangani, ulong untuk bilangan bulat yang tidak ditandatangani, dan double untuk float ) , mampu menyimpan int.MaxValue + int.MaxValue yang lebih besar dari nilainya. int ) . Ini di vektor untuk semua jenis yang didukung dan tidak memiliki alternatif yang tepat di LINQ (dengan demikian, tolok ukur di bawah ini sedikit tidak adil) .
.LongSum() tidak memiliki mitra "tidak aman", karena bahkan rentang terbesar yang menyimpan unsur int.MaxValue dari tipe int tidak dapat menyebabkan luapan akumulator 64-bit ( (long)int.MaxValue * (long)int.MaxValue < long.MaxValue ).
| Metode | Berarti | Stddev | Perbandingan | Ukuran kode |
|---|---|---|---|---|
| Longsum_loop_int16 | 2.537.1 ns | 21.30 ns | 1.00 | 98 b |
| Longsum_linq_int16 | 14.372.0 ns | 130,00 ns | 5.67 | 734 b |
| Longsum_span_int16 | 251.0 ns | 2.38 ns | 0.10 | 390 b |
.Average() adalah metode ekstensi yang dapat membantu Anda menghitung rata -rata semua nilai dalam suatu rentang. Ini di vektor untuk semua jenis yang didukung, tidak seperti Enumerable.Average() , yang hanya memberikan beberapa tingkat optimasi untuk bilangan bulat yang ditandatangani 32-bit (yaitu, int s).
Di bawah kap, .Average() menggunakan .LongSum() untuk menghitung jumlah semua elemen sambil menghindari luapan integer. Namun, jika input Anda disanitasi dan tidak dapat menyebabkannya, Anda dapat beralih ke .UnsafeAverage() , yang menggunakan .UnsafeSum() dan tidak menghabiskan waktu eksekusi yang berharga untuk pelindung overflow.
| Metode | Berarti | Stddev | Perbandingan | Ukuran kode |
|---|---|---|---|---|
| Rata -rata_loop_int16 | 2.482.1 ns | 20.04 ns | 1.00 | 241 b |
| Rata -rata_linq_int16 | 13.198.2 ns | 97.67 ns | 5.31 | 1.016 b |
| Rata -rata_span_int16 | 257.8 ns | 3.61 ns | 0.10 | 593 b |
| Unsafeaveverage_span_int16 | 116.7 ns | 1.27 ns | 0,05 | 128 b |
.FillSequential() adalah metode ekstensi yang dapat membantu Anda mengisi rentang yang diberikan dengan nilai numerik berurutan. Ini di vektor untuk semua jenis yang didukung dan tidak memiliki alternatif di LINQ.
| Metode | Berarti | Stddev | Perbandingan | Ukuran kode |
|---|---|---|---|---|
| FillSequential_loop_int16 | 2.499.4 ns | 28.47 ns | 1.00 | 118 b |
| FillSequential_span_int16 | 169.2 ns | 0,18 ns | 0,07 | 660 b |
.IndexOf() , .LastIndexOf() , dan .Contains() mungkin tampak akrab bagi Anda, karena metode ini disediakan oleh MemoryExtensions . Namun, ada dua masalah dengan mereka:
IEquatable<T> , membuatnya tidak dapat diakses dalam konteks generik yang tidak terikat.IEqualityComparer<T> Kustom.Implementasi metode yang disediakan oleh perpustakaan ini membahas kedua masalah ini.
Dilisensikan berdasarkan ketentuan lisensi MIT.