C'de Bit Ters Çevirme için Etkin Algoritma (MSB-> LSB'den LSB-> MSB'ye)


243

Aşağıdakileri başarmak için en etkili algoritma nedir:

0010 0000 => 0000 0100

Dönüşüm MSB-> LSB'den LSB-> MSB'ye yapılır. Tüm bitler ters çevrilmelidir; yani bu endianness takası değildir .


1
Bence uygun isim bitsel bir işlem.
Kredns

5
Sanırım dönüşü değil, tersini kastetmiştiniz.
Juliano

2
Çoğu ARM işlemcisinin bunun için yerleşik bir işlemi vardır. ARM Cortex-M0 bilmiyor ve bitleri değiştirmek için bir bayt başına tablo kullanmanın en hızlı yaklaşım olduğunu gördüm.
starblue

2
Ayrıca Sean Eron Anderson'ın Bit Twiddling Hacks'ına bakın .
jww

2
Lütfen "en iyi" yi tanımlayın
Lee Taylor

Yanıtlar:


497

NOT : Aşağıdaki tüm algoritmalar C cinsindendir, ancak seçtiğiniz dil için taşınabilir olmalıdır (bu kadar hızlı olmadıklarında bana bakmayın :)

Seçenekler

Düşük Bellek (32 bit int, 32 bit makine) ( buradan ):

unsigned int
reverse(register unsigned int x)
{
    x = (((x & 0xaaaaaaaa) >> 1) | ((x & 0x55555555) << 1));
    x = (((x & 0xcccccccc) >> 2) | ((x & 0x33333333) << 2));
    x = (((x & 0xf0f0f0f0) >> 4) | ((x & 0x0f0f0f0f) << 4));
    x = (((x & 0xff00ff00) >> 8) | ((x & 0x00ff00ff) << 8));
    return((x >> 16) | (x << 16));

}

Ünlü Bit Twiddling Hacks sayfasından :

En hızlı (arama tablosu) :

static const unsigned char BitReverseTable256[] = 
{
  0x00, 0x80, 0x40, 0xC0, 0x20, 0xA0, 0x60, 0xE0, 0x10, 0x90, 0x50, 0xD0, 0x30, 0xB0, 0x70, 0xF0, 
  0x08, 0x88, 0x48, 0xC8, 0x28, 0xA8, 0x68, 0xE8, 0x18, 0x98, 0x58, 0xD8, 0x38, 0xB8, 0x78, 0xF8, 
  0x04, 0x84, 0x44, 0xC4, 0x24, 0xA4, 0x64, 0xE4, 0x14, 0x94, 0x54, 0xD4, 0x34, 0xB4, 0x74, 0xF4, 
  0x0C, 0x8C, 0x4C, 0xCC, 0x2C, 0xAC, 0x6C, 0xEC, 0x1C, 0x9C, 0x5C, 0xDC, 0x3C, 0xBC, 0x7C, 0xFC, 
  0x02, 0x82, 0x42, 0xC2, 0x22, 0xA2, 0x62, 0xE2, 0x12, 0x92, 0x52, 0xD2, 0x32, 0xB2, 0x72, 0xF2, 
  0x0A, 0x8A, 0x4A, 0xCA, 0x2A, 0xAA, 0x6A, 0xEA, 0x1A, 0x9A, 0x5A, 0xDA, 0x3A, 0xBA, 0x7A, 0xFA,
  0x06, 0x86, 0x46, 0xC6, 0x26, 0xA6, 0x66, 0xE6, 0x16, 0x96, 0x56, 0xD6, 0x36, 0xB6, 0x76, 0xF6, 
  0x0E, 0x8E, 0x4E, 0xCE, 0x2E, 0xAE, 0x6E, 0xEE, 0x1E, 0x9E, 0x5E, 0xDE, 0x3E, 0xBE, 0x7E, 0xFE,
  0x01, 0x81, 0x41, 0xC1, 0x21, 0xA1, 0x61, 0xE1, 0x11, 0x91, 0x51, 0xD1, 0x31, 0xB1, 0x71, 0xF1,
  0x09, 0x89, 0x49, 0xC9, 0x29, 0xA9, 0x69, 0xE9, 0x19, 0x99, 0x59, 0xD9, 0x39, 0xB9, 0x79, 0xF9, 
  0x05, 0x85, 0x45, 0xC5, 0x25, 0xA5, 0x65, 0xE5, 0x15, 0x95, 0x55, 0xD5, 0x35, 0xB5, 0x75, 0xF5,
  0x0D, 0x8D, 0x4D, 0xCD, 0x2D, 0xAD, 0x6D, 0xED, 0x1D, 0x9D, 0x5D, 0xDD, 0x3D, 0xBD, 0x7D, 0xFD,
  0x03, 0x83, 0x43, 0xC3, 0x23, 0xA3, 0x63, 0xE3, 0x13, 0x93, 0x53, 0xD3, 0x33, 0xB3, 0x73, 0xF3, 
  0x0B, 0x8B, 0x4B, 0xCB, 0x2B, 0xAB, 0x6B, 0xEB, 0x1B, 0x9B, 0x5B, 0xDB, 0x3B, 0xBB, 0x7B, 0xFB,
  0x07, 0x87, 0x47, 0xC7, 0x27, 0xA7, 0x67, 0xE7, 0x17, 0x97, 0x57, 0xD7, 0x37, 0xB7, 0x77, 0xF7, 
  0x0F, 0x8F, 0x4F, 0xCF, 0x2F, 0xAF, 0x6F, 0xEF, 0x1F, 0x9F, 0x5F, 0xDF, 0x3F, 0xBF, 0x7F, 0xFF
};

unsigned int v; // reverse 32-bit value, 8 bits at time
unsigned int c; // c will get v reversed

// Option 1:
c = (BitReverseTable256[v & 0xff] << 24) | 
    (BitReverseTable256[(v >> 8) & 0xff] << 16) | 
    (BitReverseTable256[(v >> 16) & 0xff] << 8) |
    (BitReverseTable256[(v >> 24) & 0xff]);

// Option 2:
unsigned char * p = (unsigned char *) &v;
unsigned char * q = (unsigned char *) &c;
q[3] = BitReverseTable256[p[0]]; 
q[2] = BitReverseTable256[p[1]]; 
q[1] = BitReverseTable256[p[2]]; 
q[0] = BitReverseTable256[p[3]];

Bu fikri 64 bit ints'ye genişletebilir veya hız için bellek kapatabilirsiniz (L1 Veri Önbelleğinizin yeterince büyük olduğu varsayılarak) ve 64K girişli arama tablosuyla bir seferde 16 bit tersine çevirebilirsiniz.


Diğerleri

Basit

unsigned int v;     // input bits to be reversed
unsigned int r = v & 1; // r will be reversed bits of v; first get LSB of v
int s = sizeof(v) * CHAR_BIT - 1; // extra shift needed at end

for (v >>= 1; v; v >>= 1)
{   
  r <<= 1;
  r |= v & 1;
  s--;
}
r <<= s; // shift when v's highest bits are zero

Daha hızlı (32 bit işlemci)

unsigned char b = x;
b = ((b * 0x0802LU & 0x22110LU) | (b * 0x8020LU & 0x88440LU)) * 0x10101LU >> 16; 

Daha hızlı (64 bit işlemci)

unsigned char b; // reverse this (8-bit) byte
b = (b * 0x0202020202ULL & 0x010884422010ULL) % 1023;

Bunu 32 bit üzerinde yapmak istiyorsanız int, her bayttaki bitleri tersine çevirin ve baytların sırasını tersine çevirin. Yani:

