อัลกอริทึมที่มีประสิทธิภาพสำหรับการกลับบิต (จาก MSB-> LSB ถึง LSB-> MSB) ใน C


243

อัลกอริทึมที่มีประสิทธิภาพที่สุดคืออะไร

0010 0000 => 0000 0100

การแปลงมาจาก MSB-> LSB เป็น LSB-> MSB บิตทั้งหมดจะต้องย้อนกลับ; นั่นคือนี่คือไม่ใช่ endianness-swapping


1
ฉันคิดว่าชื่อที่เหมาะสมเป็นการดำเนินการระดับบิต
Kredns

5
ฉันคิดว่าคุณหมายถึงการกลับรายการไม่ใช่การหมุน
Juliano

2
โปรเซสเซอร์ ARM ส่วนใหญ่มีการทำงานในตัวสำหรับสิ่งนั้น ARM Cortex-M0 ทำไม่ได้และฉันพบว่าการใช้ตารางต่อไบต์เพื่อแลกเปลี่ยนบิตเป็นวิธีที่เร็วที่สุด
starblue

2
ยังเห็นฌอนเดอร์สันอีรอนของบิต twiddling Hacks
jww

2
โปรดกำหนด "ดีที่สุด"
Lee Taylor

คำตอบ:


497

บันทึก : อัลกอริทึมทั้งหมดด้านล่างเป็น C แต่ควรพกพาไปได้ในภาษาที่คุณเลือก (อย่ามองฉันเมื่อมันไม่เร็ว :)

ตัวเลือก

หน่วยความจำต่ำ (เครื่อง 32- บิตint, 32- บิต) (จากที่นี่ ):

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));

}

จากหน้าBit Twiddling Hacks ที่มีชื่อเสียง:

เร็วที่สุด (ตารางการค้นหา) :

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]];

คุณสามารถขยายแนวคิดนี้เป็น 64 บิตintหรือแลกเปลี่ยนความจำเพื่อความเร็ว (สมมติว่าแคชข้อมูล L1 ของคุณมีขนาดใหญ่พอ) และย้อนกลับ 16 บิตพร้อมกันด้วยตารางการค้นหารายการ 64K


คนอื่น ๆ

ง่าย

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

เร็วกว่า (โปรเซสเซอร์ 32 บิต)

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

เร็วกว่า (โปรเซสเซอร์ 64 บิต)

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

หากคุณต้องการทำเช่นนี้ในแบบ 32 บิตintเพียงแค่ย้อนกลับบิตในแต่ละไบต์และกลับลำดับของไบต์ นั่นคือ:

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);

ผล

ฉันเปรียบเทียบโซลูชันที่มีแนวโน้มมากที่สุดสองรายการตารางการค้นหาและ bitwise-AND (อันแรก) เครื่องทดสอบคือแล็ปท็อปที่มี DDR2-800 / 4GB และ Core 2 Duo T7500 @ 2.4GHz, แคช L2 4MB; YMMV ฉันใช้gcc 4.3.2 บน Linux 64 บิต OpenMP (และการรวม GCC) ถูกใช้สำหรับตัวจับเวลาความละเอียดสูง

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;
}

ฉันลองทั้งสองวิธีในการปรับให้เหมาะสมต่าง ๆ วิ่ง 3 ครั้งในแต่ละระดับและการทดลองแต่ละครั้งกลับสุ่ม 100 ล้านunsigned intsครั้ง สำหรับตัวเลือกตารางการค้นหาฉันลองทั้งแบบแผน (ตัวเลือก 1 และ 2) ที่กำหนดในหน้าแฮ็กบิตทริก ผลลัพธ์แสดงไว้ด้านล่าง

ระดับบิตและระดับ

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

