Nejrychlejší cesta (pro výkonnost) pro zapnutí řetězec do bajtové [] pole v C #

hlasů
7

Jaký je nejrychlejší způsob, jak proměnit řetězec do byte [] pole v jazyce C #? Posílám tun řetězec data prostřednictvím konektorů a je potřeba optimalizovat každou operaci. V současné době jsem transformovat řetězce do byte [] pole před odesláním pomocí:

private static readonly Encoding encoding = new ASCIIEncoding();
//...
byte[] bytes = encoding.GetBytes(someString);
socket.Send(bytes);
//...
Položena 26/08/2009 v 23:30
zdroj uživatelem
V jiných jazycích...                            


9 odpovědí

hlasů
15

Pokud jsou všechny vaše data ve skutečnosti bude ASCII, pak můžete být schopni to udělat o něco rychleji než ASCIIEncoding, který má různé (zcela přiměřená) bity chyba při manipulaci atd Ty mohou být také schopen zrychlit to tím, že zamezují vytvoření nového byte pole po celou dobu. Za předpokladu, že máte horní hranici, která všechny vaše zprávy se bude vztahovat:

void QuickAndDirtyAsciiEncode(string chars, byte[] buffer)
{
    int length = chars.Length;
    for (int i = 0; i < length; i++)
    {
        buffer[i] = (byte) (chars[i] & 0x7f);
    }
}

Člověk by pak něco jako:

readonly byte[] Buffer = new byte[8192]; // Reuse this repeatedly
...
QuickAndDirtyAsciiEncode(text, Buffer);
// We know ASCII takes one byte per character
socket.Send(Buffer, text.Length, SocketFlags.None);

To je docela zoufalá optimalizace ačkoli. Já bych držet ASCIIEncoding, dokud jsem prokázáno , že to bylo kritické místo (nebo alespoň, že tento druh špinavý hack nepomůže).

Odpovězeno 26/08/2009 v 23:47
zdroj uživatelem

hlasů
9

Řekl bych, že, jak to děláte teď je dost dobrá. Pokud si děláte starosti s velmi nízkou optimalizaci úrovně, jako je to, že nejlepším doporučením mohu udělat, je dostat reflektor. S reflektorem, můžete se podívat na kód sami (většinu času), a uvidíme, co algoritmy. Pokud reflektor se vám nezobrazuje, můžete vždy stáhnout Microsofts SSCLI (Shared Source Common Language Infrastructure) pro zobrazení C ++ kód za MethodImplOptions.InternalCall metod.

Pro porovnání je zde skutečná realizace Encoding.ASCII.GetBytes:

public override int GetBytes(string chars, int charIndex, int charCount, byte[] bytes, int byteIndex)
{
    if ((chars == null) || (bytes == null))
    {
        throw new ArgumentNullException();
    }
    if ((charIndex < 0) || (charCount < 0))
    {
        throw new ArgumentOutOfRangeException();
    }
    if ((chars.Length - charIndex) < charCount)
    {
        throw new ArgumentOutOfRangeException();
    }
    if ((byteIndex < 0) || (byteIndex > bytes.Length))
    {
        throw new ArgumentOutOfRangeException();
    }
    if ((bytes.Length - byteIndex) < charCount)
    {
        throw new ArgumentException();
    }
    int num = charIndex + charCount;
    while (charIndex < num)
    {
        char ch = chars[charIndex++];
        if (ch >= '\x0080')
        {
            ch = '?';
        }
        bytes[byteIndex++] = (byte) ch;
    }
    return charCount;
}
Odpovězeno 26/08/2009 v 23:34
zdroj uživatelem

hlasů
1

Charakteristickým výkon zavedení univerzálního memcpy funkci knihovny pomocí SIMD registr je podstatně pestřejší než ekvivalentní plnění pomocí univerzálního registru ...

  - Intel 64 a IA-32 architektury optimalizace referenční příručka (04 2018) §3.7 .6.1