unsigned int toReverse;
unsigned int reversed;
unsigned char inByte0 = (toReverse & 0xFF);
unsigned char inByte1 = (toReverse & 0xFF00) >> 8;
unsigned char inByte2 = (toReverse & 0xFF0000) >> 16;
unsigned char inByte3 = (toReverse & 0xFF000000) >> 24;
reversed = (reverseBits(inByte0) << 24) | (reverseBits(inByte1) << 16) | (reverseBits(inByte2) << 8) | (reverseBits(inByte3);

Sonuçlar

En umut verici iki çözümü, arama tablosunu ve bitsel-AND'i (ilk çözüm) karşılaştırdım. Test makinesi, 4GB DDR2-800'lü bir dizüstü bilgisayar ve bir Core 2 Duo T7500 @ 2.4GHz, 4MB L2 Önbellek; YMMV. 64 bit Linux'ta gcc 4.3.2 kullandım . OpenMP (ve GCC bağlamaları) yüksek çözünürlüklü zamanlayıcılar için kullanıldı.

reverse.c

#include <stdlib.h>
#include <stdio.h>
#include <omp.h>

unsigned int
reverse(register unsigned int x)
{
    x = (((x & 0xaaaaaaaa) >> 1) | ((x & 0x55555555) << 1));
    x = (((x & 0xcccccccc) >> 2) | ((x & 0x33333333) << 2));
    x = (((x & 0xf0f0f0f0) >> 4) | ((x & 0x0f0f0f0f) << 4));
    x = (((x & 0xff00ff00) >> 8) | ((x & 0x00ff00ff) << 8));
    return((x >> 16) | (x << 16));

}

int main()
{
    unsigned int *ints = malloc(100000000*sizeof(unsigned int));
    unsigned int *ints2 = malloc(100000000*sizeof(unsigned int));
    for(unsigned int i = 0; i < 100000000; i++)
      ints[i] = rand();

    unsigned int *inptr = ints;
    unsigned int *outptr = ints2;
    unsigned int *endptr = ints + 100000000;
    // Starting the time measurement
    double start = omp_get_wtime();
    // Computations to be measured
    while(inptr != endptr)
    {
      (*outptr) = reverse(*inptr);
      inptr++;
      outptr++;
    }
    // Measuring the elapsed time
    double end = omp_get_wtime();
    // Time calculation (in seconds)
    printf("Time: %f seconds\n", end-start);

    free(ints);
    free(ints2);

    return 0;
}

reverse_lookup.c

#include <stdlib.h>
#include <stdio.h>
#include <omp.h>

static const unsigned char BitReverseTable256[] = 
{
  0x00, 0x80, 0x40, 0xC0, 0x20, 0xA0, 0x60, 0xE0, 0x10, 0x90, 0x50, 0xD0, 0x30, 0xB0, 0x70, 0xF0, 
  0x08, 0x88, 0x48, 0xC8, 0x28, 0xA8, 0x68, 0xE8, 0x18, 0x98, 0x58, 0xD8, 0x38, 0xB8, 0x78, 0xF8, 
  0x04, 0x84, 0x44, 0xC4, 0x24, 0xA4, 0x64, 0xE4, 0x14, 0x94, 0x54, 0xD4, 0x34, 0xB4, 0x74, 0xF4, 
  0x0C, 0x8C, 0x4C, 0xCC, 0x2C, 0xAC, 0x6C, 0xEC, 0x1C, 0x9C, 0x5C, 0xDC, 0x3C, 0xBC, 0x7C, 0xFC, 
  0x02, 0x82, 0x42, 0xC2, 0x22, 0xA2, 0x62, 0xE2, 0x12, 0x92, 0x52, 0xD2, 0x32, 0xB2, 0x72, 0xF2, 
  0x0A, 0x8A, 0x4A, 0xCA, 0x2A, 0xAA, 0x6A, 0xEA, 0x1A, 0x9A, 0x5A, 0xDA, 0x3A, 0xBA, 0x7A, 0xFA,
  0x06, 0x86, 0x46, 0xC6, 0x26, 0xA6, 0x66, 0xE6, 0x16, 0x96, 0x56, 0xD6, 0x36, 0xB6, 0x76, 0xF6, 
  0x0E, 0x8E, 0x4E, 0xCE, 0x2E, 0xAE, 0x6E, 0xEE, 0x1E, 0x9E, 0x5E, 0xDE, 0x3E, 0xBE, 0x7E, 0xFE,
  0x01, 0x81, 0x41, 0xC1, 0x21, 0xA1, 0x61, 0xE1, 0x11, 0x91, 0x51, 0xD1, 0x31, 0xB1, 0x71, 0xF1,
  0x09, 0x89, 0x49, 0xC9, 0x29, 0xA9, 0x69, 0xE9, 0x19, 0x99, 0x59, 0xD9, 0x39, 0xB9, 0x79, 0xF9, 
  0x05, 0x85, 0x45, 0xC5, 0x25, 0xA5, 0x65, 0xE5, 0x15, 0x95, 0x55, 0xD5, 0x35, 0xB5, 0x75, 0xF5,
  0x0D, 0x8D, 0x4D, 0xCD, 0x2D, 0xAD, 0x6D, 0xED, 0x1D, 0x9D, 0x5D, 0xDD, 0x3D, 0xBD, 0x7D, 0xFD,
  0x03, 0x83, 0x43, 0xC3, 0x23, 0xA3, 0x63, 0xE3, 0x13, 0x93, 0x53, 0xD3, 0x33, 0xB3, 0x73, 0xF3, 
  0x0B, 0x8B, 0x4B, 0xCB, 0x2B, 0xAB, 0x6B, 0xEB, 0x1B, 0x9B, 0x5B, 0xDB, 0x3B, 0xBB, 0x7B, 0xFB,
  0x07, 0x87, 0x47, 0xC7, 0x27, 0xA7, 0x67, 0xE7, 0x17, 0x97, 0x57, 0xD7, 0x37, 0xB7, 0x77, 0xF7, 
  0x0F, 0x8F, 0x4F, 0xCF, 0x2F, 0xAF, 0x6F, 0xEF, 0x1F, 0x9F, 0x5F, 0xDF, 0x3F, 0xBF, 0x7F, 0xFF
};

int main()
{
    unsigned int *ints = malloc(100000000*sizeof(unsigned int));
    unsigned int *ints2 = malloc(100000000*sizeof(unsigned int));
    for(unsigned int i = 0; i < 100000000; i++)
      ints[i] = rand();

    unsigned int *inptr = ints;
    unsigned int *outptr = ints2;
    unsigned int *endptr = ints + 100000000;
    // Starting the time measurement
    double start = omp_get_wtime();
    // Computations to be measured
    while(inptr != endptr)
    {
    unsigned int in = *inptr;  

    // Option 1:
    //*outptr = (BitReverseTable256[in & 0xff] << 24) | 
    //    (BitReverseTable256[(in >> 8) & 0xff] << 16) | 
    //    (BitReverseTable256[(in >> 16) & 0xff] << 8) |
    //    (BitReverseTable256[(in >> 24) & 0xff]);

    // Option 2:
    unsigned char * p = (unsigned char *) &(*inptr);
    unsigned char * q = (unsigned char *) &(*outptr);
    q[3] = BitReverseTable256[p[0]]; 
    q[2] = BitReverseTable256[p[1]]; 
    q[1] = BitReverseTable256[p[2]]; 
    q[0] = BitReverseTable256[p[3]];

      inptr++;
      outptr++;
    }
    // Measuring the elapsed time
    double end = omp_get_wtime();
    // Time calculation (in seconds)
    printf("Time: %f seconds\n", end-start);

    free(ints);
    free(ints2);

    return 0;
}

Her iki yaklaşımı da birkaç farklı optimizasyonda denedim, her seviyede 3 deneme yaptım ve her deneme 100 milyon rastgele tersine döndü unsigned ints. Arama tablosu seçeneği için, bitsel kesmek sayfasında verilen her iki şemayı (seçenek 1 ve 2) denedim. Sonuçlar aşağıda gösterilmiştir.

Bitsel VE

mrj10@mjlap:~/code$ gcc -fopenmp -std=c99 -o reverse reverse.c
mrj10@mjlap:~/code$ ./reverse
Time: 2.000593 seconds
mrj10@mjlap:~/code$ ./reverse
Time: 1.938893 seconds
mrj10@mjlap:~/code$ ./reverse
Time: 1.936365 seconds
mrj10@mjlap:~/code$ gcc -fopenmp -std=c99 -O2 -o reverse reverse.c
mrj10@mjlap:~/code$ ./reverse
Time: 0.942709 seconds
mrj10@mjlap:~/code$ ./reverse
Time: 0.991104 seconds
mrj10@mjlap:~/code$ ./reverse
Time: 0.947203 seconds
mrj10@mjlap:~/code$ gcc -fopenmp -std=c99 -O3 -o reverse reverse.c
mrj10@mjlap:~/code$ ./reverse
Time: 0.922639 seconds
mrj10@mjlap:~/code$ ./reverse
Time: 0.892372 seconds
mrj10@mjlap:~/code$ ./reverse
Time: 0.891688 seconds

Arama Tablosu (seçenek 1)

mrj10@mjlap:~/code$ gcc -fopenmp -std=c99 -o reverse_lookup reverse_lookup.c
mrj10@mjlap:~/code$ ./reverse_lookup
Time: 1.201127 seconds              
mrj10@mjlap:~/code$ ./reverse_lookup
Time: 1.196129 seconds              
mrj10@mjlap:~/code$ ./reverse_lookup
Time: 1.235972 seconds              
mrj10@mjlap:~/code$ gcc -fopenmp -std=c99 -O2 -o reverse_lookup reverse_lookup.c
mrj10@mjlap:~/code$ ./reverse_lookup
Time: 0.633042 seconds              
mrj10@mjlap:~/code$ ./reverse_lookup
Time: 0.655880 seconds              
mrj10@mjlap:~/code$ ./reverse_lookup
Time: 0.633390 seconds              
mrj10@mjlap:~/code$ gcc -fopenmp -std=c99 -O3 -o reverse_lookup reverse_lookup.c
mrj10@mjlap:~/code$ ./reverse_lookup
Time: 0.652322 seconds              
mrj10@mjlap:~/code$ ./reverse_lookup
Time: 0.631739 seconds              
mrj10@mjlap:~/code$ ./reverse_lookup
Time: 0.652431 seconds  

Arama Tablosu (seçenek 2)

mrj10@mjlap:~/code$ gcc -fopenmp -std=c99 -o reverse_lookup reverse_lookup.c
mrj10@mjlap:~/code$ ./reverse_lookup
Time: 1.671537 seconds
mrj10@mjlap:~/code$ ./reverse_lookup
Time: 1.688173 seconds
mrj10@mjlap:~/code$ ./reverse_lookup
Time: 1.664662 seconds
mrj10@mjlap:~/code$ gcc -fopenmp -std=c99 -O2 -o reverse_lookup reverse_lookup.c
mrj10@mjlap:~/code$ ./reverse_lookup
Time: 1.049851 seconds
mrj10@mjlap:~/code$ ./reverse_lookup
Time: 1.048403 seconds
mrj10@mjlap:~/code$ ./reverse_lookup
Time: 1.085086 seconds
mrj10@mjlap:~/code$ gcc -fopenmp -std=c99 -O3 -o reverse_lookup reverse_lookup.c
mrj10@mjlap:~/code$ ./reverse_lookup
Time: 1.082223 seconds
mrj10@mjlap:~/code$ ./reverse_lookup
Time: 1.053431 seconds
mrj10@mjlap:~/code$ ./reverse_lookup
Time: 1.081224 seconds

Sonuç

Performans konusunda endişeleriniz varsa seçenek tablosuyla (bayt adresleme şaşırtıcı derecede yavaştır) arama tablosunu kullanın . Her bir son bayt belleği sisteminizden çıkarmanız gerekiyorsa (ve bit tersine çevirme performansını önemsiyorsanız), bitsel-AND yaklaşımının optimize edilmiş sürümleri de çok düşkün değildir.

Uyarı

Evet, karşılaştırma kodu tam bir kesmek olduğunu biliyorum. Nasıl geliştirileceğine dair öneriler memnuniyetle karşılanmaktadır. Bildiğim şeyler:

  • ICC'ye erişimim yok. Bu daha hızlı olabilir (bunu test edebiliyorsanız lütfen bir yoruma yanıt verin).
  • 64K arama tablosu, büyük L1D'ye sahip bazı modern mikro yapılarda iyi sonuç verebilir.
  • -mtune = yerli -O2 / -O3 için işe yaramadı ( ldbazı çılgın sembol yeniden tanımlama hatası ile patladı), bu yüzden oluşturulan kodun mikro mimarim için ayarlandığına inanmıyorum.
  • SSE ile bunu biraz daha hızlı yapmanın bir yolu olabilir. Nasıl olduğu hakkında hiçbir fikrim yok, ama hızlı çoğaltma, bitsel olarak paketlenmiş AND ve dolandırıcılık talimatları ile orada bir şey olmalı.
  • Tehlikeli olmaya yetecek kadar x86 derlemesi biliyorum; İşte seçenek 1 için -O3 üzerinde oluşturulan GCC kodu, bu yüzden kendimden daha bilgili biri kontrol edebilir:

32 bit

.L3:
movl    (%r12,%rsi), %ecx
movzbl  %cl, %eax
movzbl  BitReverseTable256(%rax), %edx
movl    %ecx, %eax
shrl    $24, %eax
mov     %eax, %eax
movzbl  BitReverseTable256(%rax), %eax
sall    $24, %edx
orl     %eax, %edx
movzbl  %ch, %eax
shrl    $16, %ecx
movzbl  BitReverseTable256(%rax), %eax
movzbl  %cl, %ecx
sall    $16, %eax
orl     %eax, %edx
movzbl  BitReverseTable256(%rcx), %eax
sall    $8, %eax
orl     %eax, %edx
movl    %edx, (%r13,%rsi)
addq    $4, %rsi
cmpq    $400000000, %rsi
jne     .L3

EDIT: Ayrıca uint64_therhangi bir performans artışı olup olmadığını görmek için makinemde türleri kullanmayı denedim . Performans 32 bit'ten yaklaşık% 10 daha hızlıydı intve bir seferde iki 32 bitlik tipte bitleri tersine çevirmek için sadece 64 bitlik tipler kullanıyor olsanız da , ya da gerçekten 64- bit değerleri. Montaj kodu aşağıda gösterilmiştir (önceki durum için int, bir seferde iki 32 bit tip için ters bitler ):

.L3:
movq    (%r12,%rsi), %rdx
movq    %rdx, %rax
shrq    $24, %rax
andl    $255, %eax
movzbl  BitReverseTable256(%rax), %ecx
movzbq  %dl,%rax
movzbl  BitReverseTable256(%rax), %eax
salq    $24, %rax
orq     %rax, %rcx
movq    %rdx, %rax
shrq    $56, %rax
movzbl  BitReverseTable256(%rax), %eax
salq    $32, %rax
orq     %rax, %rcx
movzbl  %dh, %eax
shrq    $16, %rdx
movzbl  BitReverseTable256(%rax), %eax
salq    $16, %rax
orq     %rax, %rcx
movzbq  %dl,%rax
shrq    $16, %rdx
movzbl  BitReverseTable256(%rax), %eax
salq    $8, %rax
orq     %rax, %rcx
movzbq  %dl,%rax
shrq    $8, %rdx
movzbl  BitReverseTable256(%rax), %eax
salq    $56, %rax
orq     %rax, %rcx
movzbq  %dl,%rax
shrq    $8, %rdx
movzbl  BitReverseTable256(%rax), %eax
andl    $255, %edx
salq    $48, %rax
orq     %rax, %rcx
movzbl  BitReverseTable256(%rdx), %eax
salq    $40, %rax
orq     %rax, %rcx
movq    %rcx, (%r13,%rsi)
addq    $8, %rsi
cmpq    $400000000, %rsi
jne     .L3

2
Aşırı ayrıntılı ve kapsamlı yazı için -1. j / k. +1.
mpen

8
Tüm bunlar tatmin edici olmasa da ilginç bir egzersizdi. Başka bir şey yoksa, umarım bu süreci görmek daha değerli bir şeyi karşılaştırmak isteyebilecek başka biri için yapıcıdır :)
Matt J

