尽快找到五个小整数中的最大两个


9

我在小型嵌入式系统上对图像数据使用5交叉中值滤波器,即

    x
  x x x
    x

该算法非常简单:读取5个无符号整数值,获得最大的2个值,对这些值进行一些计算,然后写回无符号整数结果。

好的是5个整数输入值都在0-20的范围内。计算出的整数值也在0-20范围内!

通过分析,我发现获得最大的两个数字是瓶颈,因此我想加快这一步。什么是执行此选择最快的方法?

当前算法使用由5个数字指定的位置中的1位的32位掩码和HW支持的CLZ函数。
我应该说CPU是专有的,在我公司之外无法使用。我的编译器是GCC,但为此CPU量身定制。

我试图弄清楚是否可以使用查找表,但未能生成可以使用的密钥。

我有输入组合,但顺序并不重要,即与相同。215[5,0,0,0,5][5,5,0,0,0]

碰巧下面的哈希函数会产生完美的哈希而不会发生冲突!

def hash(x):
    h = 0
    for i in x:
        h = 33*h+i
    return h

但是散列是巨大的,并且根本没有足够的内存来使用它。

我可以使用更好的算法吗?是否可以使用查找表并生成密钥来解决我的问题?


1
您目前使用哪种算法?七个整数比较就足够了,这太慢了吗?您hash已经执行了更多操作。对该方法的后续调用是否相关,例如中心是否x逐行穿过矩阵?
拉斐尔

过滤器逐行卷积在图像中。即获取5个值并进行计算,然后将所有步骤向右移动一步,然后重复。哈希只是一个例子。我对几种滑动窗口解决方案进行了基准测试,以最大程度地减少数据读取,但是所有这些归结为找到最高的2个值。
Fredrik Pihl

3
如果正确实施,您的算法很可能会受到内存访问的限制,而不受计算的限制。使用哈希表只会增加内存访问量,并使速度变慢。请发布您当前的代码,以便我们可以看到如何对其进行改进-我相信只能进行微优化。我能想到的最多是:也许我们可以利用相邻窗口之间共有2个值的事实?
jkff

@jkff根据矩阵,高速缓存大小和(高速缓存)映射功能,每个值可能只需要加载一次;然后,大多数操作应在寄存器或L1缓存上运行。但是,流水线处理是另一个问题。
拉斐尔

1
顺便说一句,您是否已经并行执行此操作?这似乎特别适合矢量并行化或SIMD(例如在GPU上)。这条路线将比每个单元节省几个百分点多得多。
拉斐尔

Answers:


11

在我的其他答复中,我建议有条件的跳跃可能是效率的主要障碍。结果, 想到了排序网络:它们与数据无关,即无论输入如何,都执行相同的比较序列,只有交换是有条件的。

当然,分类可能是太多的工作。我们只需要最大的两个数字。对我们来说幸运的是,还研究了选择网络。Knuth的告诉我们发现两个最小数出five²的功能也可以用 ù 25 = 6周的比较[1,5.3.4前19](和至多多达掉期)。ü^25=6

他在解决方案中提供的网络(重写为从零开始的数组)是

[04][1个4][03][1个3][02][1个2]

它在调整比较方向后以伪代码实现为

def selMax2(a : int[])
  a.swap(0,4) if a[0] < a[4]
  a.swap(1,4) if a[1] < a[4]
  a.swap(0,3) if a[0] < a[3]
  a.swap(1,3) if a[1] < a[3]
  a.swap(0,2) if a[0] < a[2]
  a.swap(1,2) if a[1] < a[2]
  return (a[0], a[1])
end

现在,幼稚的实现仍然有条件跳转(跨交换代码)。但是,根据您的计算机,您可以使用条件指令来简化它们。x86似乎是其通常的泥潭。ARM看起来更有希望,因为显然大多数操作 本身都是有条件的。如果我 正确理解了说明,则假定我们的数组值已R0通过R4以下方式加载到寄存器中,则第一次交换会转换为该指示

CMP     R0,R4
MOVLT   R5 = R0
MOVLT   R0 = R4
MOVLT   R4 = R6