Pro křičí rychlosti s převedením středně- až po větší velikosti bloků dat mezi 8-bitové byte[]a „široký“ (16-bit, Unicode) text, budete chtít, aby zvážila řešení, která nasadit SIMD instrukce PUNPCKLBW+ PUNPCKHBW(rozšíření) a PACKUSWB(zúžení ). V .NET se jedná o nově k dispozici jako x64 JIT intrinstics, emitovaných pro hardwarově akcelerované System.Numericstypů Vectora Vector<T>(viz zde pro více informací). Generické verze Vector<T>je definován v System.Numerics.Vectorsbalíčku, který v současné době zůstává v poměrně aktivním vývojem. Jak je uvedeno níže, budete také chtít, aby zahrnovala System.Runtime.CompilerServices.Unsafebalíček, protože to je výhodné, SIMD load / store postupy doporučenými Vector<T>autorů.

Příslušná JIMD akcelerace je dostupná pouze pro schopných CPU v režimu x 64 , ale jinak NET poskytuje transparentní nouzovou emulace kód v System.Numerics.Vectorsknihovně, takže kód zde prokázáno, dělá skutečně spolehlivě fungovat v rámci širšího .NET ekosystému, případně se sníženým výkonem. Chcete-li otestovat kód uvedený níže, jsem použil aplikaci konzoly na plný .NET Framework 4.7.2 ( „desktop“) v x64 (SIMD) a x86 (emulované) režimů.

Vzhledem k tomu bych nechtěl připravit někoho o možnost učit se příslušné techniky, budu používat Vector.Widenilustrovat byte[]na char[]směru, v C # 7 . Z tohoto příkladu, činíce reverse-- tj pomocí Vector.Narrowrealizovat směr zmenšuje - je jednoduchá a je ponechán jako cvičení pro čtenáře.

výstraha:
Metody zde navrhované je naprosto kódování-nevědomý, prostě proužek / expandovat - nebo úzký / rozšíření - syrové bytů do / z nezpracovaných bajtů bez ohledu na mapování znaků, kódování textu, nebo jinými jazykovými vlastnostmi. Když se rozšiřuje, přebytečné bajty se nastaví na nulu, a když se zužuje, přebytečné bajty jsou vyřazeny.

Jiní diskutovali o mnoho nebezpečí spojená s touto praxí na této stránce a jinde, tak si pečlivě přečíst a pochopit , než s ohledem na to, zda je vhodná pro danou situaci charakter této operace. Pro jasnost, validace inline je zmenšován z příkladu kódu níže, ale například může být přidán k nejvnitřnější smyčky s minimálním dopadem na prospěch JIMD.

Byli jste varováni . I když to není SIMD urychlené, kanonické techniky využívající vhodnou Encodinginstanci jsou vhodné pro téměř všechny realistických scénářů aplikací . Ačkoli OP skutečně konkrétně požadovat maximální výkon (nebo více otevřeně, jako poslední snaha k očkování proti downvotes podle hanění policie), další I, řádně shrnout správné, schvalované techniky, které by mělo být normálně použity.

Pro rozšíření bajtové pole na .NET String, vyvolat GetString () metoda na vhodném byte orientované kódující například:

String Encoding.ASCII.GetString(byte[] bytes)

Pro zúžení .NET Stringdo (například ASCII) bytového pole, uplatnit GetBytes () metoda na vhodném byte orientované kódující například:

byte[] Encoding.ASCII.GetBytes(char[] chars)


Ok, teď se k zábavnější části - extrémně rychlý SIMD-umožnil ( „vectorized“), C # kód pro „němý“ rozšiřování bytového pole. Připomínáme, že zde jsou některé závislosti, které by měly být odkazované:

// ... 
using System.Numerics;                  // nuget: System.Numerics.Vectors
using System.Runtime.CompilerServices;  // nuget: System.Runtime.CompilerServices.Unsafe
// ... 

