我需要以相反的顺序重写大约4KB的数据,在位级别(最后一个字节的最后一位成为第一个字节的第一位),尽可能快。有没有聪明的小册子呢?
基本原理:数据是嵌入式设备中LCD屏幕的显示内容,通常以屏幕位于肩膀上的方式定位。屏幕有“6点钟”的方向,可以从下面看 - 就像平躺或挂在眼睛上方。这可以通过将屏幕旋转180度来固定,但是我需要从屏幕的左上角开始反转屏幕数据(由库生成),即1位= 1像素。 CPU功能不是很强大,设备已经有足够的工作量,加上一秒钟的几帧,所以性能是个问题。 RAM没那么多。
编辑: 单核,ARM 9系列。 64MB,(缩小到32MB以后),Linux。数据通过8位IO端口从系统存储器推送到LCD驱动程序。
CPU为32位,在字长处的性能要比字节级好得多。
答案 0 :(得分:22)
有一种经典的方法可以做到这一点。假设unsigned int是你的32位字。我正在使用C99,因为restrict关键字允许编译器在此速度关键代码中执行额外的优化,否则这些代码将不可用。这些关键字通知编译器“src”和“dest”不重叠。这也假设你正在复制整数个单词,如果你不是,那么这只是一个开始。
我也不知道哪个位移/旋转基元在ARM上速度快,哪些速度慢。这是需要考虑的事情。如果您需要更高的速度,请考虑从C编译器中反汇编输出并从那里开始。如果使用GCC,请尝试使用O2,O3和Os来查看哪一个最快。您可以通过同时执行两个单词来减少管道中的停顿。
每个单词使用23次操作,不计算加载和存储。但是,这23个操作都非常快,并且没有一个访问内存。我不知道查找表是否会更快。
void
copy_rev(unsigned int *restrict dest,
unsigned int const *restrict src,
unsigned int n)
{
unsigned int i, x;
for (i = 0; i < n; ++i) {
x = src[i];
x = (x >> 16) | (x << 16);
x = ((x >> 8) & 0x00ff00ffU) | ((x & 0x00ff00ffU) << 8);
x = ((x >> 4) & 0x0f0f0f0fU) | ((x & 0x0f0f0f0fU) << 4);
x = ((x >> 2) & 0x33333333U) | ((x & 0x33333333U) << 2);
x = ((x >> 1) & 0x55555555U) | ((x & 0x555555555) << 1);
dest[n-1-i] = x;
}
}
此页面是一个很好的参考:http://graphics.stanford.edu/~seander/bithacks.html#BitReverseObvious
最后注意:查看ARM程序集引用,有一个“REV”操作码,它反转一个字中的字节顺序。这将使每个循环中的7个操作脱离上述代码。
答案 1 :(得分:16)
最快的方法可能是将所有可能的字节值的反转存储在查找表中。该表只需要256个字节。
答案 2 :(得分:14)
构建一个256元素的字节值查找表,与其索引位反转。
{0x00,0x80,0x40,0xc0等}
然后使用每个字节作为查找表的索引来遍历数组复制。
如果您正在编写汇编语言,则x86指令集具有XLAT指令,只执行此类查找。虽然它实际上可能不比现代处理器上的C代码快。
如果从两端向中间迭代,则可以执行此操作。由于缓存效应,您可能会发现交换16字节块(假设16字节缓存行)更快。
这是基本代码(不包括缓存行优化)
// bit reversing lookup table
typedef unsigned char BYTE;
extern const BYTE g_RevBits[256];
void ReverseBitsInPlace(BYTE * pb, int cb)
{
int iter = cb/2;
for (int ii = 0, jj = cb-1; ii < iter; ++ii, --jj)
{
BYTE b1 = g_RevBits[pb[ii]];
pb[ii] = g_RevBits[pb[jj]];
pb[jj] = b1;
}
if (cb & 1) // if the number of bytes was odd, swap the middle one in place
{
pb[cb/2] = g_RevBits[pb[cb/2]];
}
}
// initialize the bit reversing lookup table using macros to make it less typing.
#define BITLINE(n) \
0x0##n, 0x8##n, 0x4##n, 0xC##n, 0x2##n, 0xA##n, 0x6##n, 0xE##n,\
0x1##n, 0x9##n, 0x5##n, 0xD##n, 0x3##n, 0xB##n, 0x7##n, 0xF##n,
const BYTE g_RevBits[256] = {
BITLINE(0), BITLINE(8), BITLINE(4), BITLINE(C),
BITLINE(2), BITLINE(A), BITLINE(6), BITLINE(E),
BITLINE(1), BITLINE(9), BITLINE(5), BITLINE(D),
BITLINE(3), BITLINE(B), BITLINE(7), BITLINE(F),
};
答案 3 :(得分:9)
Bit Twiddling Hacks网站是解决这类问题的良好起点。看看here进行快速位反转。然后由你决定将它应用于你的内存块的每个字节/字。
修改强>
受到Dietrich Epps回答并查看ARM instruction set的启发,有一个RBIT
操作码可以反转寄存器中包含的位。因此,如果性能至关重要,您可以考虑使用一些汇编代码。
答案 4 :(得分:3)
循环遍历数组的一半,转换并交换字节。
for( int i = 0; i < arraySize / 2; i++ ) {
char inverted1 = invert( array[i] );
char inverted2 = invert( array[arraySize - i - 1] );
array[i] = inverted2;
array[arraySize - i - 1] = inverted1;
}
对于转换使用预先计算的表 - 2个 CHAR_BIT (CHAR_BIT
的数组很可能是8个)元素,其中位置“I”是具有值“I”的字节的结果存储反转。这将是非常快的 - 一次通过 - 并且表格仅消耗2 CHAR_BIT 。
答案 5 :(得分:3)
看起来这个代码在我的i7 XPS 8500机器上每位交换大约需要50个时钟。一百万次阵翻翻7.6秒。单线程。它根据1s和0s的模式打印一些ASCI艺术品。在使用图形编辑器反转位阵列后,我将图片旋转了180度,它们看起来与我相同。双反转图像与原始图像相同。
至于加号,它是一个完整的解决方案。它将位从位阵列的后面交换到前面,而不是在整数/字节上运行,然后需要在数组中交换整数/字节。
此外,这是一个通用的位库,因此您可能会发现将来解决其他更普通的问题很方便。
是否和接受的答案一样快?我认为它很接近,但没有工作代码来进行基准测试就不可能了。随意剪切和粘贴此工作程序。
// Reverse BitsInBuff.cpp : Defines the entry point for the console application.
#include "stdafx.h"
#include "time.h"
#include "memory.h"
//
// Manifest constants
#define uchar unsigned char
#define BUFF_BYTES 510 //400 supports a display of 80x40 bits
#define DW 80 // Display Width
// ----------------------------------------------------------------------------
uchar mask_set[] = { 0x01, 0x02, 0x04, 0x08, 0x10, 0x20, 0x40, 0x80 };
uchar mask_clr[] = { 0xfe, 0xfd, 0xfb, 0xf7, 0xef, 0xdf, 0xbf, 0x7f };
//
// Function Prototypes
static void PrintIntBits(long x, int bits);
void BitSet(uchar * BitArray, unsigned long BitNumber);
void BitClr(uchar * BitArray, unsigned long BitNumber);
void BitTog(uchar * BitArray, unsigned long BitNumber);
uchar BitGet(uchar * BitArray, unsigned long BitNumber);
void BitPut(uchar * BitArray, unsigned long BitNumber, uchar value);
//
uchar *ReverseBitsInArray(uchar *Buff, int BitKnt);
static void PrintIntBits(long x, int bits);
// -----------------------------------------------------------------------------
// Reverse the bit ordering in an array
uchar *ReverseBitsInArray(uchar *Buff, int BitKnt) {
unsigned long front=0, back = BitKnt-1;
uchar temp;
while( front<back ) {
temp = BitGet(Buff, front); // copy front bit to temp before overwriting
BitPut(Buff, front, BitGet(Buff, back)); // copy back bit to front bit
BitPut(Buff, back, temp); // copy saved value of front in temp to back of bit arra)
front++;
back--;
}
return Buff;
}
// ---------------------------------------------------------------------------
// ---------------------------------------------------------------------------
int _tmain(int argc, _TCHAR* argv[]) {
int i, j, k, LoopKnt = 1000001;
time_t start;
uchar Buff[BUFF_BYTES];
memset(Buff, 0, sizeof(Buff));
// make an ASCII art picture
for(i=0, k=0; i<(sizeof(Buff)*8)/DW; i++) {
for(j=0; j<DW/2; j++) {
BitSet(Buff, (i*DW)+j+k);
}
k++;
}
// print ASCII art picture
for(i=0; i<sizeof(Buff); i++) {
if(!(i % 10)) printf("\n"); // print bits in blocks of 80
PrintIntBits(Buff[i], 8);
}
i=LoopKnt;
start = clock();
while( i-- ) {
ReverseBitsInArray((uchar *)Buff, BUFF_BYTES * 8);
}
// print ASCII art pic flipped upside-down and rotated left
printf("\nMilliseconds elapsed = %d", clock() - start);
for(i=0; i<sizeof(Buff); i++) {
if(!(i % 10)) printf("\n"); // print bits in blocks of 80
PrintIntBits(Buff[i], 8);
}
printf("\n\nBenchmark time for %d loops\n", LoopKnt);
getchar();
return 0;
}
// -----------------------------------------------------------------------------
// Scaffolding...
static void PrintIntBits(long x, int bits) {
unsigned long long z=1;
int i=0;
z = z << (bits-1);
for (; z > 0; z >>= 1) {
printf("%s", ((x & z) == z) ? "#" : ".");
}
}
// These routines do bit manipulations on a bit array of unsigned chars
// ---------------------------------------------------------------------------
void BitSet(uchar *buff, unsigned long BitNumber) {
buff[BitNumber >> 3] |= mask_set[BitNumber & 7];
}
// ----------------------------------------------------------------------------
void BitClr(uchar *buff, unsigned long BitNumber) {
buff[BitNumber >> 3] &= mask_clr[BitNumber & 7];
}
// ----------------------------------------------------------------------------
void BitTog(uchar *buff, unsigned long BitNumber) {
buff[BitNumber >> 3] ^= mask_set[BitNumber & 7];
}
// ----------------------------------------------------------------------------
uchar BitGet(uchar *buff, unsigned long BitNumber) {
return (uchar) ((buff[BitNumber >> 3] >> (BitNumber & 7)) & 1);
}
// ----------------------------------------------------------------------------
void BitPut(uchar *buff, unsigned long BitNumber, uchar value) {
if(value) { // if the bit at buff[BitNumber] is true.
BitSet(buff, BitNumber);
} else {
BitClr(buff, BitNumber);
}
}
下面是使用新缓冲区进行优化的代码清单,而不是交换字节。由于if()测试只需要2030:4080 BitSet(),并且通过消除TEMP消除了大约一半的GetBit()和PutBits(),我怀疑内存访问时间是一个很大的固定成本这些类型的操作,为优化提供了硬性限制。
使用查找方法,并且有条件地交换字节而不是位,将内存访问次数减少8倍,并且测试0字节将在8位而不是1中分摊。
一起使用这两种方法,在执行ANYTHING之前测试整个8位字符是否为0,包括表查找和写入,可能是最快的方法,但需要额外的512新的目标位数组的字节数,以及查找表的256字节。尽管如此,表现回报可能非常显着。
// -----------------------------------------------------------------------------
// Reverse the bit ordering in new array
uchar *ReverseBitsInNewArray(uchar *Dst, const uchar *Src, const int BitKnt) {
int front=0, back = BitKnt-1;
memset(Dst, 0, BitKnt/BitsInByte);
while( front < back ) {
if(BitGet(Src, back--)) { // memset() has already set all bits in Dst to 0,
BitSet(Dst, front); // so only reset if Src bit is 1
}
front++;
}
return Dst;
答案 6 :(得分:2)
要反转单个字节x,您可以一次处理一个位:
unsigned char a = 0;
for (i = 0; i < 8; ++i) {
a += (unsigned char)(((x >> i) & 1) << (7 - i));
}
您可以在数组中创建这些结果的缓存,这样您就可以通过单次查找而不是循环来快速反转字节。
然后你只需要反转字节数组,并在编写数据时应用上面的映射。反转字节数组是一个记录良好的问题,例如, here
答案 7 :(得分:1)
单核?
记忆多少?
显示屏是否在内存中缓冲并推送到设备,或者是屏幕内存中像素的唯一副本?
答案 8 :(得分:1)
数据通过8位IO从系统内存推送到LCD驱动程序 端口。
由于您将一次一个字节地写入LCD,我认为最好的想法是在将数据发送到LCD驱动程序时执行位反转,而不是单独执行前通。沿着这些方向的东西应该比任何其他答案更快:
void send_to_LCD(uint8_t* data, int len, bool rotate) {
if (rotate)
for (int i=len-1; i>=0; i--)
write(reverse(data[i]));
else
for (int i=0; i<len; i++)
write(data[i]);
}
其中write()
是向LCD驱动程序发送字节的函数,reverse()
是其他答案中描述的单字节位反转方法之一。
这种方法避免了在ram中存储两个视频数据副本的需要,也避免了read-invert-write往返。还要注意,这是最简单的实现:它可以简单地适应从内存中一次加载4个字节,如果这样可以产生更好的性能。智能矢量化编译器甚至可以为您完成。