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.
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
'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. –
@dauphic Nhưng sau đó tại sao CUDA có 'cudaMemcpy2D' sao chép với quảng cáo chiêu hàng? – JackOLantern