Zde je veřejná vstupní funkce bod obálka. Pokud dáváte přednost verzi, která vrátí char[]místo String, to je k dispozici na konci tohoto příspěvku.

/// <summary>
/// 'Widen' each byte in 'bytes' to 16-bits with no consideration for
/// character mapping or encoding.
/// </summary>
public static unsafe String ByteArrayToString(byte[] bytes)
{
    // note: possible zeroing penalty; consider buffer pooling or 
    // other ways to allocate target?
    var s = new String('\0', bytes.Length);

    if (s.Length > 0)
        fixed (char* dst = s)
        fixed (byte* src = bytes)
            widen_bytes_simd(dst, src, s.Length);
    return s;
}

Další na řadě je hlavní pracovní smyčky těla. Všimněte si, že prolog smyčku, která zarovná cíl do paměti hranice 16 bajtů, je-li to nutné, bytewise kopírování až 15 zdrojových bajtů. To zajišťuje co nejefektivnější provoz hlavního „ quad quadwise “ vedení, které, s jediným párování JIMD PUNPCKLBW/PUNPCKHBWinstrukcí zapisuje 32 bajtů najednou (16 zdroj bajty jsou načteny a uloženy jako 16 širokých znaků zabírat 32 bajtů). Pre-vyrovnání, plus volba dst zarovnání (na rozdíl od src ) jsou oficiální doporučení od Intel manuál výše. Stejně tak vyrovnány operace znamená, že když hlavní smyčka dokončí, zdroj může mít až 15 zbytkových vlečných bytů; tyto jsou skončil krátkou epilogu smyčky.

static unsafe void widen_bytes_simd(char* dst, byte* src, int c)
{
    for (; c > 0 && ((long)dst & 0xF) != 0; c--)
        *dst++ = (char)*src++;

    for (; (c -= 0x10) >= 0; src += 0x10, dst += 0x10)
        Vector.Widen(Unsafe.AsRef<Vector<byte>>(src),
                     out Unsafe.AsRef<Vector<ushort>>(dst + 0),
                     out Unsafe.AsRef<Vector<ushort>>(dst + 8));

    for (c += 0x10; c > 0; c--)
        *dst++ = (char)*src++;
}

To je vlastně všechno, co se na něj! Funguje to jako kouzlo, a jak uvidíte dále, to znamená ‚řvát‘ as-inzeroval .

Ale nejprve vypnutím vs2017 ladicí možnost „Zakázat JIT optimalizace,“ můžeme zkoumat nativní SIMD instrukční tok, že x64 JIT generuje za ‚propuštění‘ stavět na .NET 4.7.2 . Zde je příslušná část hlavní vnitřní smyčce, která výbuchy přes data-32 bajtů najednou. Všimněte si, že JIT se podařilo emitovat teoreticky minimální načtení / store vzor.

L_4223  mov         rax,rbx  
L_4226  movups      xmm0,xmmword ptr [rax] ; fetch 16 bytes
L_4229  mov         rax,rdi  
L_422C  lea         rdx,[rdi+10h]  
L_4230  movaps      xmm2,xmm0  
L_4233  pxor        xmm1,xmm1  
L_4237  punpcklbw   xmm2,xmm1               ; interleave 8-to-16 bits (lo)
L_423B  movups      xmmword ptr [rax],xmm2  ; store 8 bytes (lo) to 8 wide chars (16 bytes)
L_423E  pxor        xmm1,xmm1  
L_4242  punpckhbw   xmm0,xmm1               ; interleave 8-to-16 bits (hi)
L_4246  movups      xmmword ptr [rdx],xmm0  ; store 8 bytes (hi) to 8 wide chars (16 bytes)
L_4249  add         rbx,10h  
L_424D  add         rdi,20h  
L_4251  add         esi,0FFFFFFF0h  
L_4254  test        esi,esi  
L_4256  jge         L_4223  
L_4258  ...

