用活跃的霓虹灯标志编译代码几乎没有任何改进(甚至恶化)

时间:2015-02-17 18:09:12

标签: c++ arrays optimization arm neon

我试图了解在gcc编译器中使用活动的霓虹灯标志编译C ++代码的可能好处。为此,我制作了一个小程序,它遍历数组并进行简单的算术运算。

我更改了代码,以便任何人都可以编译并运行它。如果有人能够很好地执行此测试并分享结果,我将非常感激:)

编辑:我真的问一个碰巧在附近有Cortex-A9板的人来执行这个测试并检查结果是否相同。我真的很感激。

#include <ctime>

int main()
{
    unsigned long long arraySize = 30000000;

    unsigned short* arrayShort = new unsigned short[arraySize];

    std::clock_t begin;

    for (unsigned long long n = 0; n < arraySize; n++)
    {
        *arrayShort = rand() % 100 + 1;
        arrayShort++;
    }

    arrayShort -= arraySize;

    begin = std::clock();
    for (unsigned long long n = 0; n < arraySize; n++)
    {
        *arrayShort += 10;
        *arrayShort /= 3;

        arrayShort++;
    }

     std::cout << "Time: " << (std::clock() - begin) / (double)(CLOCKS_PER_SEC / 1000) << " ms" << std::endl;

    arrayShort -= arraySize;
    delete[] arrayShort;

    return 0;
}

基本上,我用1到100之间的随机数填充一个30000000大小的数组,然后我遍历所有元素总和10并除以3.我期望用活动的霓虹灯标记编译这个代码会带来很大的改进由于它能够一次进行多个阵列操作。

我正在使用带有GCC 4.8.3的Linaro工具链编译此代码以在Cortex A9 ARM板中运行。 我使用和不使用以下标志编译了这段代码:

-O3 -mcpu=cortex-a9 -ftree-vectorize -mfloat-abi=hard -mfpu=neon 

我还复制了代码,使用unsigned int,float和double类型的数组运行,这些是以秒为单位的结果:

Array type unsigned short: 
With NEON flags: 0.07s
Without NEON flags: 0.089s

Array type unsigned int: 
With NEON flags: 0.524s
Without NEON flags: 0.529s

Array type float: 
With NEON flags: 0.65s
Without NEON flags: 0.673s

Array type double: 
With NEON flags: 0.955s
Without NEON flags: 0.927s

你可以看到,在大多数情况下,使用霓虹灯标志几乎没有任何改善,甚至在双打数组的情况下也会导致更糟糕的结果。

我真的觉得我在这里做错了,可能你可以帮我解释这些结果。

2 个答案:

答案 0 :(得分:3)

我必须修改你的代码:

#include <iostream>
#include <cstdlib>

之后,GCC 5.0将你的循环自动调整为:

.L7:
    vld1.64 {d16-d17}, [r1:64]
    adds    r4, r4, #1
    vadd.i16    q8, q8, q11
    adc r5, r5, #0
    cmp r3, r5
    add r1, r1, #16
    vmull.u16 q9, d16, d20
    cmpeq   r2, r4
    vmull.u16 q8, d17, d21
    add lr, lr, #16
    vuzp.16 q9, q8
    vshr.u16    q8, q8, #1
    vstr    d16, [lr, #-16]
    vstr    d17, [lr, #-8]
    bhi .L7

所以是的,编译器可以自动向量化代码,但这有什么用呢?在附近的Cortex-A7主板上,我看到以下几次:

g++ ~/foo.cpp -O3
./a.out 
Time: 129.355 ms

g++ ~/foo.cpp -O3 -fno-tree-vectorize
./a.out 
Time: 430.405 ms

看看你希望4x矢量化因子(4x16位值)的内容。

在这种情况下,我认为数据和生成的汇编代码不言自明,并驳斥了上述评论中的一些声明。编译器可以并且将会执行自动矢量化,您可以从中获得的性能是一个有意义的加速。

另外值得注意的是,编译器已经从评论中击败了一位专家汇编程序员!

  

NEON不支持整数除法,因此没有任何内容   量化。尝试乘法运算。

在一般情况下是的,是的。但是有效的序列存在使用Neon除以特定常数,而'3'恰好是这些常数之一!

我的Linaro / Ubuntu GCC 4.8.2系统编译器还对此代码进行了矢量化处理,产生了与上述代码非常相似的代码,具有相似的时序。

答案 1 :(得分:0)

我尝试使用arm_neon.h内在函数重新编写此代码,结果非常令人惊讶......以至于我需要一些帮助来解释它们。

以下是代码:

#include <ctime>
#include <stdio.h>
#include <cstdlib>
#include <arm_neon.h>

int main()
{
    unsigned long long arraySize = 125000000;

     std::clock_t begin;

    unsigned short* arrayShort = new unsigned short[arraySize];

    for (unsigned long long n = 0; n < arraySize; n++)
    {
        *arrayShort = rand() % 100 + 1;
        arrayShort++;
    }

    arrayShort -= arraySize;

    uint16x8_t vals;
    uint16x8_t constant1 = {10, 10, 10, 10, 10, 10, 10, 10};
    uint16x8_t constant2 = {3, 3, 3, 3, 3, 3, 3, 3};

    begin = std::clock();
    for (unsigned long long n = 0; n < arraySize; n+=8)
    {
        vals = vld1q_u16(arrayShort);
        vals = vaddq_u16(vals, constant1);
        vals = vmulq_u16(vals, constant2);

//      std::cout << vals[0] <<  "   " << vals[1] <<  "   " << vals[2] <<  "   " << vals[3] <<  "   " << vals[4] <<  "   " << vals[5] <<  "   " << vals[6] <<  "   " << vals[7] <<  std::endl;

        arrayShort += 8;
    }

    std::cout << "Time: " << (std::clock() - begin) / (double)(CLOCKS_PER_SEC / 1000) << " ms" << std::endl;

    arrayShort -= arraySize;
    delete[] arrayShort;

    return 0;
}

所以,现在我正在创建一个1.25亿元长的无符号短裤阵列。然后我一次检查8个元素,然后加10,然后乘以3.

在cortex A9主板上,此代码的普通C ++版本需要 270毫秒来处理该阵列,而此NEON代码仅需 20毫秒

现在,在看到结果之前我的期望并不高,但是,我头脑中最好的情况是减少了8倍的时间。我无法解释这是如何导致执行时间缩短13.5倍的。我非常感谢帮助解释这些结果。

我很明显看到数学的结果输出已完成,我可以确保代码正常工作,结果非常一致。