如果您正在针对分支价格昂贵的架构(比如PS3的单元处理器)进行优化,那么能够确定是否可以在不使用分支或至少使用更少的分支的情况下表达给定算法非常重要分支机构。我在未经优化的代码中看到的一种模式是用于将索引调整到某个数组的一堆if(如果数组的大小是奇数,则将索引加1,在某些其他情况下,乘以2等等)。 )。因此,如果有一种方法,给定两个数字列表,就可以确定是否可以编写一个无分支函数将一个列表转换为另一个列表。
例如,我最近想知道是否有可能编写一个无分支函数,将0, 1, 2, 3, 4, 5, 6, 7, 8, 9
转换为:0, 2, 4, 6, 8, 9, 7, 5, 3, 1
(升序甚至后跟奇数降序)。从技术上讲,我可以编写一个大的开关/案例函数,但显然我对一个将遵循任意大小模式的函数感兴趣。编写一个函数来进行这种转换对于分支是很简单的,但是如果有一种非分支的方法可以做到这一点并不是很明显。
那么对这类问题或任何类型的快速试金石有一般的解决方法吗?或者你必须根据具体情况提出证据吗?我可以更加努力地解决这些问题,但如果他们真的不可能那就毫无意义。我似乎记得在某些时候读过,有一个正式的数学单词用于只使用算术而没有分支的函数,但我不记得了。
答案 0 :(得分:4)
变换:0,1,2,3,4,5,6,7,8,9到:0,2,4,6,8,9,7,5,3,1(甚至后面跟着下降奇数)。
简单:给定X的N值从0到N-1的序列,我们看到序列的前半部分是2X。序列的后半部分是(2N-1)-2X。序列在X =(N + 1)/ 2处用“整数”数学分裂。在上面的例子中,N == 10。
假设32位有符号整数,右移算术:
int Transform(int x)
{
const int seq1=x+x;
const int mask=(x-((N+1)>>1))>>31;
const int seq2=(N+N-1)-seq1;
return (mask&seq1)|((~mask)&seq2);
}
请注意,此处使用的掩码模式很快,因为PowerPC具有ANDC(和补码),使(~mask)
成为自由操作。
答案 1 :(得分:1)
如果您正在针对PS3进行优化,Power PC Compiler Writers Guide在第3.1.5节中有关于无分支代码的技术,并且在附录D中为无分支代码提供了GNU Superoptimizer序列。
您可能也对Mike Acton's Cell Performance博客感兴趣。
答案 2 :(得分:1)
如果根据输入索引绘制所需的索引,则会得到三角形函数。事实证明,对于n
= 10的情况,它是
9.5 - abs(2 (x - 4.75))
因此,对于一般n
,它将是
n-0.5 - abs(2*(x - n/2-0.25))
或者以整数形式,
(2*n-1 - abs(4*x - 2*n + 1)) / 2
这是完全无分支的,因为您的输出索引是使用单个mathematecal函数生成的。我认为一般的方法是绘制出所需的索引并寻找一种模式以及用数学函数表示它的方法。
显然,如果您想要的最终索引形成一条直线,那么转换很简单。如果映射中存在扭结,则需要使用绝对值函数来引入折弯,并且可以调整缩放以更改折弯的角度。您可以通过偏置来扭转扭结(例如abs(x)+x/2
)。如果在最终索引函数中需要跳转不连续,则使用符号函数(希望内置或使用abs(x)/ x)。您需要具有创造性,如何在这里使用常用功能图表。
<强>附录强>
如果索引函数是分段线性的,则有一种简单的算法。假设所需的索引函数表示为段列表
{(sx1,sy1)-(ex1,ey1), (sx2,sy2)-(ex2,ey2), ... , (sxN,syN)-(exN,eyN)}
segment 1 segment 2 segment N
其中exK&gt;所有K和sxK的sxK&gt;所有K的sx(K-1)(从左到右)。
k = 1
f(x) = Make affine model of segment k
g(x) = f(x)
Do:
k = k + 1
h(x) = Makeaffine model of segment k
If g(x) and h(x) intersect between ex(k-1) and ex(k)
f(x) = f(x) + [slope difference of g(x) and h(x)] * ramp(x)
Else
f(x) = f(x) + (h(ex(k-1)) - f(ex(k-1))) * step(x)
f(x) = f(x) + [slope difference of g(x) and h(x)] * ramp(x)
其中ramp(x) = (abs(x)+x)/2
和step(x) = (sign(x)+1)/2
。 f(x)表示所需的函数,g(x)
是最后一个段的仿射模型,h(x)
是当前段的仿射模型。仿射模型只是斜率偏移形式的一条线:a*x+b
,斜率差异是斜率的差异。这个算法简单地从左到右进行,随着它的进行添加适当的函数。它添加的函数对于x <= 0
始终为零,因此它们不会影响到目前为止已构建的f(x)
。
当然,上面可能存在一些错误/错别字。我真的得参加一个会议,所以我不能再写了。
答案 3 :(得分:1)
例如,您总是可以使用拉格朗日插值来编写多项式公式。不漂亮(或特别快)但它没有任何分支。
答案 4 :(得分:0)
如果速度真的很重要,你难道不能写出一定长度的列表说明吗? (当然,可以预先生成此代码)。
这样:
void algorithm1_Length6(int *srcList, int *destList)
{
*destList++ = *srcList;
*destList++ = srcList[2];
*destList++ = srcList[4];
*destList++ = srcList[5];
*destList++ = srcList[3];
*destList++ = srcList[1];
}
以及所有其他变体达到一定的长度。
答案 5 :(得分:0)
从技术上讲,任何一系列操作都可以在没有使用布尔操作的状态机“分支”的情况下执行。分支的概念是因为大多数程序是由程序计数器执行的一系列指令,可以采用这种方式。
即使您正在谈论无状态的纯函数方法,对于一组有限的离散值,您总是可以(以大量内存为代价)使用查找表。
答案 6 :(得分:-2)
对于给定的数组,您可以使用以下方法:
void tranform(int[] src, int[] dest) {
//0, 2, 4, 6, 8, 9, 7, 5, 3, 1
dest[0] = src[0];
dest[1] = src[2];
dest[2] = src[4];
dest[3] = src[6];
dest[4] = src[8];
dest[5] = src[9];
dest[6] = src[7];
dest[7] = src[5];
dest[8] = src[3];
dest[9] = src[1];
}
但是一般来说对于大数组来说很难编写这样的方法,因此如果你编写这样的生成方法会很有用:
static void createFunction(int[] src, int[] dest) {
System.out.println("void tranform(int[] src, int[] dest) {");
for (int i = 0; i < dest.length; i++) {
for (int j = 0; j < src.length; j++) {
if (dest[i] == src[j]) {
System.out.println("dest[" + i + "]=src[" + j + "];");
break;
}
}
}
System.out.println("}");
}
使用您的数组调用它:
createFunction(new int[]{0, 1, 2, 3, 4, 5, 6, 7, 8, 9}, new int[]{0, 2, 4, 6, 8, 9, 7, 5, 3, 1});
并将此方法的输出粘贴到您的程序中。