2010-01-28 20 views
14

Tôi cần phải viết lại khoảng 4KB dữ liệu theo thứ tự ngược lại, ở mức bit (bit cuối cùng của byte cuối cùng trở thành bit đầu tiên của byte đầu tiên), càng nhanh càng tốt. Có bất kỳ sniplets thông minh để làm điều đó?Xoay một đoạn lớn bộ nhớ lùi, nhanh

Lý do: Dữ liệu là nội dung hiển thị của màn hình LCD trong thiết bị được nhúng thường được định vị theo cách màn hình ở trên vai của bạn. Màn hình có định hướng "6 giờ", đó là để được xem từ bên dưới - như nằm phẳng hoặc treo trên mức mắt của bạn. Điều này có thể khắc phục bằng cách xoay màn hình 180 độ, nhưng sau đó tôi cần đảo ngược dữ liệu màn hình (được tạo bởi thư viện), là 1 bit = 1 pixel, bắt đầu với phía trên bên trái của màn hình. CPU không phải là rất mạnh mẽ, và các thiết bị có đủ công việc đã có, cộng với một số khung hình một giây sẽ là mong muốn vì vậy hiệu suất là một vấn đề; RAM không quá nhiều.

chỉnh sửa: lõi đơn, ARM 9 series. 64MB, (để được thu nhỏ xuống còn 32MB sau), Linux. Dữ liệu được đẩy từ bộ nhớ hệ thống đến trình điều khiển LCD qua cổng IO 8 bit.

CPU là 32 bit và hoạt động tốt hơn nhiều ở kích thước từ này so với mức byte.

+0

Tôi thấy rằng các trang web hiển thị LCD thường có thói quen tốt để ghi dữ liệu vào mô-đun của họ (và theo cả hai hướng). Ngoài ra, vì đây là một vấn đề phổ biến, bảng sở thích (và chuyên nghiệp) cho bộ xử lý của bạn cũng có thể có các thuật toán để thực hiện việc này. – KevinDTimm

+2

Một điều, bạn có thể sắp xếp lại các bit bên trong một byte bằng cách sử dụng bảng tra cứu, ví dụ: '00101101 -> 10110100'. Phần còn lại của vấn đề sau đó sẽ được giảm xuống để nhanh chóng đảo ngược bộ đệm ở mức byte. – stakx

+0

