不久前我写了一个三元逻辑模拟器,这个问题对我来说是可行的,因为它直接影响我的解释器执行速度;我被要求尽可能快地模拟成吨的三元逻辑门。
在二进制编码的三进制系统中,一个三元组被打包成两位。最高有效位表示负数,最低有效位表示正数。案例“11”不应该发生,但必须妥善处理,并以0威胁。
考虑inline int bct_decoder( unsigned bctData )
函数,它应该将我们格式化的 trit 返回为常规整数 -1、0 或 1;正如我所观察到的,有 4 种方法:我称它们为“cond”、“mod”、“math”和“lut”;让我们调查一下
首先是基于 jz|jnz 和 jl|jb 条件跳转,因此 cond. 它的性能一点也不好,因为依赖于分支预测器。更糟糕的是 - 它会有所不同,因为不知道是否会有一个或两个先验分支。这是一个例子:
inline int bct_decoder_cond( unsigned bctData ) {
unsigned lsB = bctData & 1;
unsigned msB = bctData >> 1;
return
( lsB == msB ) ? 0 : // most possible -> make zero fastest branch
( lsB > msB ) ? 1 : -1;
}
这是最慢的版本,在最坏的情况下可能涉及 2 个分支,这是二进制逻辑失败的地方。在我的 3770k 上,它在随机数据上平均产生大约 200MIPS。(这里和之后 - 每个测试是在随机填充的 2mb 数据集上进行 1000 次尝试的平均值)
下一个依赖于模运算符,它的速度介于第一和第三之间,但绝对更快 - 600 MIPS:
inline int bct_decoder_mod( unsigned bctData ) {
return ( int )( ( bctData + 1 ) % 3 ) - 1;
}
下一个是无分支方法,它只涉及数学,因此是数学;它根本不假设跳转指令:
inline int bct_decoder_math( unsigned bctData ) {
return ( int )( bctData & 1 ) - ( int )( bctData >> 1 );
}
这做了应该做的事情,并且表现得非常好。相比之下,性能估计为 1000 MIPS,比分支版本快 5 倍。由于缺乏原生 2 位有符号整数支持,分支版本可能会变慢。但在我的应用程序中,它本身就是一个很好的版本。
如果这还不够,那么我们可以走得更远,有一些特别的东西。接下来称为查找表方法:
inline int bct_decoder_lut( unsigned bctData ) {
static const int decoderLUT[] = { 0, 1, -1, 0 };
return decoderLUT[ bctData & 0x3 ];
}
在我的例子中,一个 trit 只占用 2 位,所以 lut 表只有 2b*4 = 8 个字节,值得一试。它适合缓存并以 1400-1600 MIPS 的速度快速运行,这就是我的测量精度下降的地方。这就是快速数学方法的 1.5 倍加速。那是因为你只有预先计算好的结果和单AND
条指令。遗憾的是缓存很小并且(如果您的索引长度大于几位)您根本无法使用它。
所以我想我回答了你的问题,关于分支/无分支代码可能是什么样的。答案要好得多,并且有详细的样本、真实世界的应用和真实的性能测量结果。