Spanned ist eine leistungsstarke, Null-Alocation-.NET-Bibliothek, in der Span-kompatible Alternativen zu beliebten BCL-Typen einführt und vektorisierte Lösungen für gemeinsame Operationen für Spannweiten bietet.
Fügen Sie zunächst das überdurchschnittliche Paket Ihrem Projekt hinzu. Sie können dies tun, indem Sie den folgenden Befehl ausführen:
dotnet add package SpannedAlternativ können Sie es mit diesem Befehl über die Package Manager -Konsole installieren:
Install-Package SpannedBeachten Sie, dass .NET im Laufe der Zeit zahlreiche Optimierungsrouten akkumuliert hat, sowohl rahmen- als auch runt-abhängig. Infolgedessen ist es für den nachbarnoptimierten Code für verschiedene Frameworks in einer einzelnen Codebasis äußerst schwierig geworden. Daher habe ich eine schwierige Entscheidung getroffen, nur einen Framework pro Version dieser Bibliothek zu unterstützen. In der folgenden Tabelle finden Sie eine Liste der Bibliotheksversionen und die entsprechenden unterstützten Framework -Versionen:
| .NET Standard 2.0 | .NET Standard 2.1 | .NET 8+ | |
|---|---|---|---|
Während es möglicherweise verlockend ist, v0.0.1 für alle Ihre Anforderungen zu verwenden, unterstützt ich für .NET Standard 2.0 und die weit verbreitete Übernahme von .NET Standard 2.0 heutzutage, ich empfehle dringend, dies zu tun. Es gibt praktisch keine Optimierungen, die man mit diesem Legacy -Framework durchführen kann. Sogar unsere geliebte Span , die in .NET Standard 2.0 über das System.Memory -Nuget-Paket zugänglich ist, ist als "langsame Spannweite" bezeichnet, da diese Span nichts weiter als ein neu erfundenes ArraySegment ist und auf der Laufzeit/JIT-Seite nicht angemessen unterstützt wird. Wählen Sie daher bitte die beste Paketversion für Ihre Umgebung aus, nicht diejenige, die zu allen zu passen scheint.
Bevor wir uns mit den Details befassen, diskutieren wir einige gemeinsame Kantenfälle, denen Sie bei der Verwendung dieser Bibliothek begegnen können, und beantworten Sie die Frage: "Warum ist X -Teil von .NET nicht?" Kurz gesagt, alles, was Sie hier finden können, ist sowohl einfach zu bedienen als auch einfach zu missbrauchen.
Beginnen wir mit einem offensichtlichen Punkt. Diese Bibliothek wurde speziell für Szenarien entwickelt, in denen Sie für jedes zugewiesene Byte und jede Nanosekunde der Ausführungszeit auf hochkritischen Pfaden kämpfen. Es ist nicht als einheitliche Lösung für Ihre gesamte Codebasis vorgesehen. Das Einbeziehen von Typen aus dieser Bibliothek in jedem denkbaren Szenario kann die Gesamtleistung Ihrer Anwendung abbauen, anstatt sie zu verbessern.
Denken Sie daran, tauchen Sie nicht zuerst in den Ozean der Nanooptimierung ein, bis Sie sicher sind, dass dies notwendig ist.
Ein häufiger zu vermeidener Fehler besteht darin, eine der von dieser Bibliothek bereitgestellten Typen nach Wert zu verabschieden (ja, dies allein sollte Ihnen helfen, zu verstehen, warum so etwas nicht Teil der BCL sein sollte und wird) . Obwohl der folgende Code in Ordnung erscheinen kann, ist er beispielsweise tatsächlich katastrophal:
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 ) ;
} Das Anhängen an einen String -Builder kann seinen internen Puffer vergrößern. Da wir jedoch unseren ValueStringBuilder nach Wert verabschiedet haben (dh ihn kopiert) , wird der ursprüngliche ValueStringBuilder es nicht bewusst und werden weiterhin einen bereits entsorgten Puffer verwenden.
Während dieser Ansatz während des Tests mit sanitären Eingaben zu funktionieren scheint, wird er gelegentlich fehlgeschlagen und nicht nur Ihren Code, sondern auch einige zufällige Teile der Laufzeit Ihrer App durch Einmischung eines Puffers, dass eine Kopie von ValueStringBuilder bereits in den Pool zurückgekehrt ist, damit er von etwas anderem wiederverwendet werden kann.
Möglicherweise versuchen Sie, klug zu sein und das Problem zu beheben, indem Sie die problematische Erweiterungsmethode wie folgt neu schreiben:
public static void AppendUserName ( this in ValueStringBuilder sb , User user )
{
sb . Append ( user . FirstName ) ;
sb . Append ( ' ' ) ;
sb . Append ( user . LastName ) ;
} Jetzt wird ValueStringBuilder mit Bezug genommen, also sollte es keine Probleme geben, oder? Nun, nein. Der in existiert, um die Kosten für das Kopieren der Gesamtheit einer Wertstypinstanz zu senken, indem der Verweis auf eine Methode übergeht und gleichzeitig die Semantik aufrechterhalten wird, als ob sie mit Wert übergeben würden. Dies bedeutet, dass staatliche Änderungen des bereitgestellten ValueStringBuilder nicht an die ursprüngliche Instanz ausgegeben werden. Wir haben also immer noch das gleiche Problem in unseren Händen. Die einzige korrekte Möglichkeit, eine Methode zu implementieren, die den internen Zustand einer Werttypinstanz ändern kann, besteht darin, sie tatsächlich durch Referenz zu übergeben:
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 ) ;
}Obwohl diese nicht so ausgefallen sind, wie manche es sein würden, hat diese Lösung den Vorteil, tatsächlich zu arbeiten.
Die meisten der in dieser Bibliothek bereitgestellten Typen definieren eine Dispose() -Methode, wodurch deren Verwendung mit dem unten stehenden Keyword using wird, wie unten angezeigt wird:
using ValueStringBuilder sb = new ValueStringBuilder ( stackalloc char [ 16 ] ) ;
Foo ( ref sb ) ;
return sb . ToString ( ) ; Dies bedeutet jedoch nicht, dass sie mit der using des Keywords verwendet werden sollten . Es ist sehr wichtig, sich daran zu erinnern, wie der obige Code tatsächlich gesenkt wird:
ValueStringBuilder sb = new ValueStringBuilder ( stackalloc char [ 16 ] ) ;
try
{
Foo ( ref sb ) ;
return sb . ToString ( ) ;
}
finally
{
sb . Dispose ( ) ;
} Das Erstellen und Verwalten von geschützten Regionen ist nicht kostenlos. In Anbetracht unseres Fokus auf Nanooptimierungen sind die Auswirkungen hier spürbar. Daher ist es vorzuziehen, Dispose() manuell anzurufen:
ValueStringBuilder sb = new ValueStringBuilder ( stackalloc char [ 16 ] ) ;
Foo ( ref sb ) ;
string result = sb . ToString ( ) ;
sb . Dispose ( ) ;
return result ;Alternativ prüfen Sie, ob die letzte Methode, die Sie bei einem bestimmten Typ aufrufen, über eine Überladung verfügt, die automatisch die Reinigung durchführt:
ValueStringBuilder sb = new ValueStringBuilder ( stackalloc char [ 16 ] ) ;
Foo ( ref sb ) ;
return sb . ToString ( dispose : true ) ;In Modern .NET ist es üblich, auf das folgende Muster zu stoßen:
Oder das gleiche Konzept im Code ausdrücken:
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 ) ;
} Nicht das schönste Stück Kesselplatte, oder? Die tatsächliche Logik endet oft von ihr, was alles andere als ideal ist. Dies ist das genaue Problem, SpanOwner für die Lösung von Spanager zielt. Hier ist die gleiche Logik, aber die gesamte Kesselplatte wurde hinter dem SpanOwner versteckt:
SpanOwner < T > owner = SpanOwner < T > . ShouldRent ( length ) ? SpanOwner < T > . Rent ( length ) : stackalloc T [ length ] ;
Span < T > span = owner . Span ;
DoSomeWorkWithSpan ( span ) ;
owner . Dispose ( ) ; Viel einfacher zu schreiben, viel einfacher zu lesen, und vor allem liefert dieser Ansatz genau die gleiche Leistung, da SpanOwner so konzipiert ist, dass sie vollständig nicht mehr ansteht. Es kann von JIT vollständig aus Ihrem Code beseitigt werden:
| Verfahren | Bedeuten | Stddev | Verhältnis | Codegröße |
|---|---|---|---|---|
| Ohne_spanowner_int32 | 5.134 ns | 0,0425 ns | 1.00 | 315 b |
| With_spanowner_int32 | 4.908 ns | 0,0168 ns | 0,96 | 310 b |
ValueStringBuilder ist eine erneute Implementierung von StringBuilder , mit denen stapelgestützte Puffer unterstützt werden sollen. Es ist in der Lage, einen gemeinsam genutzten Array -Pool zu verwenden, um seinen internen Puffer bei Bedarf zu erweitern. ValueStringBuilder eignet sich perfekt zum Bau von kompakten Saiten, die auf den Stapel passen können. Es sollte jedoch für nichts anderes verwendet werden, da die Operationen in größeren Zeichenfolgen die Gesamtleistung Ihrer App beeinträchtigen können und werden. Die wahre Brillanz des ValueStringBuilder entsteht, wenn Sie eine kurze Zeichensequenz erstellen müssen, die überhaupt nicht als Zeichenfolge materialisiert werden muss.
ValueStringBuilder spiegelt alle Funktionen von StringBuilder wider und besteht erfolgreich denselben Satz von Unit-Tests, sodass es in den meisten Szenarien nahtlos als Drop-In-Ersatz dient.
// 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> ist eine Neuauflagen von List<T> für die Unterstützung von Stapel-zu-zu-zu-zu-allocated-Puffern. Es ist in der Lage, einen gemeinsam genutzten Array -Pool zu verwenden, um seinen internen Puffer bei Bedarf zu erweitern. ValueList<T> eignet sich perfekt für die Verarbeitung kleiner Datenmengen, die auf den Stapel passen. Es sollte jedoch für nichts anderes verwendet werden, da Vorgänge in größeren Datensätzen die Gesamtleistung Ihrer App beeinträchtigen können und werden.
ValueList<T> spiegelt alle Funktionen von List<T> wider und ermöglicht erfolgreich den gleichen Satz von Unit-Tests, sodass sie in den meisten Szenarien nahtlos als Drop-In-Ersatz dienen können.
// 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> ist eine Neuauflagen von Stack<T> , die zur Unterstützung von Stack-zu Allocated-Puffern entwickelt wurde. Es ist in der Lage, einen gemeinsam genutzten Array -Pool zu verwenden, um seinen internen Puffer bei Bedarf zu erweitern. ValueStack<T> eignet sich perfekt für die Verarbeitung kleiner Datenmengen, die auf den Stapel passen. Es sollte jedoch für nichts anderes verwendet werden, da Vorgänge in größeren Datensätzen die Gesamtleistung Ihrer App beeinträchtigen können und werden.
ValueStack<T> spiegelt alle Funktionen von Stack<T> wider und ermöglicht erfolgreich den gleichen Satz von Unit-Tests, sodass es in den meisten Szenarien nahtlos als Drop-In-Ersatz dient.
// 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> ist eine Neuauflagen von Queue<T> die zur Unterstützung von Stapel-zu-zu-allocated-Puffern entwickelt wurde. Es ist in der Lage, einen gemeinsam genutzten Array -Pool zu verwenden, um seinen internen Puffer bei Bedarf zu erweitern. ValueQueue<T> eignet sich perfekt für die Verarbeitung kleiner Datenmengen, die auf den Stapel passen. Es sollte jedoch für nichts anderes verwendet werden, da Vorgänge in größeren Datensätzen die Gesamtleistung Ihrer App beeinträchtigen können und werden.
ValueQueue<T> spiegelt alle Funktionen von Queue<T> wider und ermöglicht erfolgreich denselben Satz von Unit-Tests, sodass es in den meisten Szenarien nahtlos als Drop-In-Ersatz dient.
// 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> ist eine Neuauflagen von HashSet<T> , die zur Unterstützung von Stapel-zugegebenen Puffern entwickelt wurde. Es ist in der Lage, einen gemeinsam genutzten Array -Pool zu verwenden, um seinen internen Puffer bei Bedarf zu erweitern. ValueSet<T> eignet sich perfekt für die Verarbeitung kleiner Datenmengen, die auf den Stapel passen. Es sollte jedoch für nichts anderes verwendet werden, da Vorgänge in größeren Datensätzen die Gesamtleistung Ihrer App beeinträchtigen können und werden.
ValueSet<T> spiegelt alle Funktionen von HashSet<T> wider und ermöglicht erfolgreich den gleichen Satz von Unit-Tests, sodass es in den meisten Szenarien nahtlos als Drop-In-Ersatz dient.
// 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> ist eine Neuauflagen von Dictionary<TKey, TValue> für die Unterstützung von Stack-zu Allocated-Puffern. Es ist in der Lage, einen gemeinsam genutzten Array -Pool zu verwenden, um seinen internen Puffer bei Bedarf zu erweitern. ValueDictionary<TKey, TValue> eignet sich perfekt für die Verarbeitung kleiner Datenmengen, die auf den Stapel passen. Es sollte jedoch für nichts anderes verwendet werden, da Vorgänge in größeren Datensätzen die Gesamtleistung Ihrer App beeinträchtigen können und werden.
ValueDictionary<TKey, TValue> Spiegelt alle Funktionen von Dictionary<TKey, TValue> wider und ermöglicht erfolgreich den gleichen Satz von Unit-Tests, sodass es in den meisten Szenarien nahtlos als Drop-In-Ersatz dient.
// 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() und .Max() sind Erweiterungsmethoden, mit denen Sie den Minimum/Maximalwert in einer Spanne finden können. Sie sind für alle unterstützten Typen im Gegensatz zu Enumerable.Min() und Enumerable.Max() vektorisiert.
Es gibt jedoch ein geringes Problem mit schwimmenden Punktnummern (dh, float und double ), und der Name dieses Problems ist NaN . Wie Sie vielleicht wissen, ist NaN weder größer als noch weniger als eine Zahl, und es ist auch nicht der Zahl für sich selbst. Wenn also eine NaN in der bereitgestellten Sequenz vorhanden ist, kann sie eine naive Implementierung stören, die ausschließlich auf das Ergebnis regelmäßiger Vergleichsvorgänge beruht. Wenn also eine NaN in der bereitgestellten Sequenz vorhanden ist, kann sie eine naive Implementierung stören, die ausschließlich auf das Ergebnis regelmäßiger Vergleichsvorgänge beruht. Daher ist es keine Option, diesen Flügel von Gleitkomma-Vergleiche zu berücksichtigen.
Spanned schafft es, alle NaN bezogenen Schecks auf hocheffiziente Weise einzusetzen und einen erheblichen Leistungssteiger für nicht optimierte Lösungen zu erzielen. Die Leistung könnte jedoch sogar noch besser sein, wenn wir nicht für NaN s berücksichtigen müssten. Deshalb existieren .UnsafeMin() und .UnsafeMax() . Diese Methoden sind spezifisch für Spannweiten, die Gleitkomma-Zahlen enthalten, und sie führen Vergleichsoperationen durch, ohne die Existenz von NaN anzuerkennen, wodurch alle damit verbundenen Überprüfungen beseitigt werden. Wenn Sie also absolut sicher sind, dass eine Spannweite von Gleitkomma-Zahlen saniert ist und keine NaN -S enthalten kann, können Sie noch mehr Leistung aus .Min() und .Max() operationen herausholen.
Während der Unterschied zwischen .Min() und .UnsafeMin() möglicherweise nicht sehr auffällig sein kann:
| Verfahren | Bedeuten | Stddev | Verhältnis | Codegröße |
|---|---|---|---|---|
| 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 |
| Unafemin_span_single | 569.0 ns | 1,82 ns | 0,15 | 319 b |
Die Leistungslücke wird zwischen .Max() und .UnsafeMax() ziemlich erheblich:
| Verfahren | Bedeuten | Stddev | Verhältnis | Codegröße |
|---|---|---|---|---|
| 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 |
| Unafemax_span_single | 551.8 ns | 3.06 ns | 0,14 | 321 b |
.Sum() ist eine Erweiterungsmethode, mit der Sie die Summe aller Werte in einer Spanne berechnen können. Es ist für alle unterstützten Typen vektorisiert, im Gegensatz zu Enumerable.Sum() , der nicht nur die Vektorisierung fehlt, sondern für die meisten numerischen Typen überhaupt keine Überladungen liefert.
Ähnlich wie .Min() und .Max() hat .Sum() den bösen Zwilling, der den Namen hat .UnsafeSum() . Die Basismethode wirft eine OverflowException auf, wenn die Summenberechnung zu einem Überlauf/Unterlauf von Ganzzahl führt. Überlauf -Wächter sind natürlich mit Kosten und es ist keine vernachlässigbare. Wenn Ihre Eingabe saniert wird und keinen Überlauf verursachen kann oder wenn Ganzzahlüberlauf das erwartete Verhalten in Ihrem funktionierenden Kontext ist, können Sie .UnsafeSum() gerne verwenden. Es ist doppelt so schnell wie .Sum() , 34 -mal schneller als die Berechnung der Summe innerhalb einer Schleife und 130 -mal schneller als die Berechnung der Summe über Linq:
| Verfahren | Bedeuten | Stddev | Verhältnis | Codegröße |
|---|---|---|---|---|
| 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 |
| Unafesum_span_int16 | 111.8 ns | 1,00 ns | 0,03 | 200 b |
.LongSum() ist eine Erweiterungsmethode, mit der Sie die Summe aller Werte in einer Span unter Verwendung eines 64-Bit-Akkumulators (dh long für signierte Ganzzahlen, ulong für nicht signierte Ganzzahlen) berechnen können, und double für float ) . int.MaxValue + int.MaxValue int das Speichern eines Ergebniss größer als der Maximum/Minimumwert des Originaltyps speichern. . Es ist für alle unterstützten Typen vektorisiert und hat keine geeigneten Alternativen in Linq (daher ist der unten stehende Benchmark etwas unfair) .
.LongSum() hat kein "unsicher" Gegenstück, da selbst die größtmögliche Spanne, die int.MaxValue Elemente vom Typ int speichert, keinen Überlauf eines 64-Bit-Akkumulators ( (long)int.MaxValue * (long)int.MaxValue < long.MaxValue ) verursachen kann.
| Verfahren | Bedeuten | Stddev | Verhältnis | Codegröße |
|---|---|---|---|---|
| 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() ist eine Erweiterungsmethode, mit der Sie den Durchschnitt aller Werte in einer Spanne berechnen können. Im Gegensatz zu Enumerable.Average() ist es für alle unterstützten Typen vektorisiert, die nur ein gewisses Maß an Optimierung für 32-Bit-Zahlen (dh int S) liefert.
Unter der Motorhaube verwendet. .Average() .LongSum() um die Summe aller Elemente zu berechnen und gleichzeitig Ganzzahlüberläufe zu vermeiden. Wenn Ihre Eingabe jedoch saniert ist und keine verursachen kann, können Sie zu .UnsafeAverage() wechseln, das .UnsafeSum() verwendet und die kostbare Ausführungszeit für Überlaufschutz nicht ausgibt.
| Verfahren | Bedeuten | Stddev | Verhältnis | Codegröße |
|---|---|---|---|---|
| Durchschnitt_loop_int16 | 2.482,1 ns | 20.04 ns | 1.00 | 241 b |
| ADIVE_LINQ_INT16 | 13.198,2 ns | 97.67 ns | 5.31 | 1.016 b |
| Durchschnitt_span_int16 | 257,8 ns | 3.61 ns | 0,10 | 593 b |
| Unafeaverage_span_int16 | 116.7 ns | 1.27 ns | 0,05 | 128 b |
.FillSequential() ist eine Erweiterungsmethode, mit der Sie eine bestimmte Spanne mit sequentiellen numerischen Werten füllen können. Es ist für alle unterstützten Typen vektorisiert und hat keine Alternativen in Linq.
| Verfahren | Bedeuten | Stddev | Verhältnis | Codegröße |
|---|---|---|---|---|
| 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() und .Contains() mag Ihnen bekannt zu sein, da diese Methoden durch MemoryExtensions bereitgestellt werden. Es gibt jedoch zwei Probleme mit ihnen:
IEquatable<T> zu implementieren, sodass sie im ungebundenen generischen Kontext nicht zugänglich sind.IEqualityComparer<T> -implementierungen.Die Implementierungen dieser von dieser Bibliothek bereitgestellten Methoden begehen diese beiden Probleme.
Lizenziert unter den Bestimmungen der MIT -Lizenz.