5
Tanrım! Sanırım buldum ... ne çok iyi olabilir ... GERÇEK bir uzman. Belgelerime danışmam ve daha fazla araştırma yapmam gerekecek, ancak bir şey bana (Tanrı, bana yardım et), bunun Stack Overflow'un henüz sahip olduğu en büyük, en kapsamlı ve yararlı cevap olduğunu söylüyor. John Skeet bile hem de dehşete düşecek ve etkilenecekti!
zeboidlund

3
Mikrobenchmarkingin belirli bir kusurunun (diğerlerinin bir listesi arasında), arama tablosu tabanlı çözümleri yapay olarak destekleme eğiliminde olduğunu unutmayın. Karşılaştırma, bir işlemi bir döngüde yinelediğinden, genellikle L1'e uyan bir arama tablosu kullanmanın en hızlı olduğunu görecektir, çünkü önbellek baskısı olmadığından her şey her seferinde L1'e çarpacaktır. Gerçek bir kullanım durumunda, işlem genellikle önbellek basıncına neden olan diğer işlemlerle araya eklenir. RAM'i kaçırmak normalden 10 veya 100 kat daha uzun sürebilir, ancak bu kriterlerde göz ardı edilir.
BeeOnRope

2
Sonuç olarak, iki çözüm yakınsa, genellikle LUT olmayan çözümü (veya daha küçük LUT'a sahip olanı) seçerim çünkü bir LUT'un gerçek dünyadaki etkisi şiddetli olabilir. Daha da iyisi, her bir çözümü "yerinde" karşılaştırmak olacaktır - gerçekte daha büyük uygulamada, gerçekçi girdilerle kullanılır. Tabii ki, bunun için her zaman zamanımız yok ve her zaman gerçekçi girdinin ne olduğunu bilmiyoruz.
BeeOnRope

80

Bu iş parçacığı dikkatimi çekti, çünkü modern bir CPU için bile çok fazla iş (CPU döngüleri) gerektiren basit bir sorunla ilgileniyor. Ve bir gün orada aynı ¤ #% "#" sorunuyla durdum. Milyonlarca baytı çevirmek zorunda kaldım. Ancak tüm hedef sistemlerimin modern Intel tabanlı olduğunu biliyorum, bu yüzden aşırıya optimize etmeye başlayalım !!!

Baz olarak Matt J'nin arama kodunu kullandım. Kıyasladığım sistem bir i7 haswell 4700eq.

Matt J'nin arama biti 400 000 000 bayt çevirme: Yaklaşık 0,272 saniye.

Daha sonra devam ettim ve Intel'in ISPC derleyicisinin aritmetiği tersine çevirip çeviremediğini görmeye çalıştım. C.

Derleyicinin bir şeyler bulmasına yardımcı olmak için çok çalıştığımdan, sizi burada bulgularımla sıkmayacağım, her neyse 400 000 000 bayt bitflip için yaklaşık 0.15 saniye performansla sonuçlandım. Bu büyük bir azalma ama benim uygulama için bu hala çok yavaş.

İnsanlar dünyadaki en hızlı Intel tabanlı bitflipper'ı sunmama izin verdi. Saat:

Bitflip 400000000 bayt: 0.050082 saniye !!!!!

// Bitflip using AVX2 - The fastest Intel based bitflip in the world!!
// Made by Anders Cedronius 2014 (anders.cedronius (you know what) gmail.com)

#include <stdio.h>
#include <stdlib.h>
#include <math.h>
#include <omp.h>

using namespace std;

#define DISPLAY_HEIGHT  4
#define DISPLAY_WIDTH   32
#define NUM_DATA_BYTES  400000000

// Constants (first we got the mask, then the high order nibble look up table and last we got the low order nibble lookup table)
__attribute__ ((aligned(32))) static unsigned char k1[32*3]={
        0x0f,0x0f,0x0f,0x0f,0x0f,0x0f,0x0f,0x0f,0x0f,0x0f,0x0f,0x0f,0x0f,0x0f,0x0f,0x0f,0x0f,0x0f,0x0f,0x0f,0x0f,0x0f,0x0f,0x0f,0x0f,0x0f,0x0f,0x0f,0x0f,0x0f,0x0f,0x0f,
        0x00,0x08,0x04,0x0c,0x02,0x0a,0x06,0x0e,0x01,0x09,0x05,0x0d,0x03,0x0b,0x07,0x0f,0x00,0x08,0x04,0x0c,0x02,0x0a,0x06,0x0e,0x01,0x09,0x05,0x0d,0x03,0x0b,0x07,0x0f,
        0x00,0x80,0x40,0xc0,0x20,0xa0,0x60,0xe0,0x10,0x90,0x50,0xd0,0x30,0xb0,0x70,0xf0,0x00,0x80,0x40,0xc0,0x20,0xa0,0x60,0xe0,0x10,0x90,0x50,0xd0,0x30,0xb0,0x70,0xf0
};

// The data to be bitflipped (+32 to avoid the quantization out of memory problem)
__attribute__ ((aligned(32))) static unsigned char data[NUM_DATA_BYTES+32]={};

extern "C" {
void bitflipbyte(unsigned char[],unsigned int,unsigned char[]);
}

int main()
{

    for(unsigned int i = 0; i < NUM_DATA_BYTES; i++)
    {
        data[i] = rand();
    }

    printf ("\r\nData in(start):\r\n");
    for (unsigned int j = 0; j < 4; j++)
    {
        for (unsigned int i = 0; i < DISPLAY_WIDTH; i++)
        {
            printf ("0x%02x,",data[i+(j*DISPLAY_WIDTH)]);
        }
        printf ("\r\n");
    }

    printf ("\r\nNumber of 32-byte chunks to convert: %d\r\n",(unsigned int)ceil(NUM_DATA_BYTES/32.0));

    double start_time = omp_get_wtime();
    bitflipbyte(data,(unsigned int)ceil(NUM_DATA_BYTES/32.0),k1);
    double end_time = omp_get_wtime();

    printf ("\r\nData out:\r\n");
    for (unsigned int j = 0; j < 4; j++)
    {
        for (unsigned int i = 0; i < DISPLAY_WIDTH; i++)
        {
            printf ("0x%02x,",data[i+(j*DISPLAY_WIDTH)]);
        }
        printf ("\r\n");
    }
    printf("\r\n\r\nTime to bitflip %d bytes: %f seconds\r\n\r\n",NUM_DATA_BYTES, end_time-start_time);

    // return with no errors
    return 0;
}

Printf'ler hata ayıklama içindir.

İşte beygir:

bits 64
global bitflipbyte

bitflipbyte:    
        vmovdqa     ymm2, [rdx]
        add         rdx, 20h
        vmovdqa     ymm3, [rdx]
        add         rdx, 20h
        vmovdqa     ymm4, [rdx]
bitflipp_loop:
        vmovdqa     ymm0, [rdi] 
        vpand       ymm1, ymm2, ymm0 
        vpandn      ymm0, ymm2, ymm0 
        vpsrld      ymm0, ymm0, 4h 
        vpshufb     ymm1, ymm4, ymm1 
        vpshufb     ymm0, ymm3, ymm0         
        vpor        ymm0, ymm0, ymm1
        vmovdqa     [rdi], ymm0
        add     rdi, 20h
        dec     rsi
        jnz     bitflipp_loop
        ret

Kod 32 bayt alır, ardından kemikleri maskeler. Yüksek kemirmek 4'e doğru kaydırılır. Sonra arama tabloları olarak vpshufb ve ymm4 / ymm3 kullanıyorum. Tek bir arama tablosu kullanabilirsiniz ama sonra tekrar nibbles ORing önce sola kaydırmak gerekir.

Uçları çevirmenin daha hızlı yolları var. Ama tek iş parçacığına ve CPU'ya bağlıyım, bu yüzden başarabildiğim en hızlı şeydi. Daha hızlı bir sürüm yapabilir misiniz?

Lütfen Intel C / C ++ Compiler Intrinsic Eşdeğer komutlarını kullanma hakkında yorum yapmayın ...


2
FAR bundan daha fazla oy hak ediyor. Bunun yapılabilmesi gerektiğini biliyordum pshub, çünkü sonuçta en iyi popcount da bununla yapılıyor! Senin için olmasaydı buraya yazardım. Kudos.
Iwillnotexist Idonotexist

3
Teşekkürler! 'popcnt' benim en sevdiğim konu;) BMI2 sürümüme göz atın: sonuç = __ tzcnt_u64 (~ _pext_u64 (veri [i], veri [i]));
Anders Cedronius

