BitShift-Operatoren

    • VB.NET

    Es gibt 2 Antworten in diesem Thema. Der letzte Beitrag () ist von FAtheone.

      BitShift-Operatoren

      Hallo Com,

      Seit .NET FW 1.1 gibt es die sog. BitShift-Operatoren
      << (-> LShift od. Links-shift) und
      >> (-> RShift od. Rechts-shift)

      Diese machen Mathematisch gesehen:

      VB.NET-Quellcode

      1. x << b = x * 2^b
      2. x >> b = Math.Floor(x / 2^b)

      ACHTUNG: sie "kegeln" die anderen Bits einfach raus und schreiben am "Anfang" oben rein:

      1001 >> 3 = 0001 (1001 << 3 = 1000)

      Folgender Stub sollte die Funktionsweise eläutern (gibt in der Konsole ein paar Operationen aus):

      VB.NET-Quellcode

      1. Module Module1
      2. Sub Main()
      3. Dim b As UInteger = 1 '= 2^0
      4. For i = 0 To 10
      5. Console.WriteLine("1 << " & i & ": " & (b << i))
      6. Next
      7. b = 65536 '= 2^16 = 0x010000 = 0b1:0000:0000:0000:0000 (Hier sieht man besonders schön, was der maximalwert für einen Int16 (Short) ist: 2^16 - 1
      8. For i = 0 To 16
      9. Console.WriteLine("65536 >> " & i & ": " & (b >> i))
      10. Next
      11. Console.ReadLine()
      12. End Sub
      13. End Module


      Daraus ergibt sich ein sehr schneller Bitflag-Code:

      VB.NET-Quellcode

      1. Public Shared Function BitFlag(flags As UInt32, offset As Integer) As Boolean
      2. 'offset muss zwischen 0 und 31 liegen, da 2^32 > UInt32.MaxValue
      3. 'dementsprechend reicht ein Byte aus.
      4. 'Da VB aber standardmäßig mit Integer (bzw. Long bei x64) rechnet, wäre dann mehr (langsamere) Konveriterung erforderlich
      5. If offset < 0 OrElse offset > 31 Then Throw New ArgumentOutOfRangeException("offset", "Es sind nur Werte zwischen 0 und 31 (einschliesslich) erlaubt")
      6. Return (((flags >> offset) Mod 2) = 1)
      7. End Function



      Die Modulation mit 2 = 0010 sorgt dafür, dass wir den Stellenwert des LSB bekommen (wo wir vorher das gesuchte Bit hingeshiftet haben).
      Vergleich mit 1 gibt demnach den Boolschen Wert des Bits zurück - ein sehr schneller Einzeiler, der es in sich hat :)

      Alternativ dazu kann man auch mit dem And-Operator arbeiten und den offset "hochshiften":

      VB.NET-Quellcode

      1. Public Shared Function Bitflag(flags As UInt32, offset As Integer) As Boolean
      2. If offset < 0 OrElse offset > 31 Then Throw New ArgumentOutOfRangeException("offset", "Der Wert muss zwischen 0 und 31 (einschliesslich) liegen")
      3. Return ((flags And 1 << offset) = 1 << offset)
      4. End Function

      Beziehungsweise, wenn man weis, dass (0) = False und (1 << offset) = True gilt:
      //EDIT:
      Wie vermutet wird in dem Fall bei VB implizit konvertiert, also bleibt noch 1 << offset > 0 (für alle offset im zugelassenen bereich) also abschätzung.

      VB.NET-Quellcode

      1. Public Shared Function Bitflag(flags As UInt32, offset As Integer) As Boolean
      2. If offset < 0 OrElse offset > 31 Then Throw New ArgumentOutOfRangeException("offset", "Der Wert muss zwischen 0 und 31 (einschliesslich) liegen")
      3. Return ((flags And 1 << offset) > 0)
      4. End Function


      Was schneller ist, werde ich nun überprüfen.
      //EDIT:
      Ich werde 2 KiB 40 MB (40.000.000 Bytes) UInt32er zufällig befüllen und von jeder Funktion jedes Bitflag auslesen lassen. Ergebnisse später.
      Dieser Vorgang, 5x Wiederholt ergibt

      //EDIT2:
      Ich habe nun also
      500 Iterationen mit
      10000000 UIntgers Testdaten (Eigene für jede Iteration)
      gemacht.
      Hier die Durchschnittswerte (in Ticks / Loop = Ticks / UInteger (jedes Bit)):
      BitFlag1: 32261,858
      BitFlag2: 23841,384
      BitFlag3: 25041,44

      Demnach ist die schnellste Operation (flags And 1 << offset) = 1 << offset :D
      Hier die Testklasse: clsBitFlagBM.vb
      Wenn ihr in der Main den Initialisierungswert von

      VB.NET-Quellcode

      1. Dim total As Integer = 500
      2. Dim mem As Integer = 10000000

      ändert, könnt ihr die Testbreite variieren.
      Was bekommt ihr heraus?

      Man beachte, dass Bitshift eigentlich nur bei Unsigned werten Sinn macht (Math. gesehen), aber da ein Integer einfach nur als MSB das vorzeichen hat, wäre das Vorzeichen als offset 31 auslesbar.

      Vielleicht hilft dieser Tipp den Fortgeschrittenen und Profis hier ein wenig weiter; wenn es um die ultimative Performance und eleganten Code geht.

      Rückfragen beantworte ich gerne, nur zu. Auch Kritik ist erwünscht.

      Dieser Beitrag wurde bereits 6 mal editiert, zuletzt von „FAtheone“ () aus folgendem Grund: Benchmarkergebnisse

      Tja, da muss man sich nicht wundern, dass Mathematiker immer als Paria-Kaste betrachtet werden :rolleyes:

      Der normale Abruf von <Flags>Enumerationen geht ja nicht von ungefähr über "Wert AND &Hex", was schneller und nach meiner persönlichen Meinung nach auch intuitiver ist.

      Allerdings hast Du recht, dass BitShift Operatoren immer dann sehr elegant und schnell sind, falls es um Multiplikationen / Divisionen mit der Basis 2 geht.
      Eigentlich ist der Bitshift unter den High-End-Programmen verbreiteter, weil er schlichtweg schneller ist.
      Die schnellste lese-variante (in C) ist:

      Quellcode

      1. return (flags && (1 << offset));

      Allerdings nur, weil false = 0, true > 0 gilt.
      Eigentlich ist es also "unsauber".
      Das ist in C zwar egal, weil das kompilierte Boolean eh ein int wird, aber in VB.NET eben nicht.


      *so*
      Benchmark fertig.
      @Kangaroo:

      Mach ihn doch mal bei dir, um zu sehen, ob sich die Ergebnisse erneut zeigen.

      Edit by ~blaze~:
      *Beiträge zusammengefügt*

      Dieser Beitrag wurde bereits 1 mal editiert, zuletzt von „~blaze~“ ()