Thuật toán hiệu quả nhất để đạt được những điều sau đây là gì:
0010 0000 => 0000 0100
Việc chuyển đổi là từ MSB-> LSB sang LSB-> MSB. Tất cả các bit phải được đảo ngược; đó là, đây không phải là sự hoán đổi cuối cùng.
Thuật toán hiệu quả nhất để đạt được những điều sau đây là gì:
0010 0000 => 0000 0100
Việc chuyển đổi là từ MSB-> LSB sang LSB-> MSB. Tất cả các bit phải được đảo ngược; đó là, đây không phải là sự hoán đổi cuối cùng.
Câu trả lời:
LƯU Ý : Tất cả các thuật toán bên dưới đều bằng C, nhưng nên có thể chuyển sang ngôn ngữ bạn chọn (chỉ cần không nhìn vào tôi khi chúng không nhanh như vậy :)
Bộ nhớ thấp (máy 32 bit int
, 32 bit) (từ đây ):
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));
}
Từ trang Bit Twiddling Hacks nổi tiếng :
Nhanh nhất (bảng tra cứu) :
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]];
Bạn có thể mở rộng ý tưởng này thành 64 bit int
hoặc đánh đổi bộ nhớ để lấy tốc độ (giả sử Bộ đệm dữ liệu L1 của bạn đủ lớn) và đảo ngược 16 bit mỗi lần với bảng tra cứu mục nhập 64K.
Đơn giản
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
Nhanh hơn (bộ xử lý 32 bit)
unsigned char b = x;
b = ((b * 0x0802LU & 0x22110LU) | (b * 0x8020LU & 0x88440LU)) * 0x10101LU >> 16;
Nhanh hơn (bộ xử lý 64 bit)
unsigned char b; // reverse this (8-bit) byte
b = (b * 0x0202020202ULL & 0x010884422010ULL) % 1023;
Nếu bạn muốn làm điều này trên 32 bit int
, chỉ cần đảo ngược các bit trong mỗi byte và đảo ngược thứ tự của các byte. Đó là:
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);
Tôi đã điểm chuẩn hai giải pháp hứa hẹn nhất là bảng tra cứu và bitwise-AND (giải pháp đầu tiên). Máy thử nghiệm là máy tính xách tay w / 4GB DDR2-800 và Core 2 Duo T7500 @ 2.4GHz, Cache 4MB L2; YMMV. Tôi đã sử dụng gcc 4.3.2 trên Linux 64 bit. OpenMP (và các ràng buộc GCC) đã được sử dụng cho bộ định thời độ phân giải cao.
đảo ngượ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;
}
đảo ngược_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;
}
Tôi đã thử cả hai cách tiếp cận ở một số tối ưu hóa khác nhau, chạy 3 thử nghiệm ở mỗi cấp độ và mỗi thử nghiệm đảo ngược 100 triệu ngẫu nhiên unsigned ints
. Đối với tùy chọn bảng tra cứu, tôi đã thử cả hai lược đồ (tùy chọn 1 và 2) được đưa ra trên trang hack bitwise. Kết quả được hiển thị dưới đây.
Bitwise VÀ
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
Bảng tra cứu (tùy chọn 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
Bảng tra cứu (tùy chọn 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
Sử dụng bảng tra cứu, với tùy chọn 1 (địa chỉ byte chậm không đáng ngạc nhiên) nếu bạn quan tâm đến hiệu suất. Nếu bạn cần vắt từng byte bộ nhớ cuối cùng ra khỏi hệ thống của mình (và bạn có thể, nếu bạn quan tâm đến hiệu suất đảo ngược bit), các phiên bản tối ưu hóa của phương pháp bitwise-AND cũng không quá tồi tệ.
Vâng, tôi biết mã điểm chuẩn là một bản hack hoàn chỉnh. Đề xuất về cách cải thiện nó được chào đón nhiều hơn. Những điều tôi biết về:
ld
xuất hiện một số lỗi xác định lại biểu tượng điên rồ), vì vậy tôi không tin rằng mã được tạo được điều chỉnh cho kiến trúc vi mô của tôi.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: Tôi cũng đã thử sử dụng uint64_t
các loại trên máy của mình để xem liệu có bất kỳ tăng hiệu suất nào không. Hiệu suất nhanh hơn khoảng 10% so với 32 bit và gần như giống hệt nhau cho dù bạn chỉ sử dụng loại 64 bit để đảo ngược bit trên hai loại 32 bit int
một lần hay bạn thực sự đảo ngược bit trong một nửa số 64- giá trị bit. Mã lắp ráp được hiển thị bên dưới (đối với trường hợp trước, đảo ngược bit cho hai loại 32 bit int
cùng một lúc):
.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
Chủ đề này đã thu hút sự chú ý của tôi vì nó giải quyết một vấn đề đơn giản đòi hỏi nhiều công việc (chu kỳ CPU) ngay cả đối với một CPU hiện đại. Và một ngày, tôi cũng đứng đó với cùng một vấn đề ¤ #% "#". Tôi đã phải lật hàng triệu byte. Tuy nhiên, tôi biết tất cả các hệ thống mục tiêu của mình đều dựa trên Intel hiện đại, vì vậy hãy bắt đầu tối ưu hóa đến cùng cực !!!
Vì vậy, tôi đã sử dụng mã tra cứu của Matt J làm cơ sở. hệ thống tôi đang chuẩn là i7 haswell 4700eq.
Tra cứu của Matt J bitflipping 400 000 000 byte: Khoảng 0,272 giây.
Sau đó, tôi đã tiếp tục và cố gắng xem liệu trình biên dịch ISPC của Intel có thể véc tơ các số liệu trong đảo ngược không.
Tôi sẽ không làm bạn nhàm chán với những phát hiện của tôi ở đây vì tôi đã cố gắng rất nhiều để giúp trình biên dịch tìm kiếm công cụ, dù sao tôi cũng kết thúc với hiệu suất khoảng 0,15 giây để bitflip 400 000 000 byte. Đó là một mức giảm tuyệt vời nhưng đối với ứng dụng của tôi vẫn còn quá chậm ..
Vì vậy, mọi người hãy để tôi trình bày bitflipper dựa trên Intel nhanh nhất trên thế giới. Đồng hồ tại:
Thời gian để bitflip 400000000 byte: 0,050082 giây !!!!!
// 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;
}
Các printf là để gỡ lỗi ..
Đây là đặc điểm:
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
Mã này lấy 32 byte sau đó che dấu các nibble. Nibble cao được dịch chuyển sang phải 4. Sau đó, tôi sử dụng vpshufb và ymm4 / ymm3 làm bảng tra cứu. Tôi có thể sử dụng một bảng tra cứu duy nhất nhưng sau đó tôi sẽ phải dịch chuyển sang trái trước khi OR lại các nibble lại với nhau.
Thậm chí có những cách nhanh hơn để lật các bit. Nhưng tôi bị ràng buộc với một luồng và CPU nên đây là tốc độ nhanh nhất tôi có thể đạt được. Bạn có thể làm một phiên bản nhanh hơn?
Vui lòng không bình luận về việc sử dụng các lệnh Tương đương nội tại của Trình biên dịch Intel C / C ++ ...
pshub
, bởi vì sau tất cả các popcount tốt nhất cũng được thực hiện với nó! Tôi đã viết nó ở đây nếu không phải cho bạn. Thanh danh.
popcnt
, tzcnt
và pext
tất cả trên cổng 1. Vì vậy, mỗi pext
hoặc tzcnt
chi phí bạn một popcnt
số thông. Nếu dữ liệu của bạn nóng trong bộ đệm L1D, cách nhanh nhất để đếm một mảng trên CPU Intel là với AVX2 pshufb. (Ryzen có 4 mỗi đồng hồ popcnt
thông vì vậy đó là lẽ tối ưu, nhưng Bulldozer gia đình có một mỗi 4 đồng hồ popcnt r64,r64
thông ... agner.org/optimize ).
Đây là một giải pháp khác cho những người yêu thích đệ quy.
Ý tưởng rất đơn giản. Chia một nửa đầu vào và hoán đổi hai nửa, tiếp tục cho đến khi đạt được một bit.
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
Đây là một hàm đệ quy để giải quyết nó. (Lưu ý tôi đã sử dụng số nguyên không dấu, vì vậy nó có thể hoạt động cho các đầu vào lên đến sizeof (số nguyên không dấu) * 8 bit.
Hàm đệ quy có 2 tham số - Giá trị mà các bit cần được đảo ngược và số bit trong giá trị.
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);
}
Đây là đầu ra:
Bit Reversal Input = 0x55 Output = 0xaa
Bit Reversal Input = 0xabcd Output = 0xb3d5
Bit Reversal Input = 0x123456 Output = 0x651690
Bit Reversal Input = 0x11223344 Output = 0x22cc4488
numBits
int, khi bạn chia 3 cho 2 cho hàm param thì nó sẽ được làm tròn xuống 1?
Chà, đây chắc chắn sẽ không phải là một câu trả lời như của Matt J nhưng hy vọng nó vẫn hữu ích.
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;
}
Đây chính xác là ý tưởng tương tự như thuật toán tốt nhất của Matt ngoại trừ việc có một lệnh nhỏ này được gọi là BSWAP, hoán đổi các byte (không phải các bit) của một số 64 bit. Vậy b7, b6, b5, b4, b3, b2, b1, b0 trở thành b0, b1, b2, b3, b4, b5, b6, b7. Vì chúng tôi đang làm việc với số 32 bit, chúng tôi cần chuyển số byte được hoán đổi xuống 32 bit. Điều này chỉ để lại cho chúng ta nhiệm vụ hoán đổi 8 bit của mỗi byte được thực hiện và thì đấy! đã được thực hiện.
Thời gian: trên máy của tôi, thuật toán của Matt chạy trong ~ 0,52 giây mỗi lần dùng thử. Tôi chạy trong khoảng 0,42 giây mỗi thử nghiệm. 20% nhanh hơn không phải là xấu tôi nghĩ.
Nếu bạn lo lắng về tính khả dụng của hướng dẫn BSWAP Wikipedia liệt kê hướng dẫn BSWAP được thêm vào với 80846 được phát hành vào năm 1989. Cần lưu ý rằng Wikipedia cũng chỉ ra rằng hướng dẫn này chỉ hoạt động trên các thanh ghi 32 bit rõ ràng không phải là Trường hợp trên máy của tôi, nó chỉ hoạt động rất nhiều trên các thanh ghi 64 bit.
Phương thức này sẽ hoạt động tốt như nhau đối với bất kỳ kiểu dữ liệu tích phân nào, vì vậy phương thức có thể được khái quát hóa một cách tầm thường bằng cách chuyển số byte mong muốn:
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;
}
mà sau đó có thể được gọi là:
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
Trình biên dịch sẽ có thể tối ưu hóa tham số phụ đi (giả sử trình biên dịch nội tuyến hàm) và trong sizeof(size_t)
trường hợp dịch chuyển phải sẽ bị loại bỏ hoàn toàn. Lưu ý rằng ít nhất GCC không thể xóa BSWAP và dịch chuyển sang phải nếu được thông qua sizeof(char)
.
Câu trả lời của Anders Cedronius cung cấp một giải pháp tuyệt vời cho những người có CPU x86 có hỗ trợ AVX2. Đối với các nền tảng x86 không có hỗ trợ AVX hoặc các nền tảng không phải là x86, một trong hai cách triển khai sau sẽ hoạt động tốt.
Mã đầu tiên là một biến thể của phương pháp phân vùng nhị phân cổ điển, được mã hóa để tối đa hóa việc sử dụng thành ngữ shift-plus-logic hữu ích trên các bộ xử lý ARM khác nhau. Ngoài ra, nó sử dụng việc tạo mặt nạ đang hoạt động có thể có lợi cho các bộ xử lý RISC, nếu không yêu cầu nhiều hướng dẫn để tải từng giá trị mặt nạ 32 bit. Trình biên dịch cho nền tảng x86 nên sử dụng việc truyền liên tục để tính toán tất cả các mặt nạ tại thời gian biên dịch thay vì thời gian chạy.
/* 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;
}
Trong tập 4A của "Nghệ thuật lập trình máy tính", D. Knuth cho thấy những cách đảo ngược bit thông minh, phần nào đáng ngạc nhiên đòi hỏi ít thao tác hơn các thuật toán phân vùng nhị phân cổ điển. Một thuật toán như vậy cho toán hạng 32 bit, mà tôi không thể tìm thấy trong TAOCP, được hiển thị trong tài liệu này trên trang web của Hacker 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;
}
Sử dụng trình biên dịch C / C ++ của trình biên dịch Intel 13.1.3.198, cả hai chức năng trên đều tự động vector hóa các XMM
thanh ghi nhắm mục tiêu độc đáo . Họ cũng có thể được vector hóa thủ công mà không cần nhiều nỗ lực.
Trên IvyBridge Xeon E3 1270v2 của tôi, sử dụng mã được tự động hóa, 100 triệu uint32_t
từ được đảo ngược bit trong 0,070 giây khi sử dụng brev_classic()
và 0,068 giây khi sử dụng brev_knuth()
. Tôi đã cẩn thận để đảm bảo rằng điểm chuẩn của tôi không bị giới hạn bởi băng thông bộ nhớ hệ thống.
brev_knuth()
? Sự quy kết trong bản PDF từ Hacker Delight dường như chỉ ra rằng những con số này là trực tiếp từ chính Knuth. Tôi không thể tuyên bố đã hiểu mô tả của Knuth về các nguyên tắc thiết kế cơ bản trong TAOCP để giải thích cách các hằng số được tạo ra, hoặc cách người ta đi về các hằng số và các hệ số dịch chuyển cho các kích thước từ tùy ý.
Giả sử rằng bạn có một mảng các bit, làm thế nào về điều này: 1. Bắt đầu từ MSB, đẩy từng bit một chồng lên nhau. 2. Các bit pop từ ngăn xếp này vào một mảng khác (hoặc cùng một mảng nếu bạn muốn tiết kiệm không gian), đặt bit popped đầu tiên vào MSB và chuyển sang các bit ít quan trọng hơn từ đó.
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();
}
Đây không phải là việc làm cho một con người! ... nhưng hoàn hảo cho một chiếc máy
Đây là năm 2015, 6 năm kể từ khi câu hỏi này lần đầu tiên được hỏi. Trình biên dịch đã trở thành bậc thầy của chúng tôi và công việc của chúng tôi là con người chỉ để giúp đỡ họ. Vì vậy, cách tốt nhất để đưa ra ý định của chúng tôi cho máy là gì?
Sự đảo ngược bit rất phổ biến đến nỗi bạn phải tự hỏi tại sao ISA ngày càng phát triển của x86 không bao gồm một hướng dẫn để thực hiện điều đó một lần.
Lý do: nếu bạn đưa ra ý định ngắn gọn thực sự của mình cho trình biên dịch, việc đảo ngược bit chỉ nên thực hiện ~ 20 chu kỳ CPU . Hãy để tôi chỉ cho bạn cách tạo đảo ngược () và sử dụng nó:
#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;
}
Biên dịch chương trình mẫu này với phiên bản Clang> = 3.6, -O3, -march = bản địa (đã thử nghiệm với Haswell), cung cấp mã chất lượng tác phẩm nghệ thuật bằng cách sử dụng các hướng dẫn AVX2 mới, với thời gian chạy 11 giây xử lý ~ 1 tỷ đảo ngược (). Đó là ~ 10 ns mỗi lần đảo ngược (), với chu kỳ CPU 0,5 ns giả sử 2 GHz đưa chúng ta vào 20 chu kỳ CPU ngọt ngào.
Hãy cẩn thận: mã mẫu này sẽ giữ mức chuẩn trong một vài năm, nhưng cuối cùng nó sẽ bắt đầu hiển thị tuổi của nó khi trình biên dịch đủ thông minh để tối ưu hóa main () để chỉ in kết quả cuối cùng thay vì thực sự tính toán bất cứ điều gì. Nhưng bây giờ nó hoạt động trong hiển thị ngược ().
Bit-reversal is so common...
Tôi không biết về điều đó. Tôi làm việc với mã liên quan đến dữ liệu ở cấp độ bit hầu như mỗi ngày và tôi không thể nhớ mình đã từng có nhu cầu cụ thể này. Trong những kịch bản nào bạn cần nó? - Không phải là vấn đề thú vị để tự giải quyết.
Tất nhiên, nguồn gốc rõ ràng của các bản hack bit-twiddling là ở đây: http://graphics.stanford.edu/~seander/bithacks.html#BitReverseObingly
Tôi biết đó không phải là C mà là:
var1 dw 0f0f0
clc
push ax
push cx
mov cx 16
loop1:
shl var1
shr ax
loop loop1
pop ax
pop cx
Điều này hoạt động với bit carry, vì vậy bạn cũng có thể lưu cờ
rcl
chuyển CF vào var1
, thay vì chỉ shl
không đọc cờ. (Hoặc adc dx,dx
). Ngay cả với bản sửa lỗi đó, điều này rất chậm một cách lố bịch, sử dụng loop
hướng dẫn chậm và lưu giữ var1
trong bộ nhớ! Trên thực tế tôi nghĩ rằng điều này được cho là tạo ra đầu ra trong AX, nhưng nó tiết kiệm / khôi phục giá trị cũ của AX so với kết quả.
Chà, về cơ bản nó giống như "Reverse ()" đầu tiên nhưng nó là 64 bit và chỉ cần một mặt nạ ngay lập tức được tải từ luồng lệnh. GCC tạo mã mà không cần nhảy, vì vậy việc này sẽ khá nhanh.
#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;
}
Tôi tò mò sẽ nhanh như thế nào khi quay thô. Trên máy của tôi (i7 @ 2600), trung bình cho 1.500.150.000 lần lặp là 27.28 ns
(trên một bộ ngẫu nhiên gồm 131.071 số nguyên 64 bit).
Ưu điểm: dung lượng bộ nhớ cần thiết rất ít và mã đơn giản. Tôi cũng sẽ nói nó không lớn lắm. Thời gian cần thiết là có thể dự đoán và không đổi cho bất kỳ đầu vào nào (128 phép toán SHift số học + 64 phép toán AND logic + 64 phép toán OR logic).
Tôi đã so sánh với thời gian tốt nhất mà @Matt J có được - người có câu trả lời được chấp nhận. Nếu tôi đọc chính xác câu trả lời của anh ta, điều tốt nhất anh ta có được là 0.631739
vài giây cho các 1,000,000
lần lặp lại, dẫn đến trung bình 631 ns
mỗi vòng quay.
Đoạn mã tôi đã sử dụng là đoạn mã dưới đây:
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);
}
Bạn có thể muốn sử dụng thư viện mẫu tiêu chuẩn. Nó có thể chậm hơn mã đã đề cập ở trên. Tuy nhiên, dường như tôi rõ ràng và dễ hiểu hơn.
#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;
}
Chung
Mã C. Sử dụng num dữ liệu đầu vào 1 byte làm ví dụ.
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);
Làm thế nào về những điều sau đây:
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;
}
Nhỏ và dễ dàng (mặc dù, chỉ 32 bit).
Tôi nghĩ rằng đây là một trong những cách đơn giản nhất để đảo ngược bit. xin vui lòng cho tôi biết nếu có bất kỳ lỗ hổng trong logic này. về cơ bản trong logic này, chúng tôi kiểm tra giá trị của bit ở vị trí. đặt bit nếu giá trị là 1 trên vị trí đảo ngược.
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;
}
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
luôn là sức mạnh của 2, nhưng trình biên dịch có thể sẽ không chứng minh điều đó và biến nó thành quét / dịch chuyển bit.
Tôi nghĩ rằng phương pháp đơn giản nhất mà tôi biết sau đây. MSB
là đầu vào và LSB
là đầu ra 'đảo ngược':
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.
// 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
Một giải pháp dựa trên vòng lặp khác thoát ra nhanh chóng khi số lượng thấp (trong C ++ cho nhiều loại)
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;
}
hoặc trong C cho một số nguyên không dấu
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;
}
Có vẻ như nhiều bài viết khác quan tâm đến tốc độ (tức là tốt nhất = nhanh nhất). Còn sự đơn giản thì sao? Xem xét:
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;
}
và hy vọng rằng trình biên dịch thông minh sẽ tối ưu hóa cho bạn.
Nếu bạn muốn đảo ngược danh sách bit dài hơn (chứa sizeof(char) * n
bit), bạn có thể sử dụng chức năng này để nhậ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]);
}
}
Điều này sẽ đảo ngược [10000000, 10101010] thành [01010101, 00000001].
ith_bit = (c >> i) & 1
. Đồng thời lưu SUB bằng cách dịch chuyển reversed_char
thay vì dịch chuyển bit, trừ khi bạn hy vọng nó sẽ biên dịch trên x86 thành sub something
/ bts reg,reg
để đặt bit thứ n trong thanh ghi đích.
Đảo ngược bit trong mã giả
nguồn -> byte được đảo ngược đích b00101100 -> đảo ngược, cũng cần phải là loại không dấu để bit dấu không bị đẩy xuống
sao chép vào temp để bản gốc không bị ảnh hưởng, cũng cần phải là loại không dấu để bit dấu không bị dịch chuyển trong tự động
bytecopy = b0010110
LOOP8: // thực hiện kiểm tra 8 lần này nếu nội dung bằng <0 (âm)
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.
Giải pháp đơn giản của tôi
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;
i
? Ngoài ra, ma thuật đó là * 4
gì? Phải CHAR_BIT / 2
không
Đây là 32 bit, chúng ta cần thay đổi kích thước nếu xem xét 8 bit.
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);
}
Đọc số nguyên đầu vào "num" theo thứ tự LSB-> MSB và lưu trữ bằng num numverse theo thứ tự MSB-> LSB.
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;
}