3
Asm dosyasını adlandırın: bitflip_asm.s sonra: yasm -f elf64 bitflip_asm.s c dosyasını adlandırın: bitflip.c sonra: g ++ -fopenmp bitflip.c bitflip_asm.o -o bitflip Thats it.
Anders Cedronius

4
Intel CPU'lar için yürütme birimleri popcnt, tzcntve pexttüm port 1. her Yani pextya tzcntmaliyetlerin size popcntthroughput. Verileriniz L1D önbelleğinde sıcaksa, Intel CPU'larda bir dizi oluşturmanın en hızlı yolu AVX2 pshufb'dur. (Ryzen'de saat başına 4 popcntiş hacmi vardır, bu muhtemelen en uygunudur, ancak Bulldozer ailesinde 4 saatte birpopcnt r64,r64 iş çıkışı vardır ... agner.org/optimize ).
Peter Cordes

4
Kendimi içsel bir versiyon kullanıyorum. Ancak cevap verdiğimde ne olduğumu söyledim ve önceki mesajlardan biliyordum ki, derleyiciye akıllı bir aleck yazdığımda her zaman içsel olarak yapmam gerektiğini işaret ediyordu. Geliştirdiğimde önce derleyici yazarım, sonucu sevdiğimde intrinsiklere geçiyorum .. İşte benim .. Sadece 'test' derleyici sürümüm olduğunda cevabımı yolladım.
Anders Cedronius

16

Bu, özyinelemeyi seven insanlar için başka bir çözümdür.

Fikir basit. Girdiyi yarıya bölün ve iki yarıyı değiştirin, tek bite ulaşıncaya kadar devam edin.

Illustrated in the example below.

Ex : If Input is 00101010   ==> Expected output is 01010100

1. Divide the input into 2 halves 
    0010 --- 1010

