Schnellste Weg (performance-wise), um einen string in ein byte[] array in C# unter Verwendung der ASCII-Zeichen-Kodierung
Was ist der Schnellste Weg um aus einem string in ein byte[] array in C#? Ich werde senden T-string von Daten über sockets und zu optimieren brauchen jeden einzelnen Betrieb. Derzeit gestalte ich die strings in byte [] - arrays vor dem versenden mit:
private static readonly Encoding encoding = new ASCIIEncoding();
//...
byte[] bytes = encoding.GetBytes(someString);
socket.Send(bytes);
//...
- Vielleicht möchten Sie ein Profil der Anwendung, bevor Sie verbringen zu viel Zeit hier. Reaktionen aus dem Bauch heraus, dass dies klingt nicht wie ein performance-Engpass, aber es gibt keine Möglichkeit zu sagen, ohne harte zahlen.
- +1 für das sentiment, aber dies ist in den Engpass und jede nano zählt hier
- Der Engpass ist die Menge der Daten, die Sie senden über den Draht oder die Konvertierung?
- Wenn jede Nanosekunde zählt, ist es vielleicht Zeit zu verschieben, diese Funktion in C++/CLI. Sie sagen, dass gut geschriebene C# ist "nur" 10% langsamer als vergleichbare C++; gut, wenn 10% wichtig....
Du musst angemeldet sein, um einen Kommentar abzugeben.
Wenn alle Ihre Daten wirklich werde ASCII, dann können Sie in der Lage sein, es zu tun, etwas schneller als
ASCIIEncoding
, die mit verschiedenen (durchaus sinnvoll) bits des error-handling und vieles mehr. Sie können auch in der Lage sein, es zu beschleunigen, durch die Vermeidung der Schaffung neuer byte-arrays, die alle die Zeit. Vorausgesetzt, Sie haben eine Obere Schranke, die alle Ihre Nachrichten werden unter:Würden Sie dann so etwas machen:
Dies ist ziemlich verzweifelt Optimierung obwohl. Würd ich mich mit
ASCIIEncoding
bis ich würde bewährte, dass dies der Engpass (oder zumindest, dass diese Art von grotty hack hilft nicht).as
- Schlüsselwort kann nur verwendet werden für die Typen, die kannnull
(d.h. Referenz-Typen und -Nullable<T>
/T?
).static byte[] GetBytes(string str) { byte[] bytes = new byte[str.Length * sizeof(char)]; System.Buffer.BlockCopy(str.ToCharArray(), 0, bytes, 0, bytes.Length); return bytes; }
ASCIIEncoding
. Im Grunde entspricht dasUnicodeEncoding
. Wenn du gehst, um alternative vorschlagen-code aus Gründen der Geschwindigkeit, es muss das gleiche Ergebnis wie das original.& 0x7f
Teil? warum nicht einfach(byte)chars[i]
?Ich würde sagen, dass, wie Sie tun, es ist jetzt ausreichend gut. Wenn Sie wirklich besorgt mit sehr low-level-Optimierung so, die beste Empfehlung, die ich machen kann, ist, bekommen Reflektor. Mit Reflektor, man kann sich den code selbst (die meiste Zeit), und sehen, was die algorithmen sind. Wenn der Reflektor nicht zeigen, Sie könnten immer download Microsoft SSCLI (Shared Source Common Language Infrastructure), um zu sehen der C++ - code hinter MethodImplOptions.InternalCall Methoden.
Referenz, hier ist die tatsächliche Umsetzung der Codierung.ASCII.GetBytes:
Für Schreiende Geschwindigkeit, mit der Umwandlung mittlere bis grössere Datenblöcke zwischen 8-bit -
byte[]
- und "wide" (16 bit, Unicode) text, werden Sie wollen prüfen, Lösungen bereitstellen, die SIMD AnweisungenPUNPCKLBW
+PUNPCKHBW
(Erweiterung) undPACKUSWB
(Verengung). In .NET, diese sind neu verfügbar als x64 JIT intrinstics, emittiert für die hardware-beschleunigteSystem.Numerics
ArtenVector
undVector<T>
(siehe hier für mehr info). Die generische versionVector<T>
definiert wird, in derSystem.Numerik.Vektoren
- Paket, das derzeit noch unter Recht aktiver Entwicklung. Wie unten dargestellt, werden Sie wahrscheinlich auch dieSystem.- Laufzeit.CompilerServices.Unsicher
Paket, da dies am günstigsten gelegene SIMD-load/store-Technik empfohlen von derVector<T>
Autoren.Die entsprechenden SIMD-Beschleunigung ist nur verfügbar für fähige CPUs in x64-Modus, aber sonst .NET bietet einen transparenten fallback-emulation, code in der
System.Numerics.Vectors
Bibliothek, damit der code hier zeigt sich hat in der Tat zuverlässig-Funktion, über die große .NETTO-ökosystem, möglicherweise mit reduzierter Leistung. Testen Sie den code unten dargestellt habe ich eine console-app auf die volle .NET Framework 4.7.2 ("desktop") in x64 (SIMD) und x86 (emuliert) - Modi.Da würde ich nicht vorenthalten möchte jemand die Gelegenheit zum erlernen der einschlägigen Techniken, die ich verwenden werde
Vector.Widen
zu veranschaulichen, diebyte[]
zuchar[]
Richtung, in C# 7. Aus diesem Beispiel, tun das Gegenteil--ich.e, mitVector.Narrow
zur Umsetzung der Verengung Richtung-ist unkompliziert und Links als eine übung für den Leser.Sie wurden gewarnt. Obwohl nicht-SIMD-beschleunigt, kanonische Techniken mit einem geeigneten
Encoding
Instanz empfohlen für fast alle realistischen app-Szenarien. Obwohl die OP macht ja anfordern, die maximale Leistung (oder mehr offensichtlich, als ein letzter verzweifelter Versuch, die impfen gegen downvotes von der Verunglimpfung der Polizei), als Nächstes habe ich ordnungsgemäß zusammenzufassen, ist die richtige, sanktioniert Techniken, dass die Regel verwendet werden soll.Ok, jetzt zum lustigen Teil-der extrem schnellen SIMD-fähigen ("vektorisiert") C# - code für "dumme" die Erweiterung des ein byte-array. Als Erinnerung, hier sind einige Abhängigkeiten, die referenziert werden soll:
Hier ist die öffentliche Einstiegspunkt wrapper-Funktion. Wenn Sie lieber eine version, die gibt
char[]
stattString
, es ist am Ende von diesem post.Nächsten ist die wichtigste Arbeit Schleife. Beachten Sie die Prolog-Schleife, richtet mit dem Ziel, eine 16-byte-Speicher-Grenze, falls erforderlich, durch eine Byte für Byte untersucht das kopieren von bis zu 15 Quell-Byte. Dies gewährleistet die effiziente Bedienung der wichtigsten "quad-quadwise" - Schleife mit einer einzigen Paarung von SIMD -
PUNPCKLBW/PUNPCKHBW
Anweisungen, schreibt die 32-bytes auf einmal (16 Quelle Byte abgerufen werden und dann gespeichert als 16 wide-chars besetzen, 32 bytes). Pre-Ausrichtung, plus die Auswahl der dst Ausrichtung (im Gegensatz zu src) sind die offiziellen Empfehlungen von der Intel-Handbuch, die oben zitiert werden. Ebenso, ausgerichtet auf den Betrieb mit sich bringt, dass, wenn die main-Schleife abgeschlossen ist, wird die Quelle kann bis zu 15 Rest-trailing-bytes; diese sind abgeschlossen, die von einem kurzen Epilog Schleife.Das ist eigentlich alles dort ist zu ihm! Es funktioniert wie ein Charme und, wie Sie unten sehen werden, es bedeutet 'Schreien' wie angekündigt.
Aber zunächst durch das ausschalten der vs2017 debugger-option "Disable JIT-Optimierungen," wir können prüfen, die native SIMD instruction stream, die x64 JIT generiert für das 'release' - bauen auf .NET 4.7.2. Hier ist der relevante Teil der wichtigsten inneren Schleife, die Blasten durch die Daten 32-Byte zu einem Zeitpunkt. Beachten Sie, dass der JIT hat es geschafft, zu emittieren, die theoretisch minimale fetch/store-Muster.
Performance-test-Ergebnisse:
Getestet habe ich die SIMD-code gegen vier andere Techniken, die die gleiche Funktion erfüllen. Für die .NET-Encoder aufgelistet, dies war ein Aufruf an die
GetChars(byte[], int, int)
Methode.Den Tests enthalten identische Arbeit für alle und Validierung identische Ergebnisse aller Prüflinge. Test-bytes wurden zufällig und nur ASCII-(
[0x01 - 0x7F]
) um sicherzustellen, dass das identische Ergebnis aus allen test-Einheiten. Input size zufällig war, maximal 1 MB mit einem log2 bias in Richtung zu den kleineren Größen, so dass die Durchschnittliche Größe war etwa 80K.Für die Schönheit, die Reihenfolge der Ausführung wurde systematisch gedreht durch die 5 Einheiten, die für jede iteration. Für warmup, timings wurden verworfen, und auf null zurückgesetzt, sobald bei iteration 100. Die Testumgebung nicht durchführen, gehen alle Zuordnungen, die während der test-phase und eine volle GC ist gezwungen und erwartete jeder 10000 Iterationen.
Auf die bevorzugte x64 Plattform, wenn der JIT-Optimierung aktiviert ist und SIMD verfügbar ist, es war kein Wettbewerb. Die SIMD-code ausgeführt wird, der über 150% schneller als die nächsten Konkurrenten. Die
Encoding.Default
, die in der Regel "Windows-1252" codepage, durchgeführt besonders schlecht, etwa 3x langsamer als die SIMD-code.Ich habe bereits erwähnt, dass die Verteilung der Testdaten Größen stark war, log-in Richtung null. Ohne diesen Schritt-das bedeutet eine gleichmäßige Verteilung der Größen von 0 bis 1.048.576 bytes (Durchschnittliche test-Größe 512K)--SIMD weiterhin schneller als das pack mit allen anderen Geräten geht relativ schlimmer vs. den oben angezeigten code.
Als für die nicht-SIMD - (emulation) Fall, UTF-8 und SIMD sind extrem nahe beieinander-in der Regel innerhalb von 3-4% jeder andere-und weit besser als der rest. Ich fand dieses Ergebnis ist doppelt verwunderlich: dass die Bei UTF8Encoding-source-code war so schnell (viele fast-path-Optimierung), und dann auch, dass der general-purpose-SIMD-Emulations-code war in der Lage, mit dazu abgestimmten code.
Nachtrag:
In dem obigen code, den ich erwähnte, eine mögliche O(n) Leistungseinbußen (im Zusammenhang mit überschuss-re-Nullung) von der Nutzung der
new String(Char,int)
Konstruktor eine Zuordnung der Ziel-string. Der Vollständigkeit halber, hier ist ein alternativer Einstiegspunkt, das könnte das problem vermeiden, indem anstelle der Rückgabe der verbreiterten Daten alsushort[]
:Vector<byte> Narrow(Vector<ushort>, Vector<ushort>)
dass wir hier wollen; beachten Sie auch, dass, wenn Sie verwenden .NET Core verwenden, können SieSpan<T>
undMemoryMarshal.Cast<TFrom,TTo>
zu ändernSpan<char>
direkt in eineSpan<Vector<ushort>>
(und ebenso einSpan<byte>
direkt in eineSpan<Vector<byte>>
- das machtSpan<T>
eine wirklich effektive Arbeitsweise mit SIMD, ohne alleUnsafe.AsRef
Was wollen Sie optimieren? CPU? Bandbreite?
Wenn Sie zum optimieren der Bandbreite, die Sie könnten versuchen, komprimieren Sie die Daten der Zeichenfolge vorher.
Erste, Profil Ihrem code, herauszufinden, was das langsame bits sind, bevor Sie versuchen, zu optimieren, auf einem so niedrigen Niveau.
Stell ich mir die GetBytes () - Funktion ist schon gut optimiert ist für diese. Ich kann nicht glauben, Vorschläge zur Verbesserung der Geschwindigkeit Ihres bestehenden code.
BEARBEITEN -- wissen Sie, ich weiß nicht, ob das schneller ist oder nicht. Aber hier ist ein anderes Verfahren unter Verwendung der BinaryFormatter:
Der Grund, warum ich denke, das könnte schneller ist, dass es überspringt die Codierung Schritt. Ich bin mir auch nicht ganz sicher, dass diese ordnungsgemäß funktionieren. Aber man könnte versuchen es und sehen. Natürlich, wenn Sie die ascii-Codierung, dann wird das nicht helfen.
Ich hatte gerade einen anderen Gedanken. Ich glaube, dieser code würde zurückgeben, die doppelte Anzahl von bytes als mit GetBytes mit ASCII-Codierung. Der Grund dafür ist, dass alle Zeichenfolgen in .NET use unicode-hinter den kulissen. Und natürlich Unicode 2 bytes pro Zeichen verwendet, in der Erwägung, dass ASCII verwendet nur 1. Also der BinaryFormatter ist wahrscheinlich nicht das, was zu verwenden, in diesem Fall, weil Sie würde eine Verdoppelung der Menge an Daten, die Sie senden über den socket.
Ohne Hinweis auf Ihre Parallelität Anforderungen (oder irgendetwas anderes): Können Sie laichen einige threads auf der ThreadPool, die konvertiert die strings byte-arrays und legen Sie Sie in eine Warteschlange, und habe noch ein thread gerade die Warteschlange und das senden der Daten?
Wie schon andere gesagt haben, die Encoding-Klasse ist bereits optimiert für diese Aufgabe, so wird es wahrscheinlich schwer sein, um es schneller zu machen. Es gibt eine Mikro-Optimierung, dass Sie tun können : verwenden Sie
Encoding.ASCII
eher alsnew ASCIIEncoding()
. Aber wie jeder weiß, Mikro-Optimierungen sind schlecht 😉Ich würde vorschlagen, profiling, was du tust. Ich finde es zweifelhaft, dass die Geschwindigkeit der Umwandlung von einem string in ein byte-array ist, ein größeres problem im Durchsatz als die Geschwindigkeit der Steckdose selbst.
Nur noch ein Tipp : ich weiß nicht, wie man erstellen Sie Ihre ersten Saiten, aber denken Sie daran, dass die Klasse StringBuilder.Append("irgendwas") ist wirklich schneller als so etwas wie myString += "etwas".
In den gesamten Prozess der Erstellung des strings, und sendet Sie über eine socket-Verbindung, ich wäre überrascht, wenn der Engpass war die Konvertierung von Strings in byte-arrays. Aber ich bin sehr daran interessiert, wenn jemand dies testen, mit einem profiler.
Ben