2012-09-25 39 views
5

Tham khảo câu trả lời của @ auselen tại đây: Using ARM NEON intrinsics to add alpha and permute, có vẻ như trình biên dịch armcc tốt hơn nhiều so với trình biên dịch gcc để tối ưu hóa NEON. Điều này có thực sự đúng không? Tôi đã không thực sự thử trình biên dịch armcc. Nhưng tôi có mã được tối ưu hóa khá tốt bằng cách sử dụng trình biên dịch gcc với cờ tối ưu hóa -O3. Nhưng bây giờ tôi tự hỏi nếu armcc thực sự là tốt? Vậy trình biên dịch nào tốt hơn, xem xét tất cả các yếu tố?Cái nào tốt hơn, gcc hoặc armcc cho tối ưu hóa NEON?

+5

Hỗ trợ NEON trong gcc kém trưởng thành hơn hỗ trợ số nguyên/fp vô hướng. Tuy nhiên, so sánh của auselen dựa trên gcc 4.4.3, được phát hành cách đây hơn 2.5 năm. Một chút công bằng của công việc đã đi vào cải tiến NEON kể từ đó. Đồng thời, armcc 5,01 chỉ mới một tuổi. Trong khi tôi vẫn mong đợi armcc 5.02 sẽ đi trước, một so sánh có liên quan hơn sẽ là giữa nó và 4,7 gcc. – unixsmurf

+1

@unixsmurf một trong số +1 là của tôi :) – auselen

Trả lời

7

Trình biên dịch cũng là phần mềm, chúng có xu hướng cải thiện theo thời gian. Bất kỳ yêu cầu chung chung nào như armcc tốt hơn GCC trên NEON (hoặc tốt hơn được gọi là vector hóa) không thể giữ đúng mãi mãi vì một nhóm nhà phát triển có thể thu hẹp khoảng cách với đủ sự chú ý. Tuy nhiên ban đầu nó là hợp lý để mong đợi trình biên dịch được phát triển bởi các công ty phần cứng để được cấp trên bởi vì họ cần phải chứng minh/thị trường các tính năng này.

Một ví dụ gần đây tôi thấy ở đây trên Stack Overflow về một answer for branch prediction. Trích dẫn từ dòng cuối cùng của phần cập nhật "Điều này cho thấy rằng ngay cả trình biên dịch hiện đại trưởng thành cũng có thể thay đổi một cách dữ dội trong khả năng tối ưu hóa mã của chúng ...".

Tôi là một fan hâm mộ lớn của GCC, nhưng tôi sẽ không đặt cược vào chất lượng của mã được sản xuất bởi nó chống lại trình biên dịch từ Intel hoặc ARM.Tôi mong đợi bất kỳ trình biên dịch thương mại chính thống nào để sản xuất mã ít nhất là tốt như GCC.

Một câu trả lời thực nghiệm cho câu hỏi này có thể là sử dụng hilbert-space's neon optimization example và xem cách trình biên dịch khác nhau tối ưu hóa nó.

void neon_convert (uint8_t * __restrict dest, uint8_t * __restrict src, int n) 
{ 
    int i; 
    uint8x8_t rfac = vdup_n_u8 (77); 
    uint8x8_t gfac = vdup_n_u8 (151); 
    uint8x8_t bfac = vdup_n_u8 (28); 
    n/=8; 

    for (i=0; i<n; i++) 
    { 
    uint16x8_t temp; 
    uint8x8x3_t rgb = vld3_u8 (src); 
    uint8x8_t result; 

    temp = vmull_u8 (rgb.val[0],  rfac); 
    temp = vmlal_u8 (temp,rgb.val[1], gfac); 
    temp = vmlal_u8 (temp,rgb.val[2], bfac); 

    result = vshrn_n_u16 (temp, 8); 
    vst1_u8 (dest, result); 
    src += 8*3; 
    dest += 8; 
    } 
} 

Đây là armcc 5,01

20: f421140d vld3.8 {d1-d3}, [r1]! 
    24: e2822001 add r2, r2, #1 
    28: f3810c04 vmull.u8 q0, d1, d4 
    2c: f3820805 vmlal.u8 q0, d2, d5 
    30: f3830806 vmlal.u8 q0, d3, d6 
    34: f2880810 vshrn.i16 d0, q0, #8 
    38: f400070d vst1.8 {d0}, [r0]! 
    3c: e1520003 cmp r2, r3 
    40: bafffff6 blt 20 <neon_convert+0x20> 

Đây là GCC 4.4.3-4.7.1

1e: f961 040d vld3.8 {d16-d18}, [r1]! 
    22: 3301  adds r3, #1 
    24: 4293  cmp r3, r2 
    26: ffc0 4ca3 vmull.u8 q10, d16, d19 
    2a: ffc1 48a6 vmlal.u8 q10, d17, d22 
    2e: ffc2 48a7 vmlal.u8 q10, d18, d23 
    32: efc8 4834 vshrn.i16 d20, q10, #8 
    36: f940 470d vst1.8 {d20}, [r0]! 
    3a: d1f0  bne.n 1e <neon_convert+0x1e> 