2. Swap the 2 Halves
    1010     0010

3. Repeat the same for each half.
    10 -- 10 ---  00 -- 10
    10    10      10    00

    1-0 -- 1-0 --- 1-0 -- 0-0
    0 1    0 1     0 1    0 0

Done! Output is 01010100

İşte bunu çözmek için özyinelemeli bir işlev. (Not İmzasız ints kullandım, bu yüzden sizeof (unsigned int) * 8 bit'e kadar girişler için çalışabilir.

Özyinelemeli işlev 2 parametre alır - Bitleri ters çevrilmesi gereken değer ve değerdeki bit sayısı.

int reverse_bits_recursive(unsigned int num, unsigned int numBits)
{
    unsigned int reversedNum;;
    unsigned int mask = 0;

    mask = (0x1 << (numBits/2)) - 1;

    if (numBits == 1) return num;
    reversedNum = reverse_bits_recursive(num >> numBits/2, numBits/2) |
                   reverse_bits_recursive((num & mask), numBits/2) << numBits/2;
    return reversedNum;
}

int main()
{
    unsigned int reversedNum;
    unsigned int num;

    num = 0x55;
    reversedNum = reverse_bits_recursive(num, 8);
    printf ("Bit Reversal Input = 0x%x Output = 0x%x\n", num, reversedNum);

    num = 0xabcd;
    reversedNum = reverse_bits_recursive(num, 16);
    printf ("Bit Reversal Input = 0x%x Output = 0x%x\n", num, reversedNum);

    num = 0x123456;
    reversedNum = reverse_bits_recursive(num, 24);
    printf ("Bit Reversal Input = 0x%x Output = 0x%x\n", num, reversedNum);

    num = 0x11223344;
    reversedNum = reverse_bits_recursive(num,32);
    printf ("Bit Reversal Input = 0x%x Output = 0x%x\n", num, reversedNum);
}

Bu çıktı:

Bit Reversal Input = 0x55 Output = 0xaa
Bit Reversal Input = 0xabcd Output = 0xb3d5
Bit Reversal Input = 0x123456 Output = 0x651690
Bit Reversal Input = 0x11223344 Output = 0x22cc4488

Bu yaklaşım 24 bitlik örnek (3) üzerinde çalışmaz mı? C ve bitsel operatörlere pek aşina değilim ama yaklaşımın açıklamasından 24-> 12-> 6-> 3 (bölünmek için 3 bit düzensiz) tahmin ediyorum. numBitsİnt gibi , işlev parametresi için 3'ü 2'ye böldüğünüzde 1'e yuvarlanır?
Brennan

13

Bu kesinlikle Matt J'inki gibi bir cevap olmayacak, ancak umarım yine de faydalı olacaktır.

size_t reverse(size_t n, unsigned int bytes)
{
    __asm__("BSWAP %0" : "=r"(n) : "0"(n));
    n >>= ((sizeof(size_t) - bytes) * 8);
    n = ((n & 0xaaaaaaaaaaaaaaaa) >> 1) | ((n & 0x5555555555555555) << 1);
    n = ((n & 0xcccccccccccccccc) >> 2) | ((n & 0x3333333333333333) << 2);
    n = ((n & 0xf0f0f0f0f0f0f0f0) >> 4) | ((n & 0x0f0f0f0f0f0f0f0f) << 4);
    return n;
}

Bu, Matt'in en iyi algoritmasıyla tamamen aynı fikirdir, ancak BSWAP adı verilen ve 64 bitlik bir sayının baytlarını (bitlerini değil) değiştiren bu küçük komut vardır. Böylece b7, b6, b5, b4, b3, b2, b1, b0, b0, b1, b2, b3, b4, b5, b6, b7 olur. 32 bitlik bir sayı ile çalıştığımız için bayt-takas edilen sayımızı 32 bit aşağı kaydırmamız gerekir. Bu bizi, yapılan her baytın 8 bitini ve işini değiştirmekle görevlendirir! yapılmıştı.

Zamanlama: Makinemde, Matt'in algoritması deneme başına ~ 0.52 saniye içinde çalıştı. Mayın deneme başına yaklaşık 0,42 saniye içinde koştu. % 20 daha hızlı kötü değil bence.

Talimatın kullanılabilirliğinden endişe ediyorsanız BSWAP Wikipedia , 1989'da çıkan 80846 ile eklenen BSWAP talimatını listelemektedir. Wikipedia'nın da bu talimatın sadece 32 bit kayıtlarda çalıştığını ve benim durumumda, sadece 64 bit kayıtlarda çok işe yarıyor.

Bu yöntem, herhangi bir tümleşik veri tipi için eşit derecede iyi çalışacaktır, böylece yöntem, istenen bayt sayısını geçirerek önemsiz bir şekilde genelleştirilebilir:

    size_t reverse(size_t n, unsigned int bytes)
    {
        __asm__("BSWAP %0" : "=r"(n) : "0"(n));
        n >>= ((sizeof(size_t) - bytes) * 8);
        n = ((n & 0xaaaaaaaaaaaaaaaa) >> 1) | ((n & 0x5555555555555555) << 1);
        n = ((n & 0xcccccccccccccccc) >> 2) | ((n & 0x3333333333333333) << 2);
        n = ((n & 0xf0f0f0f0f0f0f0f0) >> 4) | ((n & 0x0f0f0f0f0f0f0f0f) << 4);
        return n;
    }

daha sonra şöyle adlandırılabilir:

    n = reverse(n, sizeof(char));//only reverse 8 bits
    n = reverse(n, sizeof(short));//reverse 16 bits
    n = reverse(n, sizeof(int));//reverse 32 bits
    n = reverse(n, sizeof(size_t));//reverse 64 bits

Derleyici, ekstra parametreyi uzağa optimize edebilmelidir (derleyicinin işlevi satır içine aldığı varsayılarak) ve sizeof(size_t)durumda sağ kaydırma tamamen kaldırılacaktır. GCC'nin en azından BSWAP'ı kaldıramaz ve geçilirse sağa kaydıramaz sizeof(char).


2
Intel Komut Seti Referans Hacmi 2A'ya ( intel.com/content/www/us/en/processors/… ) göre iki BSWAP talimatı vardır: BSWAP r32 (32 bit kayıtlarda çalışma), 0F C8 + rd olarak kodlanmıştır ve REX.W + 0F C8 + rd olarak kodlanan BSWAP r64 (64 bit kayıtlarda çalışma).
Nubok

Bunun şu şekilde kullanılabileceğini söylüyorsunuz: "n = ters (n, sizeof (size_t)); // ters 64 bit" ancak, tüm sabitler 64 bit'e genişletilmedikçe bu sadece 32 bit sonuç verecektir, o zaman çalışır.
rajkosto

@rajkosto, C ++ 11'den unsigned long long intitibaren, burada ve burada
SirGuy

Tamam? Sadece 64bit değerler üzerinde çalışmasını istiyorsanız, değişmez değerlerinizi genişletmeniz gerektiğini söylüyorum (örneğin, 0xf0f0f0f0f0f0f0f0ull), aksi takdirde sonucun yüksek 32 biti 0s olacaktır.
rajkosto

@rajkosto Ah, ilk yorumunuzu yanlış
anlamıştım

13

Anders Cedronius'un cevabı , AVX2 destekli x86 CPU'ya sahip insanlar için harika bir çözüm sunuyor. AVX desteği olmayan x86 platformları veya x86 olmayan platformlar için, aşağıdaki uygulamalardan herhangi birinin iyi çalışması gerekir.

İlk kod, klasik ARM bölümleme yönteminin bir çeşididir ve çeşitli ARM işlemcilerinde yararlı olan shift-plus-logic deyimin kullanımını en üst düzeye çıkarmak için kodlanmıştır. Buna ek olarak, her bir 32 bit maske değerini yüklemek için birden fazla talimat gerektiren RISC işlemcileri için yararlı olabilecek anında maske oluşturma özelliğini kullanır. X86 platformları için derleyiciler çalışma zamanı yerine derleme zamanında tüm maskeleri hesaplamak için sürekli yayılım kullanmalıdır.

/* Classic binary partitioning algorithm */
inline uint32_t brev_classic (uint32_t a)
{
    uint32_t m;
    a = (a >> 16) | (a << 16);                            // swap halfwords
    m = 0x00ff00ff; a = ((a >> 8) & m) | ((a << 8) & ~m); // swap bytes
    m = m^(m << 4); a = ((a >> 4) & m) | ((a << 4) & ~m); // swap nibbles
    m = m^(m << 2); a = ((a >> 2) & m) | ((a << 2) & ~m);
    m = m^(m << 1); a = ((a >> 1) & m) | ((a << 1) & ~m);
    return a;
}

"Bilgisayar Programlama Sanatı" nın 4A cildinde, D. Knuth, klasik ikili bölümleme algoritmalarından bir şekilde şaşırtıcı bir şekilde daha az işlem gerektiren bitleri tersine çevirmenin akıllı yollarını göstermektedir. TAOCP'de bulamadığım 32 bit işlenenler için böyle bir algoritma, bu belgede Hacker'ın Delight web sitesinde gösterilmektedir.

/* Knuth's algorithm from http://www.hackersdelight.org/revisions.pdf. Retrieved 8/19/2015 */
inline uint32_t brev_knuth (uint32_t a)
{
    uint32_t t;
    a = (a << 15) | (a >> 17);
    t = (a ^ (a >> 10)) & 0x003f801f; 
    a = (t + (t << 10)) ^ a;
    t = (a ^ (a >>  4)) & 0x0e038421; 
    a = (t + (t <<  4)) ^ a;
    t = (a ^ (a >>  2)) & 0x22488842; 
    a = (t + (t <<  2)) ^ a;
    return a;
}

Intel derleyici C / C ++ derleyici 13.1.3.198 kullanarak, yukarıdaki işlevlerin her ikisi de güzel hedefleme XMMkayıtlarını otomatik olarak vektörleştirir . Ayrıca çok fazla çaba harcamadan manuel olarak vektörleştirilebilirler.

IvyBridge Xeon E3 1270v2'de, otomatik vektör kodunu kullanarak, 100 milyon uint32_tkelime kullanarak 0.070 saniyede brev_classic()ve 0.068 saniyede bit ters çevrildi brev_knuth(). Karşılaştırmamın sistem belleği bant genişliği ile sınırlı olmadığından emin olmaya özen gösterdim.


2
@JoelSnyder Öncelikle bahsettiğiniz "çok sayıda sihirli sayı" ile mi varsayıyorum brev_knuth()? Hacker'ın Lokumu'ndaki PDF'deki atıf, bu sayıların doğrudan Knuth'un kendisinden geldiğini gösteriyor. Knuth'un TAOCP'deki temel tasarım ilkelerini tanımlamasını sabitlerin nasıl türetildiğini veya rastgele kelime boyutları için türetici sabitler ve kayma faktörleri hakkında nasıl ilerleyeceğini açıkladığını iddia edemem.
njuffa

8

Bir dizi bitiniz olduğu varsayılarak, buna ne dersiniz: 1. MSB'den başlayarak, bitleri birer birer yığına itin. 2. Bu yığındaki bitleri başka bir diziye (veya yerden tasarruf etmek istiyorsanız aynı diziye) açın, ilk atlanan biti MSB'ye yerleştirin ve oradan daha az önemli bitlere geçin.

Stack stack = new Stack();
Bit[] bits = new Bit[] { 0, 0, 1, 0, 0, 0, 0, 0 };

for (int i = 0; i < bits.Length; i++) 
{
    stack.push(bits[i]);
}

for (int i = 0; i < bits.Length; i++)
{
    bits[i] = stack.pop();
}

3
Bu beni gülümsetti :) Bu C # çözümünün yukarıda optimize edilmiş C'de özetlediğim yöntemlerden birine karşı bir kıyaslama görmek isterdim
Matt J