Výsledky testů výkonnosti:
Zkoušel jsem kód SIMD proti čtyři další techniky, které plní stejnou funkci. Pro .NET snímačů jsou uvedeny níže, to bylo volání GetChars(byte[], int, int)metody.

  • naivní implementace C # z nebezpečného bytewise smyčky
  • kódování .NET pro „Windows-1252“ kódové
  • kódování NET na ASCII
  • kódování .NET pro UTF-8 (bez BOM, bez házení)
  • SIMD kód uvedený v tomto článku

Testování součástí shodnou práci pro všechny a ověřování stejných výsledků ze všech jednotek v rámci testu. Testovací bytů byly náhodné a ASCII-only ( [0x01 - 0x7F]) s cílem zajistit shodné výsledky ze všech testovaných jednotek. Velikost vstupního byl náhodný, maximální 1megabajt, s log 2 orientací na menší velikosti tak, že průměrná velikost byla asi 80K.

Pro spravedlnosti, provádění příkazů systematicky otočí o 5 jednotek pro každou iteraci. Pro zahřívání, časování byly vyřazeny a vynulován jednou, na iterační 100. Tento test svazku neprovádí žádné příděly během testovací fáze a plné GC je nucen a očekávaný každý 10000 iterací.

                 Relativní klíšťata normalizované na nejlepší výsledek
                  .NET Framework 4.7.3056.0 x64 (uvolnění)
   iter naivní win-1252 ascii utf-8 SIMD
------- ----------- ------------ ------------ -------- ---- -----------
  10000 | 131,5 294,5 186,2 145,6 100,0
  20000 | 137,7 305,3 191,9 149,4 100,0
  30000 | 139,2 308,5 195,8 151,5 100,0
  40000 | 141,8 312,1 198,5 153,2 100,0
  50000 | 142,0 313,8 199,1 154,1 100,0
  60000 | 140,5 310,6 196,7 153,0 100,0
  70000 | 141,1 312,9 197,3 153,6 100,0
  80000 | 141,6 313,7 197,8 154,1 100,0
  90000 | 141,3 313,7 197,9 154,3 100,0
 100000 | 141,1 313,3 196,9 153,7 100,0

gcServer = FALSE; LatencyMode.Interactive; Vector.IsHardwareAccelerated = True

V přednostním x64 platformy, když je povoleno optimalizace JIT a SIMD je k dispozici, nebylo soutěž. Kód SIMD běží o 150% rychlejší než další uchazeče. Encoding.Default, Což je obvykle „Windows-1252“ codepage, hrál zejména špatně, asi 3x pomalejší než kód SIMD.

Dříve jsem se zmínil, že distribuce velikosti testovaných dat byl silně log předpětím směrem k nule. Bez tohoto kroku - tedy rovnoměrné rozdělení velikostí 0 až 1048576 bajtů (průměrná velikost testovací 512K) - SIMD nadále předběhne balíček se všemi ostatními jednotkami daří relativně horší vs. kódu je uvedeno výše.

naivní 153,45%
win-1252 358,84%
ascii 221,38%
utf-8 161,62%
SIMD 100,00%

Pokud jde o non-SIMD (emulace) případě, UTF-8 a JIMD jsou velmi blízko - obvykle do 3-4% sebe - a mnohem lepší než ostatní. Našel jsem tento výsledek být dvojnásobně překvapivé: že zdrojový kód UTF8Encoding byl tak rychlý, (hodně optimalizace fast-path), a pak také to, že všeobecné použití SIMD emulace kód byl schopen přizpůsobit tomuto účelu laděný kód.




Dodatek:

Ve výše uvedeném kódu, jsem se zmínil možnou O ( n ) snížení výkonu (spojené s nadbytkem re-nulování) z pomocí new String(Char,int)konstruktoru přidělit cílový řetězec. Pro úplnost je zde alternativní vstupní bod, které by mohly vyhnout problému namísto vrácení rozšířená data jako ushort[]:

