优化 – 优化随机访问双线性采样

我正在研究一个老式的“图像扭曲”滤镜.基本上,我有一个2D像素阵列(忽略目前的问题,无论是彩色,灰度,浮点,RGBA等)和另一个2D矢量数组(带有浮点组件),图像位于至少与矢量数组一样大.在伪代码中,我想这样做:

FOR EACH PIXEL (x,y)      
  vec = vectors[x,y]                    // Get vector
  val = get(img, x + vec.x, y + vec.y)  // Get input at <x,y> + vec
  output[x,y] = val                     // Write to output

问题是get()需要对输入图像进行双线性采样,因为矢量可以引用子像素坐标.但不像双线性采样,比如纹理映射,我们可以将插值数学运算到循环中,所以它只是添加,这里的读取来自随机位置.所以get()的定义看起来像这样:

FUNCTION get(in,x,y)
  ix = floor(x); iy = floor(y)      // Integer upper-left coordinates
  xf = x - ix;   yf = y - iy        // Fractional parts

  a = in[ix,iy];   b = in[iy+1,iy]   // Four bordering pixel values
  b = in[ix,iy+1]; d = in[ix+1,iy+1]

  ab = lerp(a,b,xf)                  // Interpolate
  cd = lerp(c,d,xf)
  RETURN lerp(ab,cd,yf)

和lerp()很简单

FUNCTION lerp(a,b,x) 
  RETURN (1-x)*a + x*b

假设输入图像和矢量数组都不是预先知道的,那么可以进行哪种高级优化? (注意:“使用GPU”是作弊.)我能想到的一件事是在get()中重新排列插值数学,以便我们可以缓存给定(ix,iy)的像素读取和中间计算.这样,如果连续访问是相同的子像素四边形,我们可以避免一些工作.如果预先知道向量数组,那么我们可以重新排列它,以便传递给get()的坐标更倾向于局部.这也可能有助于缓存局部性,但代价是对输出的写入是全部的.但是,我们不能做一些奇特的事情,比如在飞行中缩放矢量,或者甚至将扭曲效果从其原始预先计算的位置移开.

唯一的另一种可能性是使用定点矢量分量,可能具有非常有限的分数部分.例如,如果矢量仅具有2位分数分量,则仅可访问16个子像素区域.我们可以预先计算这些权重,并完全避免大量的插值数学,但是质量会受到影响.

还有其他想法吗?我想在实现它们之前积累一些不同的方法,看看哪个是最好的.如果有人能指出快速实现的源代码,那就太好了.

最佳答案 有趣的问题.

您的问题定义基本上强制对[x,y]进行不可预测的访问 – 因为可能会提供任何向量.假设矢量图像倾向于引用局部像素,那么第一个优化就是确保以合适的顺序遍历内存以充分利用缓存局部性.这可能意味着在“for each pixel”循环中扫描32 * 32块,以便[x,y]在短时间内尽可能频繁地击中相同的像素.

很可能你的算法的性能将受到两件事的约束

>从主内存加载向量[x,y]和[x,y]的速度有多快
>进行乘法和求和需要多长时间

有SSE指令可以一次将多个元素相乘,然后将它们相加(乘法和累加).你应该做的就是计算

af = (1 - xf) * ( 1 - yf )
bf = (    xf) * ( 1 - yf )
cf = (1 - xf) * (     yf )
df = (    xf) * (     yf )

然后计算

a *= af
b *= bf 
c *= cf
d *= cf
return (a + b + c + d)

很有可能这两个步骤都可以用极少量的SSE指令完成(取决于你的像素表示).

我认为缓存中间值不太可能有用 – 似乎极不可能> 1%的向量请求将指向完全相同的位置,并且缓存内容将花费你更多的内存带宽而不是它将节省.

如果在处理向量[x,y]时使用cpu上的预取指令来预取[vectors [x 1,y]],则可能会提高内存性能,CPU无法预测随机游走除此之外的内存.

提高算法性能的最后一种方法是同时处理输入像素的块,即x [0..4],x [5..8] – 这使您可以展开内部数学循环.但是,你很可能受到记忆限制,这无济于事.

点赞