LOL ... Ama hey! 'en iyi algoritmadaki' en iyi 'sıfatı oldukça öznel bir şeydir: D
Frederick The Fool

7

Yerli ARM talimatı "rbit" yenmek imkansız 1 cpu döngüsü ve 1 ekstra cpu kaydı ile yapabilirsiniz.


6

Bu bir insan için iş değil! ... ama bir makine için mükemmel

Bu 2015, bu sorunun ilk sorulduğu 6 yıl. Derleyiciler o zamandan beri efendimiz haline geldi ve insan olarak bizim işimiz sadece onlara yardım etmek. Peki, niyetimizi makineye vermenin en iyi yolu nedir?

Bit tersine çevirme o kadar yaygındır ki, x86'nın sürekli büyüyen ISA'sının neden tek seferlik bir talimat içermediğini merak etmelisiniz.

Nedeni: Eğer derleyiciye gerçek öz niyetini verirseniz, bit tersine çevirme sadece ~ 20 CPU döngüsü sürmelidir . Size tersine () nasıl yapılacağını göstereyim ve kullanayım:

#include <inttypes.h>
#include <stdio.h>

uint64_t reverse(const uint64_t n,
                 const uint64_t k)
{
        uint64_t r, i;
        for (r = 0, i = 0; i < k; ++i)
                r |= ((n >> i) & 1) << (k - i - 1);
        return r;
}

int main()
{
        const uint64_t size = 64;
        uint64_t sum = 0;
        uint64_t a;
        for (a = 0; a < (uint64_t)1 << 30; ++a)
                sum += reverse(a, size);
        printf("%" PRIu64 "\n", sum);
        return 0;
}

Bu örnek programı Clang sürümü> = 3.6, -O3, -march = native (Haswell ile test edilmiştir) ile derlemek, yeni AVX2 talimatlarını kullanarak resim kalitesinde kod verir, çalışma süresi 11 saniye ~ 1 milyar ters (s) işlenir. 2 GHz varsayımıyla .5 ns CPU döngüsü, bizi tatlı 20 CPU döngüsüne soktuğunu varsayarsak.

  • Tek bir büyük dizi için RAM'a bir kez erişmek için gereken süre içinde 10 geri () sığdırabilirsiniz!
  • L2 önbellek LUT'una iki kez erişmek için gereken süre boyunca 1 geri () takabilirsiniz.

Dikkat: Bu örnek kod birkaç yıl boyunca iyi bir kıyaslama ölçütü olarak kullanılmalıdır, ancak derleyiciler, gerçekten herhangi bir şey hesaplamak yerine nihai sonucu yazdırmak için main () 'yi optimize edecek kadar akıllı olduktan sonra yaşını göstermeye başlayacaktır. Ama şimdilik ters () vitrine çıkıyor.


Bit-reversal is so common...Bunu bilmiyorum. Hemen hemen her gün bit düzeyinde veri ile ilgilenen kod ile çalışır ve hiç bu özel ihtiyacı vardı hatırlayamıyorum. Hangi senaryolara ihtiyacınız var? - Kendi başına çözmenin ilginç bir sorun olmadığı değil.
500 - Dahili Sunucu Hatası

@ 500-InternalServerError Bu işlevi hızlı, kısa ve öz veri yapıları ile dilbilgisi çıkarımında defalarca ihtiyaç duyuyorum. Bir bitarray olarak kodlanan normal bir ikili ağaç, "büyük endian" düzeninde dilbilgisini çıkarır. Ancak, düğümleri tersine çevirme permütasyonu ile değiştirilmiş bir ağaç (bitarray) oluşturursanız daha iyi genelleme için, öğrenilen dilbilgisinin dizeleri "küçük endian" dır. Bu anahtarlama, sabit tamsayı boyutları yerine değişken uzunluklu dizeler çıkarmanıza olanak tanır. Bu durum aynı zamanda verimli FFT çok açılır: bkz en.wikipedia.org/wiki/Bit-reversal_permutation

1
Teşekkürler, bir şekilde FFT'nin cevabınıza dahil olabileceğini sezmeyi başardım :)
500 - Dahili Sunucu Hatası

neden sadece 20 döngü? Hangi mimari? Bu, insanlığa ve soyumuz tükenene kadar geleceğin tüm süper geniş VLIW mimarileri için geçerli mi? Just Questions, no answer ... downvote to hell again
Quonux


5

C değil asm biliyorum:

var1 dw 0f0f0
clc
     push ax
     push cx
     mov cx 16
loop1:
     shl var1
     shr ax
loop loop1
     pop ax
     pop cx

Bu, taşıma bitiyle çalışır, böylece bayrakları da kaydedebilirsiniz


1
Ben oldukça hızlı olurdu asm anahtar kelime kullanabilirsiniz sanırım .
tom

Bu bile işe yaramıyor. Bence rclCF'yi var1sadece shlbayrakları okumamak yerine kaydırmak istiyorsunuz . (Veya adc dx,dx). Bu düzeltmeyle bile, bu yavaş looptalimatı kullanarak ve var1hafızada tutarak gülünç yavaştır ! Aslında bunun AX'de çıktı üretmesi gerektiğini düşünüyorum, ancak AX'in eski değerini sonucun üzerine kaydeder / geri yükler.
Peter Cordes

4

Düşük bellek ve en hızlı uygulama.