ตารางค้นหา (ตัวเลือก 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  

ตารางค้นหา (ตัวเลือก 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

ข้อสรุป

ใช้ตารางการค้นหาพร้อมตัวเลือก 1 (การกำหนดแอดเดรสไบต์ช้าอย่างไม่น่าเชื่อ) หากคุณกังวลเกี่ยวกับประสิทธิภาพ หากคุณต้องการบีบหน่วยความจำทุกไบต์สุดท้ายออกจากระบบของคุณ (และคุณอาจ, หากคุณสนใจเกี่ยวกับประสิทธิภาพของการกลับบิต), รุ่นที่เหมาะสมที่สุดของวิธี bitwise-AND ไม่ได้โทรมเกินไป

ข้อแม้

ใช่ฉันรู้ว่าโค้ดมาตรฐานเป็นแฮ็คที่สมบูรณ์ คำแนะนำเกี่ยวกับวิธีการปรับปรุงให้ดียิ่งกว่ายินดีต้อนรับ สิ่งที่ฉันรู้เกี่ยวกับ:

  • ฉันไม่สามารถเข้าถึง ICC อาจเร็วกว่านี้ (โปรดตอบในความคิดเห็นหากคุณสามารถทดสอบได้)
  • ตารางการค้นหา 64K อาจทำได้ดีในสถาปัตยกรรมแบบไมโครที่ทันสมัยที่มี L1D ขนาดใหญ่
  • -mtune = native ไม่ทำงานสำหรับ -O2 / -O3 (ldพัดไปพร้อมกับข้อผิดพลาดในการกำหนดสัญลักษณ์ใหม่บางข้อผิดพลาด) ดังนั้นฉันไม่เชื่อว่ารหัสที่สร้างขึ้นจะได้รับการปรับแต่งสำหรับสถาปัตยกรรมแบบไมโครของฉัน
  • อาจมีวิธีการทำเช่นนี้ได้เร็วขึ้นเล็กน้อยด้วย SSE ฉันไม่รู้เลยว่า แต่ด้วยการทำซ้ำอย่างรวดเร็วบรรจุบิตและ AND และคำสั่งที่ร้อนแรงทำให้ต้องมีบางอย่างที่นั่น
  • ฉันรู้ว่าชุดประกอบ x86 เพียงพอที่จะเป็นอันตราย นี่คือรหัส GCC ที่สร้างใน -O3 สำหรับตัวเลือก 1 ดังนั้นใครบางคนที่มีความรู้มากกว่าตัวฉันเองก็สามารถลองดูได้:

32 บิต

.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

แก้ไข: ฉันยังลองใช้uint64_tประเภทบนเครื่องของฉันเพื่อดูว่ามีการปรับปรุงประสิทธิภาพหรือไม่ ประสิทธิภาพนั้นเร็วกว่า 32 บิตประมาณ 10% และเกือบเหมือนกันไม่ว่าคุณจะใช้ชนิด 64 บิตเพื่อย้อนกลับบิตในสองประเภท 32 บิตintในแต่ละครั้งหรือว่าคุณกลับบิตจริง ๆ ในช่วงครึ่งปีมากถึง 64- ค่าบิต รหัสแอสเซมบลีแสดงอยู่ด้านล่าง (สำหรับกรณีก่อนหน้าการย้อนกลับบิตสำหรับชนิด 32- บิตสองintครั้ง):

.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
-1 สำหรับโพสต์ที่ละเอียดและละเอียดมากเกินไป เจ / k +1
mpen

8
มันเป็นแบบฝึกหัดที่น่าสนใจถ้าไม่ใช่ทุกสิ่งที่เติมเต็ม ถ้าไม่มีอะไรผมหวังเห็นกระบวนการเป็นที่สร้างสรรค์ให้กับคนอื่นที่อาจต้องการบางสิ่งบางอย่างได้รับรางวัลมาตรฐานมากขึ้น :)
แมตต์เจ

5
พระเจ้า! ฉันคิดว่าฉันได้พบ ... สิ่งที่อาจเป็น ... ตัวจริงที่แท้จริง ฉันจะต้องอ่านเอกสารของฉันและทำการวิจัยเพิ่มเติม แต่มีบางอย่างบอกฉัน (พระเจ้าช่วยฉันด้วย) ว่านี่เป็นคำตอบที่ยิ่งใหญ่ที่สุดคำตอบที่ละเอียดและมีประโยชน์ที่สุดที่กองซ้อนยังมีอยู่ แม้แต่ John Skeet ก็จะตกใจและประทับใจ!
zeboidlund

3
โปรดจำไว้ว่าข้อบกพร่องหนึ่งของการทำเครื่องหมายขนาดเล็ก (ในรายชื่อของคนอื่น ๆ ) คือมันมีแนวโน้มที่จะให้ความสำคัญกับการแก้ปัญหาการค้นหาบนโต๊ะ เนื่องจากมาตรฐานกำลังทำซ้ำการดำเนินการหนึ่งในลูปมันมักจะพบว่าการใช้ตารางการค้นหาที่พอดีใน L1 นั้นเร็วที่สุดเพราะทุกอย่างจะเข้าสู่ L1 ทุกครั้งเนื่องจากไม่มีแรงกดดันแคชเลย ในกรณีที่ใช้งานจริงการดำเนินการมักจะถูกเชื่อมโยงกับการดำเนินการอื่นที่ทำให้เกิดแรงกดดันแคช การพลาด RAM อาจใช้เวลานานกว่าปกติ 10 หรือ 100 เท่า แต่สิ่งนี้จะถูกละเว้นในการวัดประสิทธิภาพ
BeeOnRope

2
ผลที่สุดคือว่าถ้ามีสองวิธีใกล้กันฉันมักจะเลือกวิธีที่ไม่ใช่ LUT (หรืออันที่เล็กกว่า LUT) เพราะผลกระทบจากโลกแห่งความเป็นจริงของ LUT นั้นรุนแรง ยิ่งไปกว่านั้นก็คือการวัดมาตรฐานแต่ละโซลูชัน "ในแหล่งกำเนิด" - ซึ่งมันถูกใช้จริงในแอพพลิเคชั่นขนาดใหญ่ที่มีอินพุตจริง แน่นอนว่าเราไม่มีเวลาสำหรับเรื่องนั้นและเราก็ไม่เคยรู้เลยว่าข้อมูลที่แท้จริงคืออะไร
BeeOnRope

80

หัวข้อนี้ดึงดูดความสนใจของฉันเพราะมันเกี่ยวข้องกับปัญหาง่าย ๆ ที่ต้องใช้งานมาก (รอบ CPU) แม้สำหรับ CPU ที่ทันสมัย และวันหนึ่งฉันก็ยืนอยู่ที่นั่นด้วยปัญหาเดียวกัน¤ #% "#" ฉันต้องพลิกนับล้านไบต์ อย่างไรก็ตามฉันรู้ว่าระบบเป้าหมายทั้งหมดของฉันนั้นใช้ Intel ที่ทันสมัยดังนั้นเรามาเริ่มปรับแต่งให้ดีที่สุด !!!

ดังนั้นฉันจึงใช้รหัสการค้นหาของ Matt J เป็นพื้นฐาน ระบบที่ฉันใช้เปรียบเทียบคือ i7 Haswell 4700eq

การค้นหาของ Matt J ทำการ bitflipping 400,000 000 bytes: ประมาณ 0.272 วินาที

จากนั้นฉันก็ไปข้างหน้าและพยายามดูว่าคอมไพเลอร์ ISPC ของ Intel สามารถแปลงค่า arithmetics ใน reverse.c ได้ไหม

ฉันจะไม่ทำให้คุณเบื่อกับการค้นพบของฉันที่นี่เพราะฉันพยายามอย่างมากที่จะช่วยให้ผู้รวบรวมค้นพบสิ่งต่าง ๆ อย่างไรก็ตามฉันลงเอยด้วยประสิทธิภาพการทำงานประมาณ 0.15 วินาทีเพื่อ bitflip 400,000 000 ไบต์ มันเป็นการลดที่ยอดเยี่ยม แต่สำหรับแอปพลิเคชันของฉันที่ยังช้าเกินไป

ดังนั้นผู้คนจึงขอให้ฉันนำเสนอเครื่องมือที่เป็นพื้นฐานของอินเทลที่เร็วที่สุดในโลก โอเวอร์คล็อกที่:

เวลาในการ bitflip 400000000 ไบต์: 0.050082 วินาที !!!!!

// 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's ใช้สำหรับการดีบัก ..

นี่คือสิ่งเทียม:

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

รหัสใช้ 32 ไบต์จากนั้นปิดบัง nibbles แทะสูงได้รับการเลื่อนไปทางขวา 4 จากนั้นฉันใช้ vpshufb และ ymm4 / ymm3 เป็นตารางการค้นหา ฉันสามารถใช้ตารางการค้นหาเดียว แต่จากนั้นฉันจะต้องเลื่อนไปทางซ้ายก่อนที่จะ ORI ตอดด้วยกันอีกครั้ง

มีวิธีที่เร็วกว่าในการพลิกบิต แต่ฉันต้องใช้เธรดเดี่ยวและ CPU ดังนั้นนี่จึงเป็นวิธีที่เร็วที่สุดที่ฉันจะทำได้ คุณสร้างเวอร์ชั่นเร็วขึ้นได้ไหม

กรุณาไม่แสดงความคิดเห็นเกี่ยวกับการใช้คำสั่ง Intel C / C ++ Compiler Intrinsic Equivalent ...


2
คุณควรได้รับ upvotes มากกว่า FAR มากกว่านี้ ฉันรู้ว่านี่ควรจะทำได้pshubเพราะหลังจากที่ popcount ที่ดีที่สุดก็ทำไปแล้ว! ฉันจะเขียนที่นี่ถ้าไม่ใช่เพื่อคุณ ความรุ่งโรจน์
Iwillnotexist Idonotexist

3
ขอบคุณ! 'popcnt' เป็นอีกเรื่องที่ฉันชอบ;) ลองดูรุ่น BMI2 ของฉัน: result = __ tzcnt_u64 (~ _pext_u64 (data [i], data [i]));
Anders Cedronius

