Der schnellste Weg, um eine Maske mit n Einsen ab Position i . zu erstellen

Lesezeit: 1 Minute

Der schnellste Weg um eine Maske mit n Einsen ab
Vincent

Was ist der schnellste Weg (in Bezug auf CPU-Zyklen auf gängiger moderner Architektur), um eine Maske mit len Bits auf 1 gesetzt ab Position pos:

template <class UIntType>
constexpr T make_mask(std::size_t pos, std::size_t len)
{
    // Body of the function
}

// Call of the function
auto mask = make_mask<uint32_t>(4, 10);
// mask = 00000000 00000000 00111111 11110000 
// (in binary with MSB on the left and LSB on the right)

Gibt es außerdem irgendwelche Compiler-Intrinsics oder? BMI Funktion, die helfen kann?

  • Muss dies den Fall abdecken, in dem len ist das gleiche wie die Anzahl der Bits des Typs? Das fügt zusätzliche Komplikationen hinzu

    – Harold

    4. September ’16 um 21:23

  • Es wäre besser, wenn die Funktion für funktionierte len >= Anzahl der Bits von UIntType (in diesem Fall alle Bits nach pos sind eingestellt auf 1)

    – Vincent

    4. September ’16 um 21:32

  • Wenn len gleich oder größer als die Anzahl der Bits dieses Typs ist, funktioniert es immer noch, wenn Sie (1<

    – Oldtimer

    4. September ’16 um 21:54

  • @dwelch: wenn int ist 32bit, 1U<<32 ist undefiniert, nicht 0. Siehe zum Beispiel coliru.stacked-crooked.com/a/ae28ba4070188ace . Also, wenn Sie liefern len als 32, mit diesem speziellen Ausdruck von UB, (1<<len)-1 hat alle Bits 0, nicht alle Bits 1.

    – ric

    4. September ’16 um 22:47


  • @rici: verwandt: ein Rotations-Idiom, das UB vermeidet. val << count auf der meisten Hardware sättigt entweder die Zählung oder nimmt sie modulo der Operandenbreite (dh betrachtet nur die unteren 5 Bits für eine 32-Bit-Verschiebung). Deshalb erhalten Sie 0: 1<<32 zur Laufzeit ist 1 in Hardware auf x86. Das Kompilierzeit-Verhalten kann sich unterscheiden, da dies ein ziemlich falsches, undefiniertes Verhalten ist.

    – Peter Cordes

    5. Sep. ’16 um 10:48

Der schnellste Weg um eine Maske mit n Einsen ab
KIIV

Schnellste Weg? Ich würde so etwas verwenden:

template <class T>
constexpr T make_mask(std::size_t pos, std::size_t len)
{
  return ((static_cast<T>(1) << len)-1) << pos;
}

  • Kleiner Vorschlag: ändern (T)1 zu static_cast<T>(1) macht die Klammern etwas weniger LISP-ähnlich und könnte leichter zu lesen sein. (Persönlich würde ich den static_cast verwenden, aber der C-Style Cast ist auch hier in Ordnung).

    – Peter Becker

    4. Sept. 16 um 21:36 Uhr

  • Ich habe das auf die Godbolt-Compiler-Explorer. Diese Antwort wird zu wirklich effizientem Code kompiliert. sieht besser aus als mit anzufangen -1LL und das verschieben. BMI2 shlx Anleitung macht dies wirklich effizient (da es viel schneller ist als normal shl auf Intel Haswell/BDW/Skylake, obwohl sogar normale Variablenanzahl shl r32, cl beträgt nur 2 Zyklen Latenz (und 3 uops)) (Siehe stackoverflow.com/tags/x86/info, und agner.org/optimize)

    – Peter Cordes

    5. Sep. ’16 um 11:08


  • Wenn len kann 32 (oder was auch immer die Schriftbreite) sein, aber nicht 0, dann sollten Sie mit beginnen static_cast<T>(-1LL) und rechts+links verschieben. Wenn len kann 0 sein, aber nicht 32, diese Antwort ist ideal. Wenn len kann 0 oder 32 sein, und Sie brauchen beides, um zu funktionieren, Sie brauchen etwas ausgefalleneres als jede dieser Lösungen. 🙁 Jean-Baptistes Nachschlagetabelle könnte funktionieren, wenn du dir sicher bist len braucht keinen Reichweiten-Check. (Es benötigt LUT 33 Einträge, von 0 bis 0xFFFFFFFF)

    – Peter Cordes

    5. Sep. ’16 um 11:17

1641856104 527 Der schnellste Weg um eine Maske mit n Einsen ab
ric

Wenn durch “ab pos“, meinst du, dass sich das niedrigste Bit der Maske an der Position befindet, die 2 . entsprichtPos (wie in deinem Beispiel):

((UIntType(1) << len) - UIntType(1)) << pos

Wenn das möglich ist len ist ≥ die Anzahl der Bits in UIntType, vermeiden Sie undefiniertes Verhalten mit einem Test:

(((len < std::numeric_limits<UIntType>::digits)
     ? UIntType(1)<<len
     : 0) - UIntType(1)) << pos

(Falls das auch möglich ist pos ist std::numeric_limits<UIntType>::digits, benötigen Sie einen weiteren ternären Op-Test.)

Sie könnten auch verwenden:

(UIntType(1)<<(len>>1)<<((len+1)>>1) - UIntType(1)) << pos

wodurch der ternäre Betrieb auf Kosten von drei zusätzlichen Schichtführern vermieden wird; Ich bezweifle, dass es schneller wäre, aber ein sorgfältiges Benchmarking wäre erforderlich, um es sicher zu wissen.

1641856104 889 Der schnellste Weg um eine Maske mit n Einsen ab
Jean-Baptiste Yunès

Vielleicht mit einer Tabelle? Für Typ uint32_t Du kannst schreiben:

static uint32_t masks[] = { 0x0, 0x1, 0x3, 0x7, 0xf, 0x1f, 0x3f...}; // only 32 such masks
return masks[len] << pos;

Was auch immer der int-Typ ist, die Anzahl der Masken ist nicht so groß und die Tabelle kann leicht durch Vorlagen generiert werden.

Für BMI, vielleicht mit BZHI? Ausgehend von allen Bits gesetzt, BZHI mit Wert 32-len und dann um pos verschieben.

  • +1. BZHI ist wahrscheinlich am schnellsten, da Sie keinen Speicherzugriff auf Ihre Tabelle benötigen, aber wenn dies in einer engen Schleife geschieht (und wenn nicht, warum optimieren?), ist der Tabellenzugriff wahrscheinlich fast genauso gut .

    – Periata Breatta

    5. September ’16 um 2:30

  • Sogar eine vollständige zweidimensionale Tabelle mit (pos, len) Indizes ist denkbar, 64² = 4096 Einträge (von denen die Hälfte nutzlos ist, es sei denn, Sie möchten mit Dreiecksindexierung spielen).

    – Yves Daoust

    5. September ’16 um 7:53

  • @YvesDaoust: das klingt nach einer schrecklichen Idee. Es ist unwahrscheinlich, dass es in L1 heiß bleibt. Selbst wenn Sie einen Teil davon mit einer Tabellensuche durchführen, klingt es schwierig, es sei denn, Ihr Code hat so viel Parallelität auf Befehlsebene, dass weniger Uops auf Kosten einer höheren Latenz wertvoll sind. Die Latenzzeit bei L1-Lastnutzung beträgt bei neueren Intel-CPUs ~4 Zyklen, aber ich denke, die Funktion von KIIV könnte dies haben (1<<len) - 1 in weniger Zyklen berechnet, und sogar die 2. Schicht. (Beyogen auf Insn-Tische von Agner Fog.)

    – Peter Cordes

    5. September ’16 um 8:12


  • @PeterCordes: Ich weiß, aber die Frage betrifft feste len/pos-Werte, sodass Sie davon ausgehen können, dass derselbe Wert immer und immer wieder verwendet wird. Wie auch immer, all diese Diskussion ist unnötig, siehe meine Antwort.

    – Yves Daoust

    5. Sep. ’16 um 8:17


  • @YvesDaoust: Ja, ich habe deine Antwort gesehen und sie positiv bewertet. Ich gehe davon aus, dass die anderen Antworten für den Fall nützlich sind, in dem len / pos keine Kompilierzeitkonstanten sind, vorausgesetzt, das OP hat nur Konstanten verwendet, um das Beispiel zu vereinfachen.

    – Peter Cordes

    5. Sep. ’16 um 8:26

1641856104 940 Der schnellste Weg um eine Maske mit n Einsen ab
Yves Daoust

Die Geschwindigkeit ist hier irrelevant, da der Ausdruck konstant ist, daher vom Optimierer vorberechnet und aller Wahrscheinlichkeit nach als unmittelbarer Operand verwendet wird. Was auch immer Sie verwenden, es kostet Sie 0 Zyklus.

1641856104 113 Der schnellste Weg um eine Maske mit n Einsen ab
Peter Cordes

Das größte Problem hierbei ist die Bandbreite der möglichen Eingaben. In C, Verschiebungen mit einer Anzahl größer als die Schriftbreite sind undefiniertes Verhalten. Allerdings sieht es so aus len kann sinnvollerweise von 0 bis zur Schriftbreite reichen. zB 33 verschiedene Längen für uint32_t. Mit pos=0 erhalten wir Masken von 0 bis 0xFFFFFFFF. (Ich gehe aus Gründen der Übersichtlichkeit nur von 32-Bit in Englisch und ASM aus, verwende aber generisches C++).

Wenn wir beide Enden dieses Bereichs als mögliche Eingaben ausschließen können, gibt es nur 32 mögliche Längen, und wir können eine Links- oder Rechtsverschiebung als Baustein verwenden. (Benutze ein assert() um den Eingabebereich in Debug-Builds zu überprüfen.)


Ich habe mehrere Versionen (aus anderen Antworten) der Funktion eingefügt
im Godbolt-Compiler-Explorer mit einigen Makros, um sie mit konstanter Länge, konstanter Pos oder beiden Eingangsvariablen zu kompilieren
. Manche machen es besser als andere. KIIVs sieht gut aus für den Bereich, für den sie gültig ist (len=0..31, pos=0..31).

Diese Version funktioniert für len=1..32 und pos=0..31. Es erzeugt etwas schlechteres x86-64-ASM als KIIVs, also verwenden Sie KIIVs, wenn es ohne zusätzliche Überprüfungen funktioniert.

// right-shift a register of all-ones, then shift it into position.
// works for len=1..32 and pos=0..31
template <class T>
constexpr T make_mask_PJC(std::size_t pos, std::size_t len)
{
//  T all_ones = -1LL;
//  unsigned typebits = sizeof(T)*CHAR_BIT;  // std::numeric_limits<T>::digits
//  T len_ones = all_ones >> (typebits - len);
//  return len_ones << pos

  static_assert(std::numeric_limits<T>::radix == 2, "T isn't an integer type");
  return static_cast<T>(-1LL) >> (std::numeric_limits<T>::digits - len) << pos;  // pre-C++14 constexpr needs it all in one statement
}

// Same idea, but mask the shift count the same way x86 shift instructions do, so the compiler can do it for free.
// Doesn't always compile to ideal code with SHRX (BMI2), maybe gcc only knows about letting the shift instruction do the masking for the older SHR / SHL instructions
uint32_t make_mask_PJC_noUB(std::size_t pos, std::size_t len)
{
  using T=uint32_t;

  static_assert(std::numeric_limits<T>::radix == 2, "T isn't an integer type");

  T all_ones = -1LL;
  unsigned typebits = std::numeric_limits<T>::digits;
  T len_ones = all_ones >> ( (typebits - len) & (typebits-1));     // the AND optimizes away
  return len_ones << (pos & (typebits-1));

//  return static_cast<T>(-1LL) >> (std::numeric_limits<T>::digits - len) << pos;  // pre-C++14 constexpr needs it all in one statement
}

Wenn len alles sein kann in [0..32], habe ich keine großartigen Ideen für effizienten verzweigtlosen Code. Vielleicht ist die Verzweigung der richtige Weg.

uint32_t make_mask_fullrange(std::size_t pos, std::size_t len)
{
  using T=uint32_t;

  static_assert(std::numeric_limits<T>::radix == 2, "T isn't an integer type");

  T all_ones = -1LL;
  unsigned typebits = std::numeric_limits<T>::digits;
  //T len_ones = all_ones >> ( (typebits - len) & (typebits-1));
  T len_ones = len==0 ? 0 : all_ones >> ( (typebits - len) & (typebits-1));
  return len_ones << (pos & (typebits-1));

//  return static_cast<T>(-1LL) >> (std::numeric_limits<T>::digits - len) << pos;  // pre-C++14 constexpr needs it all in one statement
}

.

342460cookie-checkDer schnellste Weg, um eine Maske mit n Einsen ab Position i . zu erstellen

This website is using cookies to improve the user-friendliness. You agree by using the website further.

Privacy policy