private Byte  BitReverse(Byte bData)
    {
        Byte[] lookup = { 0, 8,  4, 12, 
                          2, 10, 6, 14 , 
                          1, 9,  5, 13,
                          3, 11, 7, 15 };
        Byte ret_val = (Byte)(((lookup[(bData & 0x0F)]) << 4) + lookup[((bData & 0xF0) >> 4)]);
        return ret_val;
    }

4

Bu temelde ilk "reverse ()" ile aynıdır, ancak 64 bittir ve komut akışından yüklenmesi için sadece bir acil maskeye ihtiyaç vardır. GCC, atlama olmadan kod oluşturur, bu yüzden bu oldukça hızlı olmalıdır.

#include <stdio.h>

static unsigned long long swap64(unsigned long long val)
{
#define ZZZZ(x,s,m) (((x) >>(s)) & (m)) | (((x) & (m))<<(s));
/* val = (((val) >>16) & 0xFFFF0000FFFF) | (((val) & 0xFFFF0000FFFF)<<16); */

val = ZZZZ(val,32,  0x00000000FFFFFFFFull );
val = ZZZZ(val,16,  0x0000FFFF0000FFFFull );
val = ZZZZ(val,8,   0x00FF00FF00FF00FFull );
val = ZZZZ(val,4,   0x0F0F0F0F0F0F0F0Full );
val = ZZZZ(val,2,   0x3333333333333333ull );
val = ZZZZ(val,1,   0x5555555555555555ull );

return val;
#undef ZZZZ
}

int main(void)
{
unsigned long long val, aaaa[16] =
 { 0xfedcba9876543210,0xedcba9876543210f,0xdcba9876543210fe,0xcba9876543210fed
 , 0xba9876543210fedc,0xa9876543210fedcb,0x9876543210fedcba,0x876543210fedcba9
 , 0x76543210fedcba98,0x6543210fedcba987,0x543210fedcba9876,0x43210fedcba98765
 , 0x3210fedcba987654,0x210fedcba9876543,0x10fedcba98765432,0x0fedcba987654321
 };
unsigned iii;

for (iii=0; iii < 16; iii++) {
    val = swap64 (aaaa[iii]);
    printf("A[]=%016llX Sw=%016llx\n", aaaa[iii], val);
    }
return 0;
}

4

Açıkça ham dönüşün ne kadar hızlı olacağını merak ediyordum. Makinemde (i7 @ 2600), 1.500.150.000 yineleme için ortalama 27.28 ns(rastgele bir 131.071 64 bit tamsayı kümesi) idi.

Avantajları: Gerekli bellek miktarı azdır ve kod basittir. Ben de o kadar büyük olmadığını söyleyebilirim. Gerekli süre, herhangi bir girdi için öngörülebilir ve sabittir (128 aritmetik SHIFT işlemi + 64 mantıksal AND işlemleri + 64 mantıksal VEYA işlemleri).

Kabul edilen cevabı veren @Matt J tarafından elde edilen en iyi zamanla karşılaştırdım. Cevabını doğru bir şekilde okursam, elde ettiği en iyi şey, iterasyonlar 0.631739için saniyelerdi 1,000,000, bu da 631 nsrotasyon başına ortalama bir değere yol açtı .

Kullandığım kod snippet'i aşağıdadır:

unsigned long long reverse_long(unsigned long long x)
{
    return (((x >> 0) & 1) << 63) |
           (((x >> 1) & 1) << 62) |
           (((x >> 2) & 1) << 61) |
           (((x >> 3) & 1) << 60) |
           (((x >> 4) & 1) << 59) |
           (((x >> 5) & 1) << 58) |
           (((x >> 6) & 1) << 57) |
           (((x >> 7) & 1) << 56) |
           (((x >> 8) & 1) << 55) |
           (((x >> 9) & 1) << 54) |
           (((x >> 10) & 1) << 53) |
           (((x >> 11) & 1) << 52) |
           (((x >> 12) & 1) << 51) |
           (((x >> 13) & 1) << 50) |
           (((x >> 14) & 1) << 49) |
           (((x >> 15) & 1) << 48) |
           (((x >> 16) & 1) << 47) |
           (((x >> 17) & 1) << 46) |
           (((x >> 18) & 1) << 45) |
           (((x >> 19) & 1) << 44) |
           (((x >> 20) & 1) << 43) |
           (((x >> 21) & 1) << 42) |
           (((x >> 22) & 1) << 41) |
           (((x >> 23) & 1) << 40) |
           (((x >> 24) & 1) << 39) |
           (((x >> 25) & 1) << 38) |
           (((x >> 26) & 1) << 37) |
           (((x >> 27) & 1) << 36) |
           (((x >> 28) & 1) << 35) |
           (((x >> 29) & 1) << 34) |
           (((x >> 30) & 1) << 33) |
           (((x >> 31) & 1) << 32) |
           (((x >> 32) & 1) << 31) |
           (((x >> 33) & 1) << 30) |
           (((x >> 34) & 1) << 29) |
           (((x >> 35) & 1) << 28) |
           (((x >> 36) & 1) << 27) |
           (((x >> 37) & 1) << 26) |
           (((x >> 38) & 1) << 25) |
           (((x >> 39) & 1) << 24) |
           (((x >> 40) & 1) << 23) |
           (((x >> 41) & 1) << 22) |
           (((x >> 42) & 1) << 21) |
           (((x >> 43) & 1) << 20) |
           (((x >> 44) & 1) << 19) |
           (((x >> 45) & 1) << 18) |
           (((x >> 46) & 1) << 17) |
           (((x >> 47) & 1) << 16) |
           (((x >> 48) & 1) << 15) |
           (((x >> 49) & 1) << 14) |
           (((x >> 50) & 1) << 13) |
           (((x >> 51) & 1) << 12) |
           (((x >> 52) & 1) << 11) |
           (((x >> 53) & 1) << 10) |
           (((x >> 54) & 1) << 9) |
           (((x >> 55) & 1) << 8) |
           (((x >> 56) & 1) << 7) |
           (((x >> 57) & 1) << 6) |
           (((x >> 58) & 1) << 5) |
           (((x >> 59) & 1) << 4) |
           (((x >> 60) & 1) << 3) |
           (((x >> 61) & 1) << 2) |
           (((x >> 62) & 1) << 1) |
           (((x >> 63) & 1) << 0);
}

@greybeard Sorunuzu anladığımdan emin değilim.
marian adam

hata fark ettiğiniz için teşekkürler, sağlanan kod örneği düzeltildi.
marian adam

3

Standart şablon kütüphanesini kullanmak isteyebilirsiniz. Yukarıda belirtilen koddan daha yavaş olabilir. Ancak bana anlaşılır ve anlaşılması daha kolay geliyor.

 #include<bitset>
 #include<iostream>


 template<size_t N>
 const std::bitset<N> reverse(const std::bitset<N>& ordered)
 {
      std::bitset<N> reversed;
      for(size_t i = 0, j = N - 1; i < N; ++i, --j)
           reversed[j] = ordered[i];
      return reversed;
 };


 // test the function
 int main()
 {
      unsigned long num; 
      const size_t N = sizeof(num)*8;

      std::cin >> num;
      std::cout << std::showbase << std::hex;
      std::cout << "ordered  = " << num << std::endl;
      std::cout << "reversed = " << reverse<N>(num).to_ulong()  << std::endl;
      std::cout << "double_reversed = " << reverse<N>(reverse<N>(num)).to_ulong() << std::endl;  
 }

2

genel

C kodu. Örnek olarak 1 bayt giriş verisi num.

    unsigned char num = 0xaa;   // 1010 1010 (aa) -> 0101 0101 (55)
    int s = sizeof(num) * 8;    // get number of bits
    int i, x, y, p;
    int var = 0;                // make var data type to be equal or larger than num

    for (i = 0; i < (s / 2); i++) {
        // extract bit on the left, from MSB
        p = s - i - 1;
        x = num & (1 << p);
        x = x >> p;
        printf("x: %d\n", x);

        // extract bit on the right, from LSB
        y = num & (1 << i);
        y = y >> i;
        printf("y: %d\n", y);

        var = var | (x << i);       // apply x
        var = var | (y << p);       // apply y
    }

    printf("new: 0x%x\n", new);

Soru "basit / anlaşılır" değil, "en verimli" yi istedi.
Peter Cordes

1

Aşağıdakiler nasıl olur:

    uint reverseMSBToLSB32ui(uint input)
    {
        uint output = 0x00000000;
        uint toANDVar = 0;
        int places = 0;

        for (int i = 1; i < 32; i++)
        {
            places = (32 - i);
            toANDVar = (uint)(1 << places);
            output |= (uint)(input & (toANDVar)) >> places;

        }


        return output;
    }