3
ตั้งชื่อไฟล์ asm: bitflip_asm.s แล้ว: yasm -f elf64 bitflip_asm.s ตั้งชื่อไฟล์ c: bitflip.c ดังนั้น: g ++ -fopenmp bitflip.c bitflip_asm.o -o bitflip นั่นแหละ
Anders Cedronius

4
CPU ของ Intel มีหน่วยดำเนินการสำหรับpopcnt, tzcntและpextทั้งหมดในพอร์ต 1. ดังนั้นทุกpextหรือtzcntค่าใช้จ่ายคุณpopcntของผ่าน หากข้อมูลของคุณร้อนในแคช L1D วิธีที่เร็วที่สุดในการเพิ่มจำนวนอาร์เรย์ใน Intel CPUs คือใช้ AVX2 pshufb (Ryzen มีpopcntความเร็วในการรับสัญญาณ 4 ครั้งต่อชั่วโมงดังนั้นจึงน่าจะเหมาะสมที่สุด แต่ Bulldozer-family มีpopcnt r64,r64อัตราความเร็วสัญญาณหนึ่งต่อ 4 นาฬิกา... agner.org/optimize )
Peter Cordes

4
ฉันกำลังใช้รุ่นที่แท้จริงด้วยตัวเอง อย่างไรก็ตามเมื่อฉันตอบฉันโพสต์สิ่งที่ฉันมีและฉันรู้จากการโพสต์ก่อนหน้านี้ว่าทันทีที่ฉันเขียนประกอบ aleck สมาร์ทเสมอชี้ให้เห็นว่าฉันควรจะทำมันในที่แท้จริง เมื่อฉันพัฒนาฉันเขียนแอสเซมเบลอร์ก่อนแล้วเมื่อฉันชอบผลที่ได้ฉันจะย้ายไปที่อินทิลิตี้ .. นั่นฉัน .. ฉันเพิ่งโพสต์คำตอบของฉันเมื่อฉันมีแอสเซมเบลอร์ 'ทดสอบ' เท่านั้น
Anders Cedronius