是的,是的,您当然可以将XOR交换EOR一起使用

我只是希望您的处理器具有此功能或类似功能。当然,如果您为此目的建造东西,也许您可​​以在那里建立网络的硬连线?

这可能(证明)是您在经典领域中可以做的最好的事情,即,无需利用有限的域并执行邪恶的词内魔术。


  1. Donald E. Knuth的分类和搜索计算机编程艺术卷。3(第2版,1998年)
  2. w ^^25=7

我接受这个。在继续之前,我收到了许多需要进行基准测试的新想法。提到Knuth总是对我有用:-)感谢您的努力和时间!
Fredrik Pihl,2015年

@FredrikPihl很酷,请告诉我们最终结果如何!
拉斐尔

我会!立即阅读第5.3.3章。热爱它的开始,提到了刘易斯·卡洛尔(Lewis Carroll)和网球比赛:-)
弗雷德里克·皮尔

2
根据指令集,将2 * max(a,b)= a + b + abs(ab)与选择网络一起使用可能会很有用;它可能比不可预测的条件跳转要便宜得多(即使没有针对abs的内在或条件移动:gcc,至少对于x86而言,它会生成无跳转序列,而该序列似乎并不依赖于x86)。与SIMD或GPU结合使用时,具有无跳序列也很有用。
AProgrammer

1
请注意,选择网络(如分类网络)适合并行操作。特别是在指定的选择网络中,比较1:4和0:3可以并行执行(如果处理器,编译器等有效地支持的话),比较1:3和0:2也可以并行执行。
布鲁斯·莉莉

4

就像在桌子上一样,这是一个直接算法:

// Sort x1, x2
if x1 < x2
  M1 = x2
  m1 = x1
else
  M1 = x1
  m1 = x2
end

// Sort x3, x4
if x3 < x4
  M2 = x4
  m2 = x3
else
  M2 = x3
  m2 = x4
end

// Pick largest two
if M1 > M2
  M3 = M1
  if m1 > M2
    m3 = m1
  else
    m3 = M2
  end
else
  M3 = M2
  if m2 > M1
    m3 = m2
  else
    m3 = M1
  end
end

// Insert x4
if x4 > M3
  m3 = M3
  M3 = x4
else if x4 > m3
  m3 = x4
end

通过巧妙地实现if ... else,您可以摆脱直接翻译会产生的一些无条件跳转。

这很丑,但只需要

  • 五或六次比较(即条件跳转),
  • 9到10个分配(带有11个变量,全部在寄存器中)和
  • 没有额外的内存访问。

w ^25

但是,在带有流水线的机器上,这不能指望很快。考虑到条件跳高的比例,大多数时间可能会花在失速上。

请注意,一个更简单的变体(sort x1x2,然后随后插入其他值)需要进行四到七次比较,而只有五到六次分配。由于我希望在这里跳会带来更高的成本,因此我坚持使用这一跳。


  1. Donald E. Knuth的分类和搜索计算机编程艺术卷。3(第2版,1998年)

我不知道优化的编译器可以做什么。
拉斐尔

我将实现它,并针对当前基于CLZ的解决方案进行基准测试。谢谢你的时间!
Fredrik Pihl

1
@FredrikPihl您的基准测试结果是什么?
拉斐尔

1
基于SWAP的方法胜过CLZ!现在在手机上。现在可以发布更多的数据另一次,移动
弗雷德里克皮赫尔

@FredrikPihl酷!我很高兴良好的旧理论方法可以(仍然)具有实际用途。:)
拉斐尔

4

这对于Souper项目可能是一个很好的应用程序和测试用例。 Souper是一个超级优化器 -一种工具,它以一小段代码作为输入,并尝试对其进行尽可能多的优化(尝试找到等效的代码序列将更快)。

Souper是开源的。您可以尝试在代码段上运行Souper,以查看它是否可以做得更好。

另请参阅John Regehr关于编写快速代码对16个4位值进行排序的竞赛;那里的某些技术可能很有用。


我会对这对OP一直在尝试的程序能做什么感兴趣。
拉斐尔

3

213

T[T[T[441*a+21*b+c]*21+d]*21+e]

214

212

212

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.