Küçük ve kolay (gerçi, sadece 32 bit).


Soru "en verimli" diye sordu; 32 defa döngüyü dışlayabiliriz. (Ve özellikle maskeyi kaydırmamak ve sonucu LSB'ye kaydırmak zorunda kalmamak)
Peter Cordes

1

Bunun biraz tersine çevirmenin en basit yollarından biri olduğunu düşündüm. lütfen bu mantıkta herhangi bir kusur olup olmadığını bana bildirin. temel olarak bu mantıkta, bitin pozisyondaki değerini kontrol ederiz. değer ters konumda 1 ise biti ayarlayın.

void bit_reverse(ui32 *data)
{
  ui32 temp = 0;    
  ui32 i, bit_len;    
  {    
   for(i = 0, bit_len = 31; i <= bit_len; i++)   
   {    
    temp |= (*data & 1 << i)? (1 << bit_len-i) : 0;    
   }    
   *data = temp;    
  }    
  return;    
}    

Soru "basit / anlaşılır" değil, "en verimli" yi istedi.
Peter Cordes

0
unsigned char ReverseBits(unsigned char data)
{
    unsigned char k = 0, rev = 0;

    unsigned char n = data;

    while(n)

    {
        k = n & (~(n - 1));
        n &= (n - 1);
        rev |= (128 / k);
    }
    return rev;
}

İlginç, ancak bir çalışma zamanı değişkenine göre bölme yavaş. kher zaman 2 gücüdür, ancak derleyiciler muhtemelen bunu kanıtlamaz ve bit tarama / kaydırmaya dönüştürmez.
Peter Cordes

0

Bence bildiğim en basit yöntem şu. MSBgirdi ve LSB'ters' çıktıdır:

unsigned char rev(char MSB) {
    unsigned char LSB=0;  // for output
    _FOR(i,0,8) {
        LSB= LSB << 1;
        if(MSB&1) LSB = LSB | 1;
        MSB= MSB >> 1;
    }
    return LSB;
}

//    It works by rotating bytes in opposite directions. 
//    Just repeat for each byte.

0
// Purpose: to reverse bits in an unsigned short integer 
// Input: an unsigned short integer whose bits are to be reversed
// Output: an unsigned short integer with the reversed bits of the input one
unsigned short ReverseBits( unsigned short a )
{
     // declare and initialize number of bits in the unsigned short integer
     const char num_bits = sizeof(a) * CHAR_BIT;

     // declare and initialize bitset representation of integer a
     bitset<num_bits> bitset_a(a);          

     // declare and initialize bitset representation of integer b (0000000000000000)
     bitset<num_bits> bitset_b(0);                  

     // declare and initialize bitset representation of mask (0000000000000001)
     bitset<num_bits> mask(1);          

     for ( char i = 0; i < num_bits; ++i )
     {
          bitset_b = (bitset_b << 1) | bitset_a & mask;
          bitset_a >>= 1;
     }

     return (unsigned short) bitset_b.to_ulong();
}

void PrintBits( unsigned short a )
{
     // declare and initialize bitset representation of a
     bitset<sizeof(a) * CHAR_BIT> bitset(a);

     // print out bits
     cout << bitset << endl;
}


// Testing the functionality of the code

int main ()
{
     unsigned short a = 17, b;

     cout << "Original: "; 
     PrintBits(a);

     b = ReverseBits( a );

     cout << "Reversed: ";
     PrintBits(b);
}

// Output:
Original: 0000000000010001
Reversed: 1000100000000000

0

Sayı düşük olduğunda hızla çıkan başka bir döngü tabanlı çözüm (birden çok tür için C ++ ile)

template<class T>
T reverse_bits(T in) {
    T bit = static_cast<T>(1) << (sizeof(T) * 8 - 1);
    T out;

    for (out = 0; bit && in; bit >>= 1, in >>= 1) {
        if (in & 1) {
            out |= bit;
        }
    }
    return out;
}

veya imzalanmamış bir int için C

unsigned int reverse_bits(unsigned int in) {
    unsigned int bit = 1u << (sizeof(T) * 8 - 1);
    unsigned int out;

    for (out = 0; bit && in; bit >>= 1, in >>= 1) {
        if (in & 1)
            out |= bit;
    }
    return out;
}

0

Görünüşe göre diğer pek çok gönderi hız konusunda endişe duyuyor (en iyi = en hızlı). Basitliğe ne olacak? Düşünmek:

char ReverseBits(char character) {
    char reversed_character = 0;
    for (int i = 0; i < 8; i++) {
        char ith_bit = (c >> i) & 1;
        reversed_character |= (ith_bit << (sizeof(char) - 1 - i));
    }
    return reversed_character;
}

ve akıllı derleyicinin sizin için optimize edeceğini umuyoruz.

Daha uzun bir bit listesini (bit içeren sizeof(char) * n) tersine çevirmek istiyorsanız, aşağıdakileri elde etmek için bu işlevi kullanabilirsiniz:

void ReverseNumber(char* number, int bit_count_in_number) {
    int bytes_occupied = bit_count_in_number / sizeof(char);      

    // first reverse bytes
    for (int i = 0; i <= (bytes_occupied / 2); i++) {
        swap(long_number[i], long_number[n - i]);
    }

    // then reverse bits of each individual byte
    for (int i = 0; i < bytes_occupied; i++) {
         long_number[i] = ReverseBits(long_number[i]);
    }
}

Bu [10000000, 10101010] 'u [01010101, 00000001]' e çevirir.


İç döngüde 3 vardiya var. İle kaydedin ith_bit = (c >> i) & 1. Ayrıca , hedef kaydındaki n'inci biti ayarlamak için reversed_charx86 üzerinde sub something/ derleneceğini ummadıkça, biti değiştirmek yerine kaydırarak bir SUB kaydedin bts reg,reg.
Peter Cordes

-1

Sahte kodda bit tersine çevirme

kaynak -> ters çevrilecek bayt b00101100 hedef -> ters çevrilmiş, ayrıca işaretsiz tipte olmalıdır, bu nedenle işaret biti propogasyona tabi tutulmaz

orijinalin etkilenmemesi için geçici olarak kopyalayın, ayrıca işaretsiz tipte olması gerekir, böylece işaret biti otomatik olarak kaydırılmaz

bytecopy = b0010110

LOOP8: // baytkopi <0 (negatif) ise bunu 8 kez test edin

    set bit8 (msb) of reversed = reversed | b10000000 

else do not set bit8

shift bytecopy left 1 place
bytecopy = bytecopy << 1 = b0101100 result

shift result right 1 place
reversed = reversed >> 1 = b00000000
8 times no then up^ LOOP8
8 times yes then done.

-1

Benim basit çözümüm

BitReverse(IN)
    OUT = 0x00;
    R = 1;      // Right mask   ...0000.0001
    L = 0;      // Left mask    1000.0000...
    L = ~0; 
    L = ~(i >> 1);
    int size = sizeof(IN) * 4;  // bit size

    while(size--){
        if(IN & L) OUT = OUT | R; // start from MSB  1000.xxxx
        if(IN & R) OUT = OUT | L; // start from LSB  xxxx.0001
        L = L >> 1;
        R = R << 1; 
    }
    return OUT;

1
Nedir i? Ayrıca, bu büyü sabiti * 4nedir? Öyle mi CHAR_BIT / 2?
Peter Cordes

-1

Bu 32 bit içindir, 8 biti düşünürsek boyutu değiştirmeliyiz.

    void bitReverse(int num)
    {
        int num_reverse = 0;
        int size = (sizeof(int)*8) -1;
        int i=0,j=0;
        for(i=0,j=size;i<=size,j>=0;i++,j--)
        {
            if((num >> i)&1)
            {
                num_reverse = (num_reverse | (1<<j));
            }
        }
        printf("\n rev num = %d\n",num_reverse);
    }

LSB-> MSB sıralamasında "num" giriş tamsayısının okunması ve MSB-> LSB sıralamasında num_reverse kaydedilmesi.


1
Daha kolay anlaşılması için koda bir açıklama eklemelisiniz.
Tunaki

-3
int bit_reverse(int w, int bits)
{
    int r = 0;
    for (int i = 0; i < bits; i++)
    {
        int bit = (w & (1 << i)) >> i;
        r |= bit << (bits - i - 1);
    }
    return r;
}

3
Genellikle, kodun ne yapmak istediği ve bunun neden sorunu çözdüğüne dair bir açıklama içeriyorsa, cevaplar çok daha yararlıdır.
IKavanagh
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.