【问题标题】:Run Length Encoding - SIMD运行长度编码 - SIMD
【发布时间】:2013-02-01 08:45:45
【问题描述】:

我正在尝试优化运行长度编码。我正在考虑在 SIMD 中实现它。我花了几个小时研究算法,但进展不大。值得试一试吗?我正在研究 Neon。

谢谢。

【问题讨论】:

  • 请详细说明您的案例。什么是压缩的,你的 rle 是如何编码的。可以添加一些示例代码(不带 simd 和带)?你的CPU是什么?对于典型大小的输入,您当前的速度(以 MB/s 为单位)或延迟是多少?目标是什么。
  • 我正在尝试优化 JPEG 压缩的运行长度编码部分。JPEG 编码所花费的时间的主要部分是这个 RLE。我正在ARM平台上工作。它有 NEON simd 引擎。
  • 是的。我正在研究 Cortex A9
  • 你的 JPEG 实现是什么?

标签: arm jpeg simd neon run-length-encoding


【解决方案1】:

我假设您的问题在于 JPEG 中 AC 块系数编码中的 RLE。

在 JPEG 中使用的 RLE 变体非常具体。每个 8x8 像素块都用 DCT 进行转换和量化。然后将 DCT 输出(64 个系数)分离为第一个 DC 系数和 63 个 AC 系数。使用 Zig-Zag 模式将 8x8 块 AC 系数转换为线性阵列,然后进行 RLE 编码

ZigZag 通常与 RLE 运行结合使用,因此我们的 RLE 具有非线性内存访问,应该优化这两个功能。

警告! JPEG 中的 RLE 仅用于零元素!请检查 ISO/IEC 10918-1 : 1993 标准的 F.1.2.2.1、F.1.2.2.3Figure F.2

一些实现:

libjpeg-tubro(主页是 libjpeg-turbo.org),其中一些分支是 Linaro 在 2010-2011 年为 ARM 开发的 optimized

这个分叉中有list of optimizations

  • 10 - “forward_DCT”中量化代码的 ARM NEON 优化(整数除法替换为浮点乘法。...这个优化的函数现在几乎比原始 C 变体快 3 倍 - jcdctmgr.c - Siarhei Siamashka 2010-11- 10
  • 9 - “encode_one_block”的 ARM 程序集优化(“encode_one_block”的 ARM 程序集优化。几乎比原始 C 变体快 2 倍。) - jchuff.c - Siarhei Siamashka 2010-11-10
  • 8 - 'rgb_ycc_convert' 的 ARM NEON 优化版本 - Siarhei Siamashka 2010-11-10
  • 7 - 'ycc_rgb_convert' 的 ARM NEON 优化版本 - Siarhei Siamashka 2010-11-10
  • 6 - 对“convsamp”的轻微 ARM NEON 优化 - Siarhei Siamashka 2010-11-10
  • 5 - 'jpeg_fdct_ifast' 的 ARM NEON 优化版本 - Siarhei Siamashka 2010-11-10
  • 4 - 'jpeg_idct_ifast' 的 ARM NEON 优化版本 - Siarhei Siamashka 2010-11-10
  • 3 - 'jpeg_idct_4x4' 的 ARM NEON 优化版本 - Siarhei Siamashka 2010-11-10

Revision 9jchuff.c 文件的ARM 优化,用于函数encode_one_block,它编码块的AC 和DC 分量;但它是在不使用 NEON 的情况下完成的

/* Encode a single block's worth of coefficients */
LOCAL(boolean)   encode_one_block 

事实上,RLE 并没有优化;但是 ZigZag 和最后一个零系数检测是在 ARM 程序集中的 find_last_nonzero_index 辅助函数中实现的。 (它是使用ruby generator 生成的)或in linaro git。它计划用于双发 Cortex-A8:

* Find last nonzero coefficient and produce output in natural order,
* instructions are scheduled to make use of ARM Cortex-A8 dual-issue
* capability

这是函数对应的C代码:

LOCAL(int)
find_last_nonzero_index (JCOEFPTR block, JCOEFPTR out)
{
  int tmp, i, n = 0;
  for (i = 1; i < DCTSIZE2; i++) {
    if ((tmp = block[jpeg_natural_order[i]]) != 0)
      n = i;
    out[i] = tmp;
}
return n;

这里有 RLE 本身的 ARM 或 NEON 优化,但我认为这个 asm 代码有助于 RLE,将其转换为线性内存访问版本(encode_one_block,在ifdef __arm__ 下):

  for (k = 2; k <= last_nonzero_index; k += 2) {
      innerloop(k);
  }

innerloop 宏中使用的r 是 RLE 计数器。

这种带有手动编码之字折线(适用于 Cortex-A8)的线性 RLE 应该比原始 C 版本更快,即使对于 Cortex-A9 也是如此。感谢 Siarhei Siamashka!

PS:当前版本的 libjpeg-turbo 在此 RLE 中没有 arm 优化:encode_one_block, Line 454 of jchuff.c, rev929 - 内循环被稍微改写为kloop,但 RLE 仍然以非线性方式完成;之字形没有与之分离。

关于 NEON 的一些想法

NEON 有一组 32 个寄存器,每个 64 位宽(D0..D31;根据 Anderson @ ELC 2011,page 5),理论上,可用于存储 64 个 16 位系数并实现之字形+RLE。仍在寻找实现...

MPEG 标准中有类似的 zig-zag + RLE,在 x86 和 arm 上也有一些 SIMD 实现的努力。有一个blog post in x264dev; 8x8 zigzag x264_zigzag_scan_8x8_frame 是为带有 SSSE3 的 x86 实现的。但是对于 ARM,x264 中只有 4x4 NEON zigzag

PS:仅适用于我和任何不了解 Jpeg 内部结构的人。 Cardiff CM0340 lecture slides 中有简短易懂的 JPEG 编码介绍。

PPS(2013 年 2 月 18 日 13:30 更新):为了优化 RLE 编码,我们可以进行预扫描,在 AC 系数中间搜索零,然后使用这些预先计算的数据。我们甚至可以将其保存为半字节或一些 NEON regs 中的位

2 月 18 日更新:补丁作者说提交 9 的评论不准确。将此代码与 jpeg6b(而不是 libjpeg-turbo)进行比较时,改进了 2 倍。他说 unroll by 63(如在 libjpeg-tubro 中)与这个 asm 解决方案的速度几乎相同(在某些测试中,它在其他方面稍好一些)。

【讨论】:

  • 感谢您的回复。我已经优化了 Neon 中的 DCT、Quant 和 zigzag 模块,我想知道是否可以为 RLE 部分做点什么。在浏览开源代码时,我得到了一些提示。
  • 你的作品是开源的吗?你是从 libjpeg 还是 libjpeg-turbo 开始的?你得到了什么提示?
  • 最后一个非零函数和之字形扫描可以在NEON中实现。但真正的问题不止于此。是否可以在 neon 中并行计算 run,以便优化 AC 大小和 run 编码。但是,我觉得它在 NEON 中是不可能的,至少在运行部分。所以是的,我有点卡在那里。您对如何以 SIMD 方式计算运行有任何提示吗?可以通过某种方式计算运行,但最困难的部分是存储忽略零系数并使用 NEON 仅存储非零系数的运行和值。
  • Rugger,你能发布你当前的代码吗?您可以将其作为更新添加到问题中。
猜你喜欢
  • 2012-07-11
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多