2013-06-13 44 views
8

Tôi có hai mảng và tôi muốn sao chép một mảng vào mảng khác bằng một số bước. Ví dụ, tôi cóSao chép dữ liệu có cấu trúc trong C++

A A A A A A A A ... 

B B B B B B B B ... 

và tôi muốn sao chép tất cả ba yếu tố của B-A để có được

B A A B A A B A ... 

Từ bài "Is there a standard, strided version of memcpy?", có vẻ như là không có như vậy một khả năng trong C.

Tuy nhiên, tôi đã trải qua điều đó, trong một số trường hợp, memcpy nhanh hơn bản sao dựa trên vòng lặp for.

Câu hỏi của tôi là; Có cách nào để thực hiện hiệu quả bản sao bộ nhớ được cải tiến trong C++ thực hiện ít nhất là một vòng lặp for tiêu chuẩn?

Cảm ơn bạn rất nhiều.

EDIT - Làm rõ các vấn đề

Để làm cho vấn đề rõ ràng hơn, chúng ta hãy biểu thị hai mảng trong tầm tay bởi ab. Tôi có một chức năng mà thực hiện những điều sau đây độc đáo for loop

for (int i=0; i<NumElements, i++) 
    a_[i] = b_[i]; 

nơi mà cả [] 's được quá tải khai thác (Tôi đang sử dụng một kỹ thuật biểu mẫu) để họ có thể được thực sự có ý nghĩa, ví dụ

a[3*i]=b[i]; 
+0

Chuẩn cho vòng lặp thực hiện ít nhất nhanh như chuẩn cho vòng lặp ... Bỏ qua một bên, nó phụ thuộc vào cấu trúc lưu trữ dữ liệu bạn đang sử dụng. Đối với mảng, tôi không nghĩ rằng bạn có thể làm bất kỳ tốt hơn so với một vòng lặp for, tăng lên bởi modulus của bạn. – ChrisCM

+0

'memcpy' đôi khi nhanh hơn vòng lặp' for' do tối ưu hóa nó có thể thực hiện vì bộ nhớ đang hoạt động trên tiếp giáp. Những tối ưu hóa không thể được thực hiện ở đây. –

+0

@dauphic Nhưng sau đó tại sao CUDA có 'cudaMemcpy2D' sao chép với quảng cáo chiêu hàng? – JackOLantern

Trả lời

6
Is there any way to efficiently perform strided memory copy in C++ performing at least as a standard for loop? 

chỉnh sửa 2: không có chức năng sao chép strided trong các thư viện C++.

Kể từ khi sao chép không được phổ biến sao chép bộ nhớ, các nhà sản xuất chip và thiết kế ngôn ngữ có hỗ trợ chuyên biệt để sao chép có cấu trúc.

Giả sử một vòng lặp tiêu chuẩn for, bạn có thể đạt được hiệu suất bằng cách sử dụng Hủy vòng lặp. Một số trình biên dịch có các tùy chọn để hủy vòng lặp; nó không phải là một tùy chọn "chuẩn".

Cho một chuẩnfor loop:

#define RESULT_SIZE 72 
#define SIZE_A 48 
#define SIZE_B 24 

unsigned int A[SIZE_A]; 
unsigned int B[SIZE_B]; 
unsigned int result[RESULT_SIZE]; 

unsigned int index_a = 0; 
unsigned int index_b = 0; 
unsigned int index_result = 0; 
for (index_result = 0; index_result < RESULT_SIZE;) 
{ 
    result[index_result++] = B[index_b++]; 
    result[index_result++] = A[index_a++]; 
    result[index_result++] = A[index_a++]; 
} 

Vòng unrolling sẽ lặp lại các nội dung của "tiêu chuẩn" for loop:

for (index_result = 0; index_result < RESULT_SIZE;) 
{ 
    result[index_result++] = B[index_b++]; 
    result[index_result++] = A[index_a++]; 
    result[index_result++] = A[index_a++]; 

    result[index_result++] = B[index_b++]; 
    result[index_result++] = A[index_a++]; 
    result[index_result++] = A[index_a++]; 
} 

Trong phiên bản trải, số lượng các vòng đã bị cắt làm đôi.

Cải thiện hiệu suất có thể không đáng kể so với các tùy chọn khác. Các vấn đề sau ảnh hưởng đến hiệu suất và từng có những cải tiến tốc độ khác nhau: dữ liệu bộ nhớ cache

  • Processing nhớ
  • tải lại các đường ống dẫn (phụ thuộc vào bộ xử lý)
  • Hệ điều hành trao đổi bộ nhớ với đĩa
  • Các nhiệm vụ khác chạy đồng thời
  • Xử lý song song (phụ thuộc vào bộ xử lý/nền tảng)

Một ví dụ về xử lý song song là có một bộ xử lý sao chép các mục B vào mảng mới và một bộ xử lý khác sao chép các mục A vào mảng mới.

+0