16

นี่เป็นวิธีการแก้ปัญหาอื่นสำหรับผู้ที่รักการเรียกซ้ำ

ความคิดนั้นง่าย แบ่งอินพุตครึ่งและสลับครึ่งทั้งสองต่อไปจนกว่าจะถึงบิตเดียว

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

นี่คือฟังก์ชั่นวนซ้ำเพื่อแก้ปัญหา (หมายเหตุฉันใช้ int ที่ไม่ได้ลงชื่อดังนั้นมันสามารถทำงานกับอินพุตที่มีขนาดสูงสุด (int ที่ไม่ได้ลงชื่อ) * 8 บิต

ฟังก์ชั่นวนซ้ำใช้พารามิเตอร์ 2 ตัว - ค่าที่บิตต้องถูกย้อนกลับและจำนวนบิตในค่า

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);
}

นี่คือผลลัพธ์:

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

วิธีนี้ไม่สามารถใช้กับตัวอย่าง 24 บิต (ที่ 3) ได้หรือไม่? ฉันไม่คุ้นเคยกับตัวดำเนินการ C และ bitwise แต่จากคำอธิบายของคุณเกี่ยวกับวิธีการที่ฉันคาดเดา 24-> 12-> 6-> 3 (3 บิตไม่เท่ากันเพื่อแยก) ตามที่numBitsเป็นจริงเมื่อคุณแบ่ง 3 โดย 2 สำหรับฟังก์ชัน param มันจะถูกปัดเศษเป็น 1?
เบรนแนน

13

อย่างนี้แน่นอนจะไม่ตอบเช่นแมตต์เจ แต่หวังว่ามันจะยังคงมีประโยชน์

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;
}

นี่เป็นแนวคิดเดียวกันกับอัลกอริธึมที่ดีที่สุดของ Matt ยกเว้นว่ามีคำสั่งเล็ก ๆ นี้ที่เรียกว่า BSWAP ซึ่งสลับไบต์ (ไม่ใช่บิต) ของตัวเลข 64 บิต ดังนั้น b7, b6, b5, b4, b3, b2, b1, b0 กลายเป็น b0, b1, b2, b3, b4, b5, b6, b7 เนื่องจากเรากำลังทำงานกับหมายเลข 32 บิตเราจึงจำเป็นต้องเลื่อนจำนวนไบต์ลงสลับเป็น 32 บิต เพียงแค่ปล่อยให้เราทำงานสลับ 8 บิตของแต่ละไบต์ที่ทำและ voila! เราเสร็จแล้ว

การจับเวลา: ในเครื่องของฉันอัลกอริทึมของ Matt นั้นทำงานใน ~ 0.52 วินาทีต่อการทดลอง ฉันวิ่งในเวลาประมาณ 0.42 วินาทีต่อการทดลอง เร็วขึ้น 20% ไม่เลวเลยที่ฉันคิด

หากคุณกังวลเกี่ยวกับความพร้อมของคำสั่ง BSWAP Wikipediaแสดงรายการคำสั่ง BSWAP ว่ามีการเพิ่มด้วย 80846 ซึ่งออกมาในปี 1989 ควรสังเกตว่า Wikipedia ยังระบุว่าคำสั่งนี้ใช้ได้กับการลงทะเบียนแบบ 32 บิตเท่านั้นซึ่งไม่ชัดเจน กรณีบนเครื่องของฉันมันใช้งานได้เฉพาะกับการลงทะเบียน 64 บิตเท่านั้น

วิธีนี้จะทำงานได้ดีเท่าเทียมกันสำหรับประเภทข้อมูลใด ๆ เพื่อให้วิธีการทั่วไปสามารถเล็กน้อยโดยผ่านจำนวนไบต์ที่ต้องการ:

    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;
    }

ซึ่งสามารถถูกเรียกเช่น:

    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

คอมไพเลอร์ควรจะสามารถปรับพารามิเตอร์พิเศษให้เหมาะสม (สมมติว่าคอมไพเลอร์อินไลน์ฟังก์ชั่น) และสำหรับsizeof(size_t)กรณีนั้นการเปลี่ยนกะขวาจะถูกลบออกอย่างสมบูรณ์ โปรดทราบว่าอย่างน้อย GCC จะไม่สามารถลบ BSWAP และ right-shift ถ้าผ่านsizeof(char)ได้