nào trông rất giống nhau, vì vậy chúng tôi có một trận hòa. Sau khi nhìn thấy điều này tôi đã cố gắng đề cập thêm alpha và permute một lần nữa.

void neonPermuteRGBtoBGRA(unsigned char* src, unsigned char* dst, int numPix) 
{ 
    numPix /= 8; //process 8 pixels at a time 

    uint8x8_t alpha = vdup_n_u8 (0xff); 

    for (int i=0; i<numPix; i++) 
    { 
     uint8x8x3_t rgb = vld3_u8 (src); 
     uint8x8x4_t bgra; 

     bgra.val[0] = rgb.val[2]; //these lines are slow 
     bgra.val[1] = rgb.val[1]; //these lines are slow 
     bgra.val[2] = rgb.val[0]; //these lines are slow 

     bgra.val[3] = alpha; 

     vst4_u8(dst, bgra); 

     src += 8*3; 
     dst += 8*4; 
    } 
} 

Biên soạn với gcc ...

$ arm-linux-gnueabihf-gcc --version 
arm-linux-gnueabihf-gcc (crosstool-NG linaro-1.13.1-2012.05-20120523 - Linaro GCC 2012.05) 4.7.1 20120514 (prerelease) 
$ arm-linux-gnueabihf-gcc -std=c99 -O3 -c ~/temp/permute.c -marm -mfpu=neon-vfpv4 -mcpu=cortex-a9 -o ~/temp/permute_gcc.o 

