指令集加速例子

发布时间 2023-03-22 21:11:19作者: 兜尼完

从事图像处理6年多,一直使用VS+OpenCV做开发。在某些情况下需要自己实现一些算法。虽然编译器在Release版对代码优化的很好,但它并不总是能自动使用最合适的指令集对代码进行优化。这时候就需要手动优化。下面是Intel指令集内联函数官方在线文档链接:

下面给出一个例子,开发环境是VS2015、Qt5.9和OpenCV任意版本,CPU型号是Intel Core i5-7400。下述代码的应用场景是图像的模板匹配。想象:变量grad是一张运行图片的梯度图(即对整个输入图像求梯度向量),变量borders是模板图片提取的边缘点链(保存边缘点的位置和梯度向量)。为了搜索图片模板在运行图像中的位置,需要使用滑动窗口遍历整张运行图片,然后计算模板点和对应运行图像的点的梯度向量的点积(即相似度,范围是[-1,1])。下述代码提供了普通方法和几种不同的指令集加速方法,证实了即使在Release版编译器有优化的情况下采用合适的指令集加速也明显比普通C++代码速度更快。只不过加速效果不如理论值,最高只加速了2-3倍。代码中qDebug()是Qt库的调试输出函数,如果你在控制台程序中测试可以换成std::cout输出。需要包含AVX指令集头文件#include <immintrin.h>。

struct Border2f
{
    Point2f point;
    Point2f grad;
};