2
ตามปริมาณการอ้างอิงชุดคำสั่ง Intel 2A ( intel.com/content/www/us/en/processors/ ...... ) มีคำแนะนำ BSWAP สองคำสั่ง: BSWAP r32 (ทำงานกับการลงทะเบียน 32 บิต) ซึ่งเข้ารหัสเป็น 0F C8 + rd และ BSWAP r64 (ทำงานกับการลงทะเบียน 64 บิต) ซึ่งเข้ารหัสเป็น REX.W + 0F C8 + rd
Nubok

คุณบอกว่ามันสามารถใช้แบบนี้: "n = ย้อนกลับ (n, sizeof (size_t)); // reverse 64 บิต" อย่างไรก็ตามสิ่งนี้จะให้ผลลัพธ์เพียง 32 บิตเว้นแต่ว่าค่าคงที่ทั้งหมดจะขยายเป็น 64 บิตจากนั้นก็ใช้งานได้
rajkosto

@rajkosto ตั้งแต่ C ++ 11 ประเภทตัวอักษรจำนวนเต็มที่อนุญาตรวมถึงunsigned long long intอย่างน้อย 64 บิตตามที่นี่และที่นี่
SirGuy

ตกลง? ฉันแค่บอกว่าถ้าคุณต้องการให้สิ่งนี้ทำงานบนค่า 64 บิตคุณต้องขยายตัวอักษรของคุณ (ดังนั้นพวกเขาจึงเป็น 0xf0f0f0f0f0f0f0f0f0f0f0ull) มิฉะนั้นผล 32 บิตที่สูงจะเป็น 0 ทั้งหมด
rajkosto

@rajkosto อ่าฉันเข้าใจผิดความคิดเห็นแรกของคุณแล้วฉันได้แก้ไขแล้วว่าตอนนี้
SirGuy

13

คำตอบของ Anders Cedronius นำเสนอทางออกที่ยอดเยี่ยมสำหรับผู้ที่มีซีพียู x86 พร้อมการรองรับ AVX2 สำหรับแพลตฟอร์ม x86 ที่ไม่มีการสนับสนุน AVX หรือแพลตฟอร์มที่ไม่ใช่ x86 การปรับใช้อย่างใดอย่างหนึ่งต่อไปนี้ควรทำงานได้ดี

รหัสแรกเป็นตัวแปรของวิธีการแบ่งพาร์ติชันแบบคลาสสิกแบบไบรหัสเพื่อใช้ประโยชน์สูงสุดจากการใช้สำนวน shift-plus-logic ที่มีประโยชน์ในโปรเซสเซอร์ ARM ต่างๆ นอกจากนี้ยังใช้การสร้างมาสก์แบบ on-the-fly ซึ่งอาจเป็นประโยชน์สำหรับตัวประมวลผล RISC ที่ต้องการวิธีการหลายอย่างในการโหลดค่ามาสก์แบบ 32 บิต คอมไพเลอร์สำหรับแพลตฟอร์ม x86 ควรใช้การแพร่กระจายอย่างต่อเนื่องในการคำนวณมาสก์ทั้งหมดในเวลารวบรวมมากกว่าเวลาทำงาน