00000000 <neonPermuteRGBtoBGRA>: 
    0: e3520000 cmp r2, #0 
    4: e2823007 add r3, r2, #7 
    8: b1a02003 movlt r2, r3 
    c: e92d01f0 push {r4, r5, r6, r7, r8} 
    10: e1a021c2 asr r2, r2, #3 
    14: e24dd01c sub sp, sp, #28 
    18: e3520000 cmp r2, #0 
    1c: da000019 ble 88 <neonPermuteRGBtoBGRA+0x88> 
    20: e3a03000 mov r3, #0 
    24: f460040d vld3.8 {d16-d18}, [r0]! 
    28: eccd0b06 vstmia sp, {d16-d18} 
    2c: e59dc014 ldr ip, [sp, #20] 
    30: e2833001 add r3, r3, #1 
    34: e59d6010 ldr r6, [sp, #16] 
    38: e1530002 cmp r3, r2 
    3c: e59d8008 ldr r8, [sp, #8] 
    40: e1a0500c mov r5, ip 
    44: e59dc00c ldr ip, [sp, #12] 
    48: e1a04006 mov r4, r6 
    4c: f3c73e1f vmov.i8 d19, #255 ; 0xff 
    50: e1a06008 mov r6, r8 
    54: e59d8000 ldr r8, [sp] 
    58: e1a0700c mov r7, ip 
    5c: e59dc004 ldr ip, [sp, #4] 
    60: ec454b34 vmov d20, r4, r5 
    64: e1a04008 mov r4, r8 
    68: f26401b4 vorr d16, d20, d20 
    6c: e1a0500c mov r5, ip 
    70: ec476b35 vmov d21, r6, r7 
    74: f26511b5 vorr d17, d21, d21 
    78: ec454b34 vmov d20, r4, r5 
    7c: f26421b4 vorr d18, d20, d20 
    80: f441000d vst4.8 {d16-d19}, [r1]! 
    84: 1affffe6 bne 24 <neonPermuteRGBtoBGRA+0x24> 
    88: e28dd01c add sp, sp, #28 
    8c: e8bd01f0 pop {r4, r5, r6, r7, r8} 
    90: e12fff1e bx lr 

Biên soạn với armcc ...

$ armcc 
ARM C/C++ Compiler, 5.01 [Build 113] 
$ armcc --C99 --cpu=Cortex-A9 -O3 -c permute.c -o permute_arm.o 

00000000 <neonPermuteRGBtoBGRA>: 
    0: e1a03fc2 asr r3, r2, #31 
    4: f3870e1f vmov.i8 d0, #255 ; 0xff 
    8: e0822ea3 add r2, r2, r3, lsr #29 
    c: e1a031c2 asr r3, r2, #3 
    10: e3a02000 mov r2, #0 
    14: ea000006 b 34 <neonPermuteRGBtoBGRA+0x34> 
    18: f420440d vld3.8 {d4-d6}, [r0]! 
    1c: e2822001 add r2, r2, #1 
    20: eeb01b45 vmov.f64 d1, d5 
    24: eeb02b46 vmov.f64 d2, d6 
    28: eeb05b40 vmov.f64 d5, d0 
    2c: eeb03b41 vmov.f64 d3, d1 
    30: f401200d vst4.8 {d2-d5}, [r1]! 
    34: e1520003 cmp r2, r3 
    38: bafffff6 blt 18 <neonPermuteRGBtoBGRA+0x18> 
    3c: e12fff1e bx lr 

Trong trường hợp này armcc tạo mã tốt hơn nhiều. Tôi nghĩ rằng điều này biện minh cho fgp's answer above. Hầu hết thời gian GCC sẽ tạo ra mã đủ tốt, nhưng bạn nên theo dõi các phần quan trọng hoặc quan trọng nhất trước tiên bạn phải đo lường/hồ sơ.

+1

Hãy thử sử dụng cờ '-marm' trong GCC, mã ngón tay cái không phải là quá trưởng thành trong GCC kể từ chưa, thậm chí nhiều hơn như vậy cho đơn vị thumb2 trong Cortex-A9. Đã cập nhật – sgupta

+1

@ user1075375. – auselen

+2

Hmm, như mong đợi, đăng ký tràn là khá rộng với gcc. – sgupta

7

Nếu bạn sử dụng NEON nội tại, trình biên dịch không quan trọng đến mức đó. Hầu hết (nếu không phải tất cả) NEON nội tại dịch sang một hướng dẫn NEON duy nhất, do đó, điều duy nhất còn lại để trình biên dịch là đăng ký phân bổ và lập kế hoạch hướng dẫn. Theo kinh nghiệm của tôi, cả GCC 4.2 và Clang 3.1 đều làm tốt công việc đó.

Lưu ý, tuy nhiên, các hướng dẫn NEON có chút biểu cảm hơn các công cụ NEON. Ví dụ, hướng dẫn tải/lưu trữ NEON có các chế độ địa chỉ trước và sau tăng thêm kết hợp tải hoặc lưu trữ với gia tăng thanh ghi địa chỉ, do đó giúp bạn tiết kiệm một lệnh. Bản chất NEON không cung cấp một cách rõ ràng để làm điều đó, nhưng thay vào đó dựa vào trình biên dịch để kết hợp một tải trọng NEON/lưu trữ nội tại và một địa chỉ gia tăng vào lệnh nạp/lưu trữ với tăng sau. Tương tự, một số hướng dẫn tải/lưu trữ cho phép bạn chỉ định căn chỉnh địa chỉ bộ nhớ và thực thi nhanh hơn nếu bạn chỉ định đảm bảo căn chỉnh chặt chẽ hơn. NEON nội tại, một lần nữa, không cho phép bạn chỉ định căn chỉnh một cách rõ ràng, nhưng thay vào đó dựa vào trình biên dịch để suy ra định danh căn chỉnh chính xác. Về lý thuyết, bạn sử dụng thuộc tính "căn chỉnh" trên con trỏ của bạn để cung cấp gợi ý phù hợp cho trình biên dịch, nhưng ít nhất Clang dường như bỏ qua những ...

Theo kinh nghiệm của tôi, cả Clang lẫn GCC đều rất sáng sủa khi nói đến những loại tối ưu hóa đó. May mắn thay, lợi ích hiệu suất bổ sung của các loại tối ưu hóa này thường không phải là tất cả những gì cao - nó giống như 10% so với 100%.

Một khu vực khác nơi hai trình biên dịch đó không đặc biệt thông minh là tránh làm tràn ngăn xếp. Nếu mã của bạn sử dụng nhiều biến có giá trị vector hơn là có các thanh ghi NEON, tôi dường như cả hai trình biên dịch đều tạo ra mã khủng khiếp. Về cơ bản, những gì họ dường như làm là để sắp xếp hướng dẫn dựa trên giả định rằng có đủ đăng ký có sẵn. Đăng ký phân bổ dường như đến sau đó, và dường như chỉ đơn giản là tràn giá trị vào ngăn xếp khi nó chạy các thanh ghi. Vì vậy, hãy chắc chắn rằng mã của bạn có một bộ làm việc ít hơn 16 vectơ 128 bit hoặc 32 bit-bit 64 bit bất kỳ lúc nào!

Nhìn chung, tôi có kết quả khá tốt từ cả GCC và Clang, nhưng tôi thường xuyên phải tổ chức lại mã một chút để tránh trình biên dịch Idiosyncrasies. Lời khuyên của tôi sẽ là gắn bó với GCC hoặc Clang, nhưng hãy kiểm tra thường xuyên với người chia tách bạn chọn.

Vì vậy, tổng thể, tôi muốn nói gắn bó với GCC là tốt. Bạn có thể muốn xem xét việc tháo gỡ các bộ phận quan trọng về hiệu suất, và kiểm tra xem nó có hợp lý không.

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