/// <summary>
/// 'Widen' each byte in 'bytes' to 16-bits with no consideration for
/// character mapping or encoding
/// </summary>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static unsafe char[] WidenByteArray(byte[] bytes)
{
    var rgch = new char[bytes.Length];
    if (rgch.Length > 0)
        fixed (char* dst = rgch)
        fixed (byte* src = bytes)
            widen_bytes_simd(dst, src, rgch.Length);
    return rgch;
}
Odpovězeno 25/07/2018 v 07:17
zdroj uživatelem

hlasů
1

S žádnou stopu na vašich požadavků souběžnosti (nebo cokoliv jiného): Můžete plodit nějaké závity na ThreadPool které převádějí řetězce bytovými poli a vložte je do fronty, a ještě jednu nit sledování fronty a odesílá data?

Odpovězeno 27/08/2009 v 01:44
zdroj uživatelem

hlasů
1

Co se snažíte optimalizovat? PROCESOR? Bandwidth?

Pokud jste optimalizovat šířku pásma, můžete zkusit kompresi dat řetězec předem.

Za prvé, profil kód, zjistit, jaké jsou pomalé bity, než se pokusíte optimalizovat na tak nízké úrovni.

Odpovězeno 26/08/2009 v 23:40
zdroj uživatelem

hlasů
1

Dovedu si představit, GetBytes () funkce je již dobře optimalizované pro toto. Já si nemyslím, že jakékoli návrhy na zlepšení rychlosti vašeho stávajícího kódu.

EDIT - Víte, já nevím, jestli je to rychlejší, nebo ne. Ale tady je to jiný způsob použití BinaryFormatter:

BinaryFormatter bf = new BinaryFormatter();
MemoryStream ms = new MemoryStream();
bf.Serialize(ms, someString);
byte[] bytes =  ms.ToArray();
ms.Close();
socket.Send(bytes);

Důvod, proč jsem si, že to mohlo být rychlejší, je to, že přeskočí krok kódování. Jsem také není zcela jistý, že to bude fungovat správně. Ale můžete ji zkusit a uvidíme. Samozřejmě, pokud budete potřebovat kódování ASCII pak to nepomůže.

Měl jsem jen další myšlenku. Věřím, že tento kód vrátí dvojnásobný počet bajtů než při použití GetBytes s kódováním ASCII. Důvodem je, že všechny řetězce v .NET použít unicode v zákulisí. A samozřejmě Unicode používá 2 bajty na znak, zatímco ASCII používá jen 1. Takže BinaryFormatter pravděpodobně není věc pro použití v tomto případě, protože byste se zdvojnásobí množství dat, které posíláte přes zásuvky.

Odpovězeno 26/08/2009 v 23:34
zdroj uživatelem

hlasů
0

Jen další tip: Nevím, jak si vytvořit své počáteční Strings, ale pamatujte, že StringBuilder.Append ( „co“) je ve skutečnosti rychlejší, než něco jako MyString + = „něco“.

V celém procesu vytváření řetězců a jejich odeslání prostřednictvím připojení k zásuvce, byl bych surprized pokud překážkou byla přeměna Struny do bajt matice. Ale já jsem velmi zajímá, jestli by někdo tento test s Profiler.

Ben

Odpovězeno 08/10/2009 v 12:47
zdroj uživatelem

hlasů
0

Já bych navrhnout profilování, co děláte. Považuji za nepravděpodobné, že rychlost přeměny řetězec bajtové pole je větší problém propustnosti než rychlost samotné zásuvky.

Odpovězeno 27/08/2009 v 02:48
zdroj uživatelem

hlasů
0

Jak již bylo řečeno, třída kódování je již optimalizován pro daný úkol, takže to bude asi těžké, aby to rychleji. Je tu ještě jedna micro-optimalizace, které byste mohli udělat: Použití Encoding.ASCIInamísto new ASCIIEncoding(). Ale jak každý ví, micro-optimalizace jsou špatné)

Odpovězeno 26/08/2009 v 23:41
zdroj uživatelem

Cookies help us deliver our services. By using our services, you agree to our use of cookies. Learn more