【问题标题】:Optimize a nearest neighbor resizing algorithm for speed优化最近邻调整大小算法以提高速度
【发布时间】:2015-02-17 16:34:35
【问题描述】:

我正在使用下一个算法来执行最近邻调整大小。有没有办法优化它的速度?输入和输出缓冲区采用 ARGB 格式,尽管已知图像始终是不透明的。谢谢。

void resizeNearestNeighbor(const uint8_t* input, uint8_t* output, int sourceWidth, int sourceHeight, int targetWidth, int targetHeight)
{
    const int x_ratio = (int)((sourceWidth << 16) / targetWidth);
    const int y_ratio = (int)((sourceHeight << 16) / targetHeight) ;
    const int colors = 4;

    for (int y = 0; y < targetHeight; y++)
    {
        int y2_xsource = ((y * y_ratio) >> 16) * sourceWidth;
        int i_xdest = y * targetWidth;

        for (int x = 0; x < targetWidth; x++)
        {
            int x2 = ((x * x_ratio) >> 16) ;
            int y2_x2_colors = (y2_xsource + x2) * colors;
            int i_x_colors = (i_xdest + x) * colors;

            output[i_x_colors]     = input[y2_x2_colors];
            output[i_x_colors + 1] = input[y2_x2_colors + 1];
            output[i_x_colors + 2] = input[y2_x2_colors + 2];
            output[i_x_colors + 3] = input[y2_x2_colors + 3];
        }
    }
}

【问题讨论】:

  • 看起来它在计算复杂度方面是最优的。此处只能进行外观优化(例如尝试使用 memcpy 而不是输入
  • 由于您假设每个像素有四个 8 位通道,您可以通过直接使用 uint32_t 类型的元素来提高性能。这样,您可以将最内层循环中的四个赋值语句减少到一个,并且您也可以在那里删除几个乘法。 (只有当编译器已经在自己执行这样的优化时,这才无济于事。)
  • 注意:x_ratio, y_ratio, y2_xsource, i_xdest 容易出现未检测到的溢出。
  • x2, y2_x2_colors, i_x_colors 可以简化为 x 递增 1,并且可以利用这一点获得更新的值。这类似于经典的Bresenham line drawing。抱歉 - 无需深入挖掘。
  • 消除循环中的乘法。

标签: c++ c performance optimization


【解决方案1】:

restrict 关键字会有很大帮助,假设没有别名。

另一个改进是将另一个 pointerToOutputpointerToInput 声明为 uint_32_t,这样四个 8 位复制赋值可以组合成一个 32 位,假设指针是 32 位对齐的。

【讨论】:

    【解决方案2】:

    您几乎无法加快速度,因为您已经按照正确的顺序排列了循环并巧妙地使用了定点算法。正如其他人建议的那样,尝试一次移动 32 位(希望编译器还没有看到)。

    在显着放大的情况下,有一种可能性:您可以确定每个源像素需要复制多少次(您需要处理整数关系 Xd=Wd.Xs/Ws 的属性) ,并为 k 次写入执行单个像素读取。这也适用于 y,您可以 memcpy 相同的行而不是重新计算它们。您可以使用游程编码预先计算 X 和 Y 的映射并将其制成表格。

    但是有一个你不会通过的障碍:你需要填充目标图像。

    如果您迫切希望加快速度,则可以选择使用矢量运算(SEE 或 AVX)一次处理多个像素。可以使用随机播放指令来控制像素的复制(或抽取)。但由于复杂的复制模式与向量寄存器的固定结构相结合,您可能需要集成一个复杂的决策表。

    【讨论】:

    • 这种方法只适用于放大图像,不适用于缩小图像。您可以创建两个函数,针对每个目的进行优化。
    【解决方案3】:

    算法很好,但您可以通过将图像提交到 GPU 来利用大规模并行化。如果您使用 opengl,只需创建新大小的上下文并提供适当大小的四边形即可为您提供固有的最近邻计算。此外,opengl 可以让您访问其他调整大小采样技术,只需更改您从中读取的纹理的属性(这相当于一个 gl 命令,这可能是您调整大小函数的一个简单参数)。

    同样在开发后期,您可以简单地将着色器换成其他混合技术,这也可以让您继续利用出色的图像处理 GPU 处理器。

    此外,由于您没有使用任何花哨的几何图形,因此编写程序几乎变得微不足道。它会比您的算法更复杂一些,但它可以根据图像大小更快地执行数量级。

    【讨论】:

    • “幅度更快”:记忆之间的传输呢​​?这将是瓶颈,不是吗?
    • 这就是它开始依赖硬件的地方。走 GPU 路线的好处与 CPU 速度有关:GPU 总线速度(取决于 GPU 实现)比率。如果你有一个疯狂的 3-4GHz 处理器可以使用,那么你可能会在传输时间上浪费一点时间,但对于速度较慢的处理器或实施良好的 GPU,你可以为大图像节省很多时间。
    • 我仍然不明白怎么做,因为这个函数本质上是将源像素复制到目标像素并且做很少的计算。瓶颈是主存速度。
    • 还要考虑并行化。如果您正在进行任何图像处理,那么让 gpu 处理它并让您的 cpu 考虑其他出色的事情是很有意义的。事实上,鼓励像这样的其他功能进程转移到 GPU 或处理场,因此 openCL 正在寻找它进入我们生活的方式。 CPU 应该处理更多的逻辑/分支操作,而我们让 GPU 吃掉愚蠢的数字。
    【解决方案4】:

    我希望我没有破坏任何东西。这结合了迄今为止发布的一些建议,速度提高了约 30%。我很惊讶这就是我们所得到的。我实际上并没有检查目标图像是否正确。

    变化: - 从内部循环中删除乘法(10% 改进) - uint32_t 代替 uint8_t(10% 改进) - __restrict 关键字(1% 改进)

    这是在运行 Windows 的 i7 x64 机器上使用 MSVC 2013 编译的。您必须为其他编译器更改 __restrict 关键字。

    void resizeNearestNeighbor2_32(const uint8_t* __restrict input, uint8_t* __restrict output, int sourceWidth, int sourceHeight, int targetWidth, int targetHeight)
    {
        const uint32_t* input32 = (const uint32_t*)input;
        uint32_t* output32 = (uint32_t*)output;
    
        const int x_ratio = (int)((sourceWidth << 16) / targetWidth);
        const int y_ratio = (int)((sourceHeight << 16) / targetHeight);
    
        int x_ratio_with_color = x_ratio;
    
        for (int y = 0; y < targetHeight; y++)
        {
            int y2_xsource = ((y * y_ratio) >> 16) * sourceWidth;
            int i_xdest = y * targetWidth;
    
            int source_x_offset = 0;
            int startingOffset = y2_xsource;
            const uint32_t * inputLine = input32 + startingOffset;
            for (int x = 0; x < targetWidth; x++)
            {
                i_xdest += 1;
                source_x_offset += x_ratio_with_color;
                int sourceOffset = source_x_offset >> 16;
    
                output[i_xdest] = inputLine[sourceOffset];
            }
        }
    }
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2014-04-09
      • 1970-01-01
      • 2014-11-15
      • 1970-01-01
      • 2017-02-01
      • 2021-04-12
      • 2022-10-13
      • 1970-01-01
      相关资源
      最近更新 更多