Bạn nên thực sự thực hiện việc này khi đang gửi dữ liệu đến màn hình LCD chứ không phải là một thông báo trước riêng biệt. Xem [câu trả lời của tôi] (http://stackoverflow.com/a/16535315/414813) bên dưới ... – CAFxX

Trả lời

23

Có một cách cổ điển để làm điều này. Hãy nói rằng unsigned int là từ 32 bit của bạn. Tôi đang sử dụng C99 vì từ khóa hạn chế cho phép trình biên dịch thực hiện tối ưu hóa thêm trong mã tốc độ quan trọng này nếu không sẽ không có sẵn. Những từ khóa này thông báo cho trình biên dịch rằng "src" và "dest" không chồng lên nhau. Điều này cũng giả định bạn đang sao chép một số tích phân của các từ, nếu bạn không, thì đây chỉ là một sự khởi đầu.

Tôi cũng không biết các bit nguyên gốc chuyển động/xoay nào nhanh trên ARM và tốc độ chậm. Đây là một cái gì đó để xem xét. Nếu bạn cần tốc độ cao hơn, hãy xem xét tháo đầu ra khỏi trình biên dịch C và đi từ đó. Nếu sử dụng GCC, hãy thử O2, O3 và Os để xem cái nào là nhanh nhất. Bạn có thể giảm các quầy hàng trong đường ống bằng cách thực hiện hai từ cùng một lúc.

Điều này sử dụng 23 hoạt động cho mỗi từ, không kể tải và lưu trữ. Tuy nhiên, tất cả 23 hoạt động này đều rất nhanh và không ai trong số chúng truy cập được bộ nhớ. Tôi không biết liệu bảng tra cứu có nhanh hơn hay không.

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

Trang này là một tài liệu tham khảo rất lớn: http://graphics.stanford.edu/~seander/bithacks.html#BitReverseObvious

Cuối cùng lưu ý: Nhìn vào tham khảo lắp ráp ARM, có một "REV" opcode mà đảo ngược thứ tự byte trong một từ. Điều này sẽ cạo 7 hoạt động trên mỗi vòng lặp ra khỏi mã trên.

+0

Tnx để có mã và tham chiếu tuyệt vời. Sẽ đánh dấu. – RocketRoy

+0

Trên hộp Hazwell hàng hóa của tôi, KHÔNG phải mục tiêu dự định để chắc chắn, nhưng những gì tôi có, đây là khoảng 3X nhanh như giải thích rất đúng nghĩa của tôi về một giải pháp mà tôi đã cung cấp bên dưới.Một bảng tra cứu có điều kiện có lẽ sẽ là giải pháp nhanh nhất, đặc biệt nếu các byte được hoán đổi chỉ khi chúng không giống nhau để thực sự thực hiện việc hoán đổi sẽ hoàn toàn dư thừa. – RocketRoy

+0

Tôi hoài nghi về cả hai bảng tra cứu và ý tưởng bằng cách nào đó không trao đổi các byte giống hệt nhau. Di chuyển có điều kiện giới thiệu một số lượng * khá lớn * trên không, và đường ống đã giảm hiệu quả của các bảng. Mặt khác, khi tôi biên dịch mã trên, nó được chuyển thành các lệnh SSE2 tự động bởi trình biên dịch (Apple LLVM 6.0), sử dụng 54 lệnh trong vòng lặp bên trong. Hiệu suất số: 2100 MB/s cho 'memcpy()', 630 MB/s cho mã trên, và 370 MB/s cho một bảng tra cứu. (Apple LLVM 6.0, '-O3', CPU i5-4258U @ 2.40GHz, số nguyên 100M x 10 chạy) –

4

Lặp lại một nửa của mảng, chuyển đổi và trao đổi byte.

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

Đối với chuyển đổi sử dụng một bảng precomputed - một mảng của 2 CHAR_BIT (CHAR_BIT rất có thể sẽ là 8) yếu tố mà ở vị trí "I" là kết quả của byte có giá trị "I" đảo ngược được lưu trữ. Điều này sẽ rất nhanh - một lượt - và chỉ tiêu thụ 2 CHAR_BIT cho bảng.

9

Trang web Bit Twiddling Hacks là một điểm khởi đầu tốt cho các loại vấn đề này. Hãy xem here để đảo ngược bit nhanh. Sau đó, nó tùy thuộc vào bạn để áp dụng nó cho mỗi byte/từ của khối bộ nhớ của bạn.

EDIT:

Lấy cảm hứng từ Dietrich Epps câu trả lời và nhìn vào ARM instruction set, có một opcode RBIT mà đảo ngược các bit chứa trong một thanh ghi. Vì vậy, nếu hiệu suất là rất quan trọng, bạn có thể xem xét sử dụng một số mã lắp ráp.

+0

URL cho Bit Twiddling hacks bị hỏng - nó phải là: http://graphics.stanford.edu/~seander /bithacks.html –

+0

Cảm ơn gợi ý, đã sửa nó ... –

1

Lõi đơn?

Dung lượng bộ nhớ bao nhiêu?

Màn hình có được lưu vào bộ nhớ và được đẩy vào thiết bị hay là bản sao duy nhất của các pixel trong bộ nhớ màn hình?

+0

Lõi đơn, dòng ARM 9. 64MB, (để được thu nhỏ xuống còn 32MB sau), Linux. Bộ nhớ được đẩy từ bộ nhớ hệ thống (mà tôi kiểm soát) vào bộ nhớ trình điều khiển LCD (ngoài tầm kiểm soát của tôi). –

16

Cách nhanh nhất có thể lưu trữ ngược lại tất cả các giá trị byte có thể có trong bảng tra cứu. Bảng sẽ chỉ có 256 byte.

+1

... sau đó chỉ cần thay đổi vòng lặp ghi byte ra cổng IO để bắt đầu ở phần cuối của bộ đệm và làm việc ngược. – caf

2

Để đảo ngược một byte đơn x bạn có thể xử lý các bit cùng một lúc:

unsigned char a = 0; 
for (i = 0; i < 8; ++i) { 
    a += (unsigned char)(((x >> i) & 1) << (7 - i)); 
} 

Bạn có thể tạo một bộ nhớ cache của các kết quả này trong một mảng để bạn có thể nhanh chóng đảo ngược một byte chỉ bằng cách thực hiện một tra cứu đơn thay vì lặp.

Sau đó, bạn chỉ cần đảo ngược mảng byte và khi bạn ghi dữ liệu, hãy áp dụng ánh xạ ở trên. Đảo ngược một mảng byte là một vấn đề được ghi nhận rõ ràng, ví dụ: here.

14

Tạo bảng tra cứu phần tử 256 giá trị byte được đảo ngược bit từ chỉ mục của chúng.

{0x00, 0x80, 0x40, 0xc0, vv}

Sau đó lặp qua mảng sao chép của bạn sử dụng mỗi byte như một chỉ số vào bảng tra cứu của bạn.

Nếu bạn đang viết ngôn ngữ lắp ráp, tập lệnh x86 có hướng dẫn XLAT thực hiện loại tra cứu này. Mặc dù nó có thể không thực sự nhanh hơn mã C trên các bộ vi xử lý hiện đại.

Bạn có thể thực hiện việc này nếu bạn lặp lại từ cả hai đầu về phía giữa. Bởi vì các hiệu ứng bộ nhớ cache, bạn có thể tìm thấy nó nhanh hơn để trao đổi trong 16 byte khối (giả sử một dòng bộ nhớ cache 16 byte).

Dưới đây là các mã cơ bản (không bao gồm việc tối ưu hóa dòng bộ nhớ cache)

// 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

enter image description here

Dường như mã này mất khoảng 50 đồng hồ mỗi hoán đổi chút về i7 XPS của tôi 8500 máy. 7,6 giây cho một triệu mảng lật. Đơn luồng. Nó in một số nghệ thuật ASCI dựa trên các mẫu 1 và 0. Tôi xoay pic trái 180 độ sau khi đảo ngược mảng bit, sử dụng một trình soạn thảo đồ họa, và họ trông giống hệt với tôi. Một hình ảnh đảo ngược đôi xuất hiện giống như hình gốc.

Đối với điểm cộng, đó là giải pháp hoàn chỉnh. Nó hoán đổi các bit từ mặt sau của một mảng bit tới mặt trước, so với hoạt động trên ints/bytes và sau đó cần trao đổi int/byte trong một mảng.

Ngoài ra, đây là thư viện bit có mục đích chung, vì vậy bạn có thể thấy nó tiện dụng trong tương lai để giải quyết các vấn đề khác, nhiều sự trần tục hơn.

Có nhanh như câu trả lời được chấp nhận không? Tôi nghĩ rằng nó gần, nhưng không có mã làm việc để chuẩn nó không thể nói. Vui lòng cắt và dán chương trình làm việc này.

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

Dưới đây là danh sách mã để tối ưu hóa bằng bộ đệm mới thay vì hoán đổi byte tại chỗ. Cho rằng chỉ 2030: 4080 BitSet() là cần thiết vì thử nghiệm if(), và khoảng một nửa GetBit() và PutBits() được loại bỏ bằng cách loại bỏ TEMP, tôi nghi ngờ thời gian truy cập bộ nhớ là một chi phí cố định lớn các loại hoạt động này, cung cấp một giới hạn khó để tối ưu hóa.

Sử dụng một cách tiếp cận nhìn lên, và có điều kiện trao đổi byte, chứ không phải là bit, giảm bởi một yếu tố của 8 số lượng bộ nhớ truy cập, và thử nghiệm cho một byte 0 được phân bổ dần qua 8 bit, chứ không phải 1.

Sử dụng hai phương pháp này cùng với nhau, kiểm tra xem toàn bộ 8 bit bit là 0 trước khi thực hiện ANYTHING, bao gồm tra cứu bảng và ghi, có khả năng là cách tiếp cận nhanh nhất có thể, nhưng sẽ yêu cầu thêm 512 byte cho mảng bit đích mới và 256 byte cho bảng tra cứu. Tuy nhiên, hiệu suất hoạt động có thể khá ấn tượng.

// ----------------------------------------------------------------------------- 
// 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; 
+0

Nhanh hơn so với hoán đổi tại chỗ, sẽ là SET một chút, theo thứ tự ngược lại, trong mảng bit thứ 2, memset (0) - CONDITIONAL trên giá trị của mảng nguồn là 1. Điều này sẽ loại bỏ nhu cầu thiết lập giá trị của TEMP, và, trung bình, lưu tất cả 0 BitPut() s vào mảng đích. Với phương pháp tra cứu, có một số hấp dẫn, ghi 256 byte và mảng bit OP 9696 chỉ 512, ghi 256 byte (hoặc bất kỳ kích thước bộ đệm hiển thị nào) có vẻ như là một cách tiếp cận trực tiếp tốt hơn, với 2-3X hiệu suất của mã hiện tại của tôi. – RocketRoy

+1

Hơi thất vọng, nhưng thiết lập CONDITIONAL ở trên của bit đích, tùy thuộc vào việc bit của mảng nguồn có đúng hay không, chạy trong 60% thời gian. Tuy nhiên, đối với một bảng tra cứu và trao đổi ubar, tôi tin rằng đây sẽ là một tối ưu hóa tuyệt vời, và có lẽ sẽ tạo ra thói quen nhanh nhất, vì chi phí của phép thử if() sẽ được phân bổ trên 8 bit, và 8 bit không liên tiếp không phải là không phổ biến, trong khi 32 bit không liên tiếp có thể là hiếm. – RocketRoy

1

Dữ liệu được đẩy từ bộ nhớ hệ thống để người lái xe LCD hơn 8-bit cổng IO .

Vì bạn sẽ được văn bản cho màn hình LCD một byte tại một thời điểm, tôi nghĩ ý tưởng tốt nhất là để thực hiện sự đảo ngược chút ngay khi gửi dữ liệu để tài xế LCD chứ không phải là một tiền riêng vượt qua. Một cái gì đó dọc theo những đường nên nhanh hơn so với bất kỳ câu trả lời khác:

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

đâu write() là chức năng gửi một byte để trình điều khiển màn hình LCD và reverse() một trong những phương pháp chút đảo ngược single-byte được mô tả trong các câu trả lời khác .

Cách tiếp cận này tránh được sự cần thiết phải lưu trữ hai bản sao của dữ liệu video trong ram và cũng tránh được vòng lặp đọc-invert-write. Cũng lưu ý rằng đây là cách triển khai đơn giản nhất: nó có thể được điều chỉnh một cách trivially để tải, nói, 4 byte tại một thời điểm từ bộ nhớ nếu điều này là để mang lại hiệu suất tốt hơn. Một trình biên dịch vectơ thông minh thậm chí có thể làm điều đó cho bạn.

Các vấn đề liên quan