void testVec()
{
    Mat grad(320, 320, CV_32FC2, Scalar(0.707107f, 0.707107f));
    vector<Border2f> borders;
    for (int i = 0; i < 20; i++)
    {
        borders.push_back({ Point2f(i, i), Point2f(0.707107f, 0.707107f) });
    }
    Mat score = Mat::zeros(grad.size(), CV_32FC1);
    // 测试确保32字节对齐
    ASSERT(int64(grad.data) % 32 == 0);
    ASSERT(int64(score.data) % 32 == 0);

    int64 t1 = getTickCount();
    for (int k = 0; k < 20; k++)
    {
        int px = borders[k].point.x;
        int py = borders[k].point.y;
        for (int i = 0; i < 300; i++)
        {
            const Vec2f* line = grad.ptr<Vec2f>(py + i);
            float* sccc = score.ptr<float>(i);
            for (int j = 0; j < 300; j++)
            {
                float value = line[j + px][0] * borders[k].grad.x + line[j + px][1] * borders[k].grad.y;
                sccc[j] += value;
            }
        }
    }
    score /= 20;
    int64 t2 = getTickCount();
    qDebug() << score.at<float>(10, 10) << u8"普通的(ms)=" << (t2 - t1) / getTickFrequency() * 1000;

    score = 0;
    int64 t3 = getTickCount();
    for (int k = 0; k < 20; k++)
    {
        int px = borders[k].point.x;
        int py = borders[k].point.y;
        __m128 mmx2 = _mm_set_ps(borders[k].grad.x, borders[k].grad.y, 0, 0);
        for (int i = 0; i < 300; i++)
        {
            const Vec2f* line = grad.ptr<Vec2f>(py + i);
            float* sccc = score.ptr<float>(i);
            for (int j = 0; j < 300; j++)
            {
                __m128 mmx1 = _mm_load_ps(&line[j + px][0]);
                __m128 add1 = _mm_dp_ps(mmx1, mmx2, 0b11110001);
                sccc[j] += _mm_cvtss_f32(add1);
            }
        }
    }
    score /= 20;
    int64 t4 = getTickCount();
    qDebug() << score.at<float>(10, 10) << u8"优化VEC的(ms)=" << (t4 - t3) / getTickFrequency() * 1000;

    score = 0;
    int64 t5 = getTickCount();
    for (int k = 0; k < 20; k++)
    {
        int px = borders[k].point.x;
        int py = borders[k].point.y;
        __m128 mmx3 = _mm_set_ps(borders[k].grad.x, borders[k].grad.y, borders[k].grad.x, borders[k].grad.y);
        for (int i = 0; i < 300; i++)
        {
            const Vec2f* line = grad.ptr<Vec2f>(py + i);
            float* sccc = score.ptr<float>(i);
            for (int j = 0; j < 300; j += 4)
            {
                __m128 mmx1 = _mm_load_ps(&line[j + px][0]);
                __m128 mul1 = _mm_mul_ps(mmx1, mmx3);
                __m128 mmx2 = _mm_load_ps(&line[j + px + 2][0]);
                __m128 mul2 = _mm_mul_ps(mmx2, mmx3);
                __m128 add1 = _mm_hadd_ps(mul1, mul2);
                __m128 add2 = _mm_load_ps(&sccc[j]);
                __m128 actr = _mm_add_ps(add1, add2);
                _mm_store_ps(&sccc[j], actr);
            }
        }
    }
    score /= 20;
    int64 t6 = getTickCount();
    qDebug() << score.at<float>(10, 10) << u8"优化SSE的(ms)=" << (t6 - t5) / getTickFrequency() * 1000;

    score = 0;
    int64 t7 = getTickCount();
    for (int k = 0; k < 20; k++)
    {
        int px = borders[k].point.x;
        int py = borders[k].point.y;
        __m128 mmx128 = _mm_set_ps(borders[k].grad.x, borders[k].grad.y, borders[k].grad.x, borders[k].grad.y);
        __m256 mmx256 = _mm256_set_m128(mmx128, mmx128);
        for (int i = 0; i < 300; i++)
        {
            const Vec2f* line = grad.ptr<Vec2f>(py + i);
            float* sccc = score.ptr<float>(i);
            for (int j = 0; j < 296; j += 8)
            {
                __m256 mmx1 = _mm256_load_ps(&line[j + px][0]);
                __m256 mul1 = _mm256_mul_ps(mmx1, mmx256);
                __m256 mmx2 = _mm256_load_ps(&line[j + px + 4][0]);
                __m256 mul2 = _mm256_mul_ps(mmx2, mmx256);
                __m256 add1 = _mm256_hadd_ps(mul1, mul2);
                __m256 add2 = _mm256_load_ps(&sccc[j]);
                __m256 actr = _mm256_add_ps(add1, add2);
                _mm256_store_ps(&sccc[j], actr);
            }
            for (int j = 296; j < 300; j += 4)
            {
                __m128 mmx1 = _mm_load_ps(&line[j + px][0]);
                __m128 mul1 = _mm_mul_ps(mmx1, mmx128);
                __m128 mmx2 = _mm_load_ps(&line[j + px + 2][0]);
                __m128 mul2 = _mm_mul_ps(mmx2, mmx128);
                __m128 add1 = _mm_hadd_ps(mul1, mul2);
                __m128 add2 = _mm_load_ps(&sccc[j]);
                __m128 actr = _mm_add_ps(add1, add2);
                _mm_store_ps(&sccc[j], actr);
            }
        }
    }
    score /= 20;
    int64 t8 = getTickCount();
    qDebug() << score.at<float>(10, 10) << u8"优化AVX的(ms)=" << (t8 - t7) / getTickFrequency() * 1000;
}

 下面是运行50次的输出数据(Release版):