Cảm ơn câu trả lời tốt bụng của bạn. Tôi đã chỉnh sửa bài đăng của mình để giải thích rõ hơn vấn đề. Bạn có nghĩ rằng bằng một '#pragma unroll' tôi có cơ hội cải thiện tình hình không? Tôi không biết, vì tất cả mọi thứ về bản sao được biết đến trong thời gian chạy. – JackOLantern

+0

Như tôi đã nói, phụ thuộc vào bộ xử lý. Với một số bộ vi xử lý, một chi nhánh tuôn ra đường ống dẫn và bộ vi xử lý phải nạp lại nó. Một số bộ vi xử lý hiện đại có đủ bộ nhớ cache đường ống dẫn mà chúng giữ một vòng lặp đơn giản trong bộ nhớ cache lệnh và không phải tải lại. –

+0

Hầu hết các bộ vi xử lý thích thực hiện các lệnh tuần tự và không muốn gặp phải các lệnh chi nhánh. –

6

Có thể là một câu trả lời quá cụ thể, nhưng trên nền tảng ARM hỗ trợ NEON, vector hóa NEON có thể được sử dụng để làm cho bản sao được quét thậm chí còn nhanh hơn. Điều này có thể được cứu sống trong môi trường nơi tài nguyên tương đối hạn chế hơn, đó có lẽ là lý do tại sao ARM được sử dụng trong bối cảnh đó ngay từ đầu. Một ví dụ nổi bật là Android, nơi hầu hết các thiết bị vẫn sử dụng kiến ​​trúc ARM v7a hỗ trợ NEON.

Ví dụ sau minh họa điều này, đó là vòng lặp để sao chép mặt phẳng UV bán phẳng của hình ảnh YUV420sp vào mặt phẳng UV phẳng của hình ảnh YUV420p. Kích thước của bộ đệm đích và nguồn đều là 640*480/2 byte. Tất cả các ví dụ được biên dịch với g ++ 4.8 bên trong Android NDK r9d. Họ được thực hiện trên một bộ xử lý Samsung Exynos 5420 Octa:

Level 1: Regular

void convertUVsp2UVp(
    unsigned char* __restrict srcptr, 
    unsigned char* __restrict dstptr, 
    int stride) 
{ 
    for(int i=0;i<stride;i++){ 
     dstptr[i]   = srcptr[i*2]; 
     dstptr[i + stride] = srcptr[i*2 + 1]; 
    } 
} 

Biên soạn với -O3 chỉ, mất khoảng 1,5 ms trên trung bình.

Level 2: trải ra và vắt thêm một chút với con trỏ di chuyển

void convertUVsp2UVp(
    unsigned char* __restrict srcptr, 
    unsigned char* __restrict dstptr, 
    int stride) 
{ 
    unsigned char* endptr = dstptr + stride; 
    while(dstptr<endptr){ 
     *(dstptr + 0)    = *(srcptr + 0); 
     *(dstptr + stride + 0) = *(srcptr + 1); 
     *(dstptr + 1)    = *(srcptr + 2); 
     *(dstptr + stride + 1) = *(srcptr + 3); 
     *(dstptr + 2)    = *(srcptr + 4); 
     *(dstptr + stride + 2) = *(srcptr + 5); 
     *(dstptr + 3)    = *(srcptr + 6); 
     *(dstptr + stride + 3) = *(srcptr + 7); 
     *(dstptr + 4)    = *(srcptr + 8); 
     *(dstptr + stride + 4) = *(srcptr + 9); 
     *(dstptr + 5)    = *(srcptr + 10); 
     *(dstptr + stride + 5) = *(srcptr + 11); 
     *(dstptr + 6)    = *(srcptr + 12); 
     *(dstptr + stride + 6) = *(srcptr + 13); 
     *(dstptr + 7)    = *(srcptr + 14); 
     *(dstptr + stride + 7) = *(srcptr + 15); 
     srcptr+=16; 
     dstptr+=8; 
    } 
} 

Biên soạn với -O3 chỉ, mất khoảng 1,15 ms trên trung bình. Đây có lẽ là nhanh như nó được trên một kiến ​​trúc thường xuyên, theo câu trả lời khác.

Level 3: Regular + GCC NEON tự động vector hóa

void convertUVsp2UVp(
    unsigned char* __restrict srcptr, 
    unsigned char* __restrict dstptr, 
    int stride) 
{ 
    for(int i=0;i<stride;i++){ 
     dstptr[i]   = srcptr[i*2]; 
     dstptr[i + stride] = srcptr[i*2 + 1]; 
    } 
} 

Biên soạn với -O3 -mfpu=neon -ftree-vectorize -ftree-vectorizer-verbose=1 -mfloat-abi=softfp, mất khoảng 0,6 ms trên trung bình. Để tham khảo, số memcpy trong số 640*480 byte hoặc tăng gấp đôi số tiền được kiểm tra ở đây, trung bình mất khoảng 0,6 ms.

Như một lưu ý phụ, mã thứ hai (chưa được kiểm tra và được trỏ) được biên dịch với các tham số NEON ở trên có cùng khoảng thời gian, 0,6 ms.

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