/* 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;
}

ในเล่ม 4A ของ "ศิลปะแห่งการเขียนโปรแกรมคอมพิวเตอร์" D. Knuth แสดงวิธีที่ชาญฉลาดในการย้อนกลับของบิตที่ค่อนข้างน่าแปลกใจที่ต้องการการดำเนินการน้อยกว่าอัลกอริทึมการแบ่งพาร์ติชันแบบไบนารีแบบดั้งเดิม หนึ่งอัลกอริทึมดังกล่าวสำหรับโอเปอแรนด์ 32 บิตที่ฉันไม่สามารถหาได้ใน TAOCP จะแสดงในเอกสารนี้บนเว็บไซต์ Delight ของแฮ็กเกอร์

/* 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 C / C ++ คอมไพเลอร์ 13.1.3.198 ทั้งสองฟังก์ชั่นดังกล่าวข้างต้นปรับเวกเตอร์XMMลงทะเบียนเป้าหมายอย่างอัตโนมัติ พวกเขายังสามารถปรับเวกเตอร์ด้วยตนเองได้โดยไม่ต้องใช้ความพยายามมาก

บน IvyBridge Xeon E3 1270v2 ของฉันโดยใช้รหัสอัตโนมัติ - เวกเตอร์ 100 ล้านuint32_tคำกลับเป็นบิตใน 0.070 วินาทีโดยใช้brev_classic()และ 0.068 brev_knuth()วินาทีโดยใช้ ฉันดูแลเพื่อให้แน่ใจว่ามาตรฐานของฉันไม่ได้ถูก จำกัด ด้วยแบนด์วิดธ์หน่วยความจำระบบ


2
@ JoelSnyder ฉันคิดว่า "หมายเลขเวทย์มนตร์" ที่คุณพูดถึงเป็นหลักbrev_knuth()? การระบุแหล่งที่มาใน PDF จาก Delight ของแฮ็กเกอร์ดูเหมือนว่าจะระบุว่าตัวเลขเหล่านี้มาจาก Knuth โดยตรง ฉันไม่สามารถอ้างว่าเข้าใจคำอธิบายของ Knuth เกี่ยวกับหลักการออกแบบพื้นฐานใน TAOCP อย่างเพียงพอเพื่ออธิบายว่าค่าคงที่ได้รับมาอย่างไรหรือจะให้ค่าคงที่ที่ได้มาและปัจจัยการเปลี่ยนแปลงสำหรับขนาดของคำโดยพลการอย่างไร
njuffa

8

สมมติว่าคุณมีอาเรย์ของบิตวิธีการเกี่ยวกับเรื่องนี้: 1. เริ่มต้นจาก MSB กดบิตลงในสแต็คหนึ่งโดยหนึ่ง 2. ป๊อปบิตจากสแต็กนี้ไปยังอาร์เรย์อื่น (หรืออาร์เรย์เดียวกันหากคุณต้องการประหยัดพื้นที่) วางบิตแรกที่ผุดลงใน MSB และไปยังบิตที่สำคัญน้อยลงจากที่นั่น

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
หนึ่งนี้ทำให้ฉันยิ้ม :) ฉันชอบที่จะเห็นมาตรฐานของการแก้ปัญหานี้ C # กับหนึ่งในคนที่ผมระบุไว้ข้างต้นในการเพิ่มประสิทธิภาพซี
แมตต์เจ

ฮ่า ๆ ... แต่เดี๋ยวก่อน! คำคุณศัพท์ 'ดีที่สุด' ใน 'อัลกอริทึมที่ดีที่สุด' เป็นสิ่งที่อัตนัยสวย: D
เฟรเดอริหลอก

7

คำสั่ง Native ARM "rbit" สามารถทำได้ด้วย 1 cpu cycle และ 1 cpu register พิเศษไม่สามารถเอาชนะได้


6

นี่ไม่ใช่งานสำหรับมนุษย์! ... แต่เหมาะสำหรับเครื่องจักร

นี่คือ 2015, 6 ปีนับจากที่คำถามนี้ถูกถามครั้งแรก คอมไพเลอร์ได้กลายเป็นผู้เชี่ยวชาญของเราและงานของเราในฐานะมนุษย์เป็นเพียงการช่วยเหลือพวกเขาเท่านั้น ดังนั้นวิธีที่ดีที่สุดที่จะให้ความตั้งใจของเรากับเครื่องคืออะไร?

การกลับตัวของบิตเป็นเรื่องธรรมดามากที่คุณต้องสงสัยว่าทำไม ISA ที่เติบโตขึ้นเรื่อย ๆ ของ x86 ไม่ได้มีคำสั่งให้ทำเช่นนี้เพียงครั้งเดียว

เหตุผล: ถ้าคุณให้ความตั้งใจจริงของคุณกับคอมไพเลอร์การกลับรายการบิตควรใช้เวลาประมาณ 20 รอบ CPUเท่านั้น ให้ฉันแสดงวิธีการย้อนกลับ () และใช้มัน:

#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;
}

รวบรวมโปรแกรมตัวอย่างนี้ด้วยรุ่น Clang> = 3.6, -O3, -march = native (ทดสอบด้วย Haswell) ให้รหัสคุณภาพงานศิลปะโดยใช้คำแนะนำ AVX2 ใหม่พร้อมเวลารันไทม์11 วินาทีในการประมวลผล ~ 1 พันล้านย้อนกลับ () นั่นคือ ~ 10 ns ต่อการย้อนกลับ () โดยมี. 5 ns รอบการทำงานของ CPU สมมติว่า 2 GHz ทำให้เราอยู่ในระดับ 20 ซีพียูที่แสนหวาน

  • คุณสามารถใส่ 10 reverse () ในเวลาที่เข้าถึง RAM ได้ครั้งเดียวสำหรับอาร์เรย์ขนาดใหญ่!
  • คุณสามารถใส่ 1 reverse () ในเวลาที่ใช้ในการเข้าถึง L2 cache LUT สองครั้ง

Caveat: โค้ดตัวอย่างนี้ควรถือเป็นเกณฑ์มาตรฐานที่ดีสำหรับไม่กี่ปี แต่ในที่สุดมันก็จะเริ่มแสดงอายุเมื่อคอมไพเลอร์ฉลาดพอที่จะปรับ main () ให้เป็นเพียงแค่พิมพ์ผลสุดท้ายแทนการคำนวณอะไรจริงๆ แต่ตอนนี้มันทำงานในการแสดงย้อนกลับ ()


Bit-reversal is so common...ฉันไม่รู้เรื่องนั้น ฉันทำงานกับรหัสที่จัดการกับข้อมูลที่ระดับบิตแทบทุกวันและฉันจำไม่ได้ว่าเคยมีความต้องการเฉพาะนี้ คุณต้องการมันในสถานการณ์อะไร - ไม่ใช่ว่ามันไม่ใช่ปัญหาที่น่าสนใจที่จะแก้ไขด้วยตัวเอง
500 - ข้อผิดพลาดเซิร์ฟเวอร์ภายใน

@ 500-InternalServerError ฉันต้องการฟังก์ชั่นนี้หลายครั้งในการอนุมานไวยากรณ์ด้วยโครงสร้างข้อมูลที่รวดเร็วและกระชับ ต้นไม้ไบนารีปกติที่เข้ารหัสเป็นบิตเรย์จะสรุปไวยากรณ์ในคำสั่ง "big endian" แต่สำหรับการวางนัยทั่วไปที่ดีกว่าถ้าคุณสร้าง tree (bitarray) ด้วย nodes ที่สลับไปมาด้วยการเปลี่ยนรูปบิตกลับรายการสตริงของไวยากรณ์ที่เรียนรู้จะอยู่ใน "endian น้อย" การสลับนั้นอนุญาตให้คุณอนุมานสตริงความยาวตัวแปรแทนที่จะเป็นขนาดจำนวนเต็มคงที่ สถานการณ์นี้ปรากฏขึ้นจำนวนมากใน FFT ที่มีประสิทธิภาพเช่นกัน: ดูen.wikipedia.org/wiki/Bit-reversal_permutation

1
ขอบคุณฉันได้จัดการกับวิธีการที่ FFT อาจมีส่วนร่วมในคำตอบของคุณ :)
500 - ข้อผิดพลาดเซิร์ฟเวอร์ภายใน

ทำไมแค่ 20 รอบ สถาปัตยกรรมแบบไหน นี่เป็นความจริงสำหรับสถาปัตยกรรม VLIW กว้างพิเศษแห่งอนาคตจนกระทั่งมนุษย์และผู้สืบทอดของเราตายไปหรือไม่? แค่คำถามไม่มีคำตอบ ... ลงไปสู่นรกอีกครั้ง
Quonux


5

ฉันรู้ว่ามันไม่ใช่ C แต่ asm:

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

ใช้งานได้กับบิตพกพาดังนั้นคุณสามารถบันทึกค่าสถานะได้เช่นกัน


1
ฉันเดาว่าคุณสามารถใช้คำหลักasmซึ่งจะค่อนข้างเร็ว
Tom

มันใช้งานไม่ได้ ฉันคิดว่าคุณต้องการrclเปลี่ยน CF เป็นvar1แทนที่จะshlไม่อ่านธง (หรือ adc dx,dx) แม้จะมีการแก้ไขปัญหานี้ แต่ก็ช้าอย่างน่าขันโดยใช้loopคำสั่งช้าและเก็บไว้var1ในหน่วยความจำ! ที่จริงฉันคิดว่านี่ควรจะสร้างผลลัพธ์ใน AX แต่มันจะบันทึก / เรียกคืนค่าเดิมของ AX เหนือผลลัพธ์
Peter Cordes

4

การใช้งานกับหน่วยความจำต่ำและเร็วที่สุด

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

ทีนี้นี่เป็นพื้นฐานเหมือนกับ "reverse ()" ตัวแรก แต่มันเป็น 64 บิตและต้องการเพียงมาสก์ทันทีที่จะโหลดจากสตรีมคำสั่ง GCC สร้างรหัสโดยไม่ข้ามดังนั้นสิ่งนี้ควรจะค่อนข้างเร็ว

#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

ฉันอยากรู้ว่าการหมุนรอบตัวแบบดิบจะเร็วแค่ไหน บนเครื่องของฉัน (i7 @ 2600) ค่าเฉลี่ยสำหรับการทำซ้ำ 1,500,150,000 ครั้งคือ27.28 ns(มากกว่าชุดสุ่มของจำนวนเต็ม 131,071 64 บิตที่ 131,071

ข้อดี: จำนวนหน่วยความจำที่ต้องการน้อยและรหัสนั้นง่าย ฉันจะบอกว่ามันไม่ใหญ่มากเช่นกัน เวลาที่ต้องการสามารถคาดการณ์ได้และคงที่สำหรับอินพุตใด ๆ (การดำเนินการ SHIFT ทางคณิตศาสตร์ 128 ครั้ง + ตรรกะลอจิคัล AND 64 และการดำเนินการ + 64 ตรรกะหรือการดำเนินการ)

ฉันเปรียบเทียบกับเวลาที่ดีที่สุดที่ @Matt J - ใครมีคำตอบที่ยอมรับ ถ้าฉันอ่านคำตอบของเขาอย่างถูกต้องสิ่งที่ดีที่สุดที่เขาได้รับคือ0.631739วินาทีสำหรับ1,000,000การวนซ้ำซึ่งนำไปสู่การเฉลี่ย631 nsต่อการหมุน

ข้อมูลโค้ดที่ฉันใช้คืออันนี้ด้านล่าง:

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 ฉันไม่แน่ใจว่าฉันเข้าใจคำถามของคุณหรือไม่
marian adam

ขอบคุณที่สังเกตข้อผิดพลาดฉันแก้ไขตัวอย่างโค้ดที่ให้ไว้
marian adam

3

คุณอาจต้องการใช้ไลบรารีแม่แบบมาตรฐาน อาจช้ากว่ารหัสที่กล่าวถึงด้านบน อย่างไรก็ตามดูเหมือนว่าฉันจะชัดเจนและเข้าใจง่ายขึ้น

 #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

ทั่วไป

รหัส C การใช้ข้อมูลอินพุต 1 ไบต์เป็นตัวเลข

    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);

คำถามที่ถามว่า "มีประสิทธิภาพมากที่สุด" ไม่ใช่ "ง่าย / ตรงไปตรงมา"
Peter Cordes

1

วิธีการเกี่ยวกับต่อไปนี้:

    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;
    }

เล็กและง่าย (แต่ 32 บิตเท่านั้น)


คำถามถามหา "ประสิทธิภาพสูงสุด"; เราสามารถออกกฎวนซ้ำ 32 ครั้ง (และโดยเฉพาะอย่างยิ่งไม่ได้ขยับหน้ากากและต้องเปลี่ยนผลลัพธ์ลงไปที่ LSB)
Peter Cordes

1

ฉันคิดว่านี่เป็นหนึ่งในวิธีที่ง่ายที่สุดในการย้อนกลับบิต โปรดแจ้งให้เราทราบหากมีข้อบกพร่องใด ๆ ในตรรกะนี้ โดยพื้นฐานแล้วในตรรกะนี้เราจะตรวจสอบค่าบิตในตำแหน่ง กำหนดบิตถ้าค่าเป็น 1 ในตำแหน่งที่กลับด้าน

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;    
}    

คำถามนั้นถามว่า "มีประสิทธิภาพมากที่สุด" ไม่ใช่ "ง่าย / ตรงไปตรงมา"
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;
}

ที่น่าสนใจ แต่การหารด้วยตัวแปรรันไทม์ช้า kอยู่เสมอพลังของ 2 แต่คอมไพเลอร์อาจจะไม่พิสูจน์และเปลี่ยนเป็นบิตสแกน / กะ
Peter Cordes

0

ฉันคิดว่าวิธีที่ง่ายที่สุดที่ฉันรู้มีดังนี้ MSBเป็นอินพุทและLSBเอาท์พุท 'กลับรายการ':

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

โซลูชันแบบวนซ้ำอีกตัวที่ออกอย่างรวดเร็วเมื่อจำนวนต่ำ (ใน C ++ สำหรับหลายประเภท)

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;
}

หรือใน C สำหรับ int ที่ไม่ได้ลงนาม

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

ดูเหมือนว่าโพสต์อื่น ๆ มีความกังวลเกี่ยวกับความเร็ว (เช่นดีที่สุด = เร็วที่สุด) แล้วความเรียบง่ายล่ะ? พิจารณา:

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;
}

และหวังว่าคอมไพเลอร์ที่ฉลาดจะปรับให้เหมาะสมสำหรับคุณ

หากคุณต้องการย้อนกลับรายการบิตที่ยาวขึ้น (มีsizeof(char) * nบิต) คุณสามารถใช้ฟังก์ชันนี้เพื่อรับ:

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]);
    }
}

สิ่งนี้จะย้อนกลับ [10000000, 10101010] เป็น [01010101, 00000001]


คุณมี 3 กะในวงด้านใน ith_bit = (c >> i) & 1บันทึกเป็นหนึ่งเดียวกับ นอกจากนี้ยังบันทึก SUB ด้วยการขยับreversed_charแทนการเปลี่ยนบิตเว้นแต่ว่าคุณคาดหวังว่ามันจะรวบรวมบน x86 ถึงsub something/ bts reg,regเพื่อตั้งค่าบิตที่ n ในการลงทะเบียนปลายทาง
Peter Cordes

-1

บิตกลับรายการในรหัสเทียม

แหล่งที่มา -> ไบต์ที่จะถูกย้อนกลับ b00101100 ปลายทาง -> ย้อนกลับยังต้องเป็นประเภทที่ไม่ได้ลงชื่อดังนั้นบิตการลงชื่อไม่ได้ถูกนำเสนอ

คัดลอกไปที่ temp ดังนั้นต้นฉบับจะไม่ได้รับผลกระทบและยังต้องเป็นประเภทที่ไม่ได้ลงชื่อดังนั้นบิตของการลงชื่อจะไม่ถูกเปลี่ยนเป็นแบบอัตโนมัติ

bytecopy = b0010110

LOOP8: // ทำสิ่งนี้ 8 ครั้งทดสอบว่า bytecopy เป็น <0 (ค่าลบ)

    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

ทางออกที่ง่ายของฉัน

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
อะไรนะi? นอกจากนี้ค่าคงที่เวทมนตร์* 4คืออะไร? มันคือCHAR_BIT / 2อะไร
Peter Cordes

-1

นี่คือ 32 บิตเราต้องเปลี่ยนขนาดถ้าเราพิจารณา 8 บิต

    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);
    }

การอ่านจำนวนเต็มอินพุต "num" ในคำสั่ง LSB-> คำสั่ง MSB และการจัดเก็บใน num_reverse ใน MSB-> คำสั่ง LSB


1
คุณควรเพิ่มคำอธิบายลงในโค้ดเพื่อให้เข้าใจได้ง่ายขึ้น
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
โดยทั่วไปแล้วคำตอบจะมีประโยชน์มากขึ้นหากพวกเขามีคำอธิบายว่ารหัสมีไว้ทำอะไรและเหตุใดจึงแก้ปัญหาได้
IKavanagh
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.