1 普通的(ms)= 3.6468
1 优化VEC的(ms)= 1.9677
1 优化SSE的(ms)= 1.0773
1 优化AVX的(ms)= 0.8371
1 普通的(ms)= 2.0903
1 优化VEC的(ms)= 2.2704
1 优化SSE的(ms)= 1.0037
1 优化AVX的(ms)= 0.613
1 普通的(ms)= 2.1404
1 优化VEC的(ms)= 4.7986
1 优化SSE的(ms)= 1.115
1 优化AVX的(ms)= 0.7623
1 普通的(ms)= 1.7461
1 优化VEC的(ms)= 2.1931
1 优化SSE的(ms)= 0.9493
1 优化AVX的(ms)= 1.1275
1 普通的(ms)= 3.5117
1 优化VEC的(ms)= 2.1232
1 优化SSE的(ms)= 0.8816
1 优化AVX的(ms)= 0.6049
1 普通的(ms)= 2.0143
1 优化VEC的(ms)= 4.8926
1 优化SSE的(ms)= 1.5648
1 优化AVX的(ms)= 0.9288
1 普通的(ms)= 2.8638
1 优化VEC的(ms)= 2.5643
1 优化SSE的(ms)= 1.2164
1 优化AVX的(ms)= 0.8905
1 普通的(ms)= 2.6565
1 优化VEC的(ms)= 5.7017
1 优化SSE的(ms)= 0.8229
1 优化AVX的(ms)= 0.8778
1 普通的(ms)= 2.032
1 优化VEC的(ms)= 1.9618
1 优化SSE的(ms)= 0.796
1 优化AVX的(ms)= 0.5937
1 普通的(ms)= 1.7191
1 优化VEC的(ms)= 2.0014
1 优化SSE的(ms)= 0.7839
1 优化AVX的(ms)= 0.6547
1 普通的(ms)= 2.5793
1 优化VEC的(ms)= 2.5262
1 优化SSE的(ms)= 1.0038
1 优化AVX的(ms)= 0.6846
1 普通的(ms)= 1.8684
1 优化VEC的(ms)= 2.0676
1 优化SSE的(ms)= 0.863
1 优化AVX的(ms)= 0.6861
1 普通的(ms)= 1.987
1 优化VEC的(ms)= 2.043
1 优化SSE的(ms)= 0.798
1 优化AVX的(ms)= 0.6719
1 普通的(ms)= 4.4993
1 优化VEC的(ms)= 2.2936
1 优化SSE的(ms)= 2.0022
1 优化AVX的(ms)= 0.9909
...<输出太多删除一部分>
1 普通的(ms)= 1.7674
1 优化VEC的(ms)= 2.4455
1 优化SSE的(ms)= 0.8557
1 优化AVX的(ms)= 0.6413
1 普通的(ms)= 1.8348
1 优化VEC的(ms)= 2.1462
1 优化SSE的(ms)= 0.8002
1 优化AVX的(ms)= 0.7038
1 普通的(ms)= 1.8552
1 优化VEC的(ms)= 2.1501
1 优化SSE的(ms)= 0.8713
1 优化AVX的(ms)= 1.088
1 普通的(ms)= 1.7212
1 优化VEC的(ms)= 1.8769
1 优化SSE的(ms)= 0.8949
1 优化AVX的(ms)= 0.7477
1 普通的(ms)= 1.7201
1 优化VEC的(ms)= 1.8817
1 优化SSE的(ms)= 0.7227
1 优化AVX的(ms)= 0.5767
1 普通的(ms)= 1.6432
1 优化VEC的(ms)= 1.8463
1 优化SSE的(ms)= 0.7296
1 优化AVX的(ms)= 0.5427
1 普通的(ms)= 1.6331
1 优化VEC的(ms)= 1.9023
1 优化SSE的(ms)= 0.9842
1 优化AVX的(ms)= 0.6735
1 普通的(ms)= 1.7306
1 优化VEC的(ms)= 1.9457
1 优化SSE的(ms)= 0.7248
1 优化AVX的(ms)= 0.5347
1 普通的(ms)= 1.6297
1 优化VEC的(ms)= 1.804
1 优化SSE的(ms)= 1.3137
1 优化AVX的(ms)= 0.891
1 普通的(ms)= 2.0636
1 优化VEC的(ms)= 2.0244
1 优化SSE的(ms)= 0.9372
1 优化AVX的(ms)= 0.546
1 普通的(ms)= 1.6895
1 优化VEC的(ms)= 1.8947
1 优化SSE的(ms)= 0.7086
1 优化AVX的(ms)= 0.5351
1 普通的(ms)= 1.624
1 优化VEC的(ms)= 1.8509
1 优化SSE的(ms)= 0.7791
1 优化AVX的(ms)= 0.5626
1 普通的(ms)= 1.7917
1 优化VEC的(ms)= 1.9443
1 优化SSE的(ms)= 0.7838
1 优化AVX的(ms)= 0.5689
1 普通的(ms)= 1.66
1 优化VEC的(ms)= 1.8985
1 优化SSE的(ms)= 0.7233
1 优化AVX的(ms)= 0.7324
1 普通的(ms)= 1.8586
1 优化VEC的(ms)= 2.0881
1 优化SSE的(ms)= 0.8729
1 优化AVX的(ms)= 0.9853