在同一偏移量的两个整数中快速搜索某些半字节(C,微优化)

在同一偏移量的两个整数中快速搜索某些半字节(C,微优化),c,optimization,micro-optimization,C,Optimization,Micro Optimization,我的任务是检查(>万亿次检查),两个int是否包含任何预定义的半字节对(第一对0x2 0x7;第二对0xd 0x8)。例如: bit offset: 12345678 first int: 0x3d542783 first pair of 0x2 second: 0xd second int: 0x486378d9 nibbles: 0x7 pair: 0x8 ^ ^ 所以,在这个例子中,我用所需

我的任务是检查(>万亿次检查),两个int是否包含任何预定义的半字节对(第一对0x2 0x7;第二对0xd 0x8)。例如:

bit offset:   12345678
first int:  0x3d542783     first pair of  0x2    second:   0xd   
second int: 0x486378d9      nibbles:      0x7      pair:   0x8
               ^  ^
所以,在这个例子中,我用所需的对标记了两个偏移(偏移是2和5;但不是7)。任务中不需要实际偏移量和找到的对数

所以,对于给定的两个整数,问题是:它们是否包含位于相同偏移量的任何一对半字节

我检查了我的程序,这部分是最热的地方(
gprof
proven);它被称为非常多次(
gcov
profed)。实际上,它是嵌套循环中的第三个或第四个循环(最嵌套)

我当前的代码很慢(我将其重写为函数,但它是来自内部循环的代码):

是否可以并行重写此函数和宏


我的编译器是gcc452,cpu是32位模式(x86)的Intel Core2 Solo。

最快的解决方案可能是使用某种查找表

你的记忆力有多差?一个16位的表将是64K,允许您一次测试4个字节。因此,其中4个(每一小口1个)将是256K

如果我理解你的问题,我想这会管用的。这是一个8位的示例-您可以将其扩展到16位:

 /* Look for 0x2 in either nibble - hits on 0x02, 0x20, 0x22 */
 char table_0x2[] = {
     0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 0x02 */
     0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
     1, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 0x20, 0x22 */
     0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
     0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
     0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
     0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
     0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
     0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
     0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
     0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
     0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
     0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
     0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
     0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
     0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
 };

 char table_0x7[] = { fill this in };
 char table_0xd[] = { fill this in };
 char table_0x8[] = { fill this in };

 int nibble_check (uint32_t A, uint32_t B)
 {

       int i;

       for (i = 0; i < 4; i++) {
           if ((table_0x2[A & 0xff] && table_0x7[B & 0xff]) ||
               (table_0xd[A & 0xff] && table_0x8[B & 0xff])) {
                  /*
                   * check to see if the A&B hits are in corresponding
                   * nibbles - return 1 or break
                   */
           }

           A = A >> 8;
           B = B >> 8;

       }
       return 0;
   }
/*在半字节中查找0x2-点击0x02、0x20、0x22*/
字符表_0x2[]={
0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,/*0x02*/
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
1,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,/*0x20,0x22*/
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
};
字符表_0x7[]={填写此项};
char表_0xd[]={填写此项};
字符表_0x8[]={填写此项};
整数零位校验(uint32\u t A、uint32\u t B)
{
int i;
对于(i=0;i<4;i++){
if((表0x2[A&0xff]&表0x7[B&0xff])||
(表0xd[A&0xff]&表0x8[B&0xff])){
/*
*检查A&B点击是否在相应位置
*小口-返回1或中断
*/
}
A=A>>8;
B=B>>8;
}
返回0;
}
下面是一个更好的实现:

 /* 16 bit tables - upper 8 bits are A, lower 8 bits are B */
 /* for 0x02, 0x07 */
 char *table_2_7;
 /* for 0x0d, 0x08 */
 char *table_d_8;

 void init(void)
 {
     int i;
     int j;

     /* error checking eliminated for brevity */
     table_2_7 = malloc(64 * 1024);
     table_d_8 = malloc(64 * 1024);

     memset(table_2_7, 0, 64 * 1024);
     memset(table_d_8, 0, 64 * 1024);

     for (i = 0 ; i < 16; i++) {
         for (j = 0 ; j < 16; j++) {
             table_2_7[(i << 12)   | (0x2 << 8)  | (j << 4)   | (0x7 << 0)] = 1;
             table_2_7[(0x2 << 12) | (i << 8)    | (0x7 << 4) | (j << 0)] = 1;

             table_d_8[(i << 12)   | (0xd << 8)  | (j << 4)    | (0x8 << 0)] = 1;
             table_d_8[(0xd << 12) | (i << 8)    | (0x8 << 4) | (j << 0)] = 1;
    }
}


 }

 int nibble_check(uint32_t A, uint32_t B)
 {
     int i;

     for (i = 0; i < 4; i++) {
         if (table_2_7[ ((A & 0xff) << 8) | (B & 0xff) ] ||
             table_d_8[ ((A & 0xff) << 8) | (B & 0xff) ]) {
             return 1;
         }

         A = A >> 8;
         B = B >> 8;

     }
     return 0;
 }
/*16位表-上8位为A,下8位为B*/
/*对于0x02,0x07*/
字符*表2\u 7;
/*对于0x0d,0x08*/
字符*表8;
void init(void)
{
int i;
int j;
/*为简洁起见,消除了错误检查*/
表2_7=malloc(64*1024);
表8=malloc(64*1024);
memset(表2,7,0,64*1024);
memset(表8,0,64*1024);
对于(i=0;i<16;i++){
对于(j=0;j<16;j++){

表2\u 7[(i您是否尝试展开循环

if( ( ((A & 0x0000000F) == 0x0000000D) && ((B & 0x0000000F) == 0x00000008) )
 || ( ((A & 0x000000F0) == 0x000000D0) && ((B & 0x000000F0) == 0x00000080) )
 || ( ((A & 0x00000F00) == 0x00000D00) && ((B & 0x00000F00) == 0x00000800) )
 || ( ((A & 0x0000F000) == 0x0000D000) && ((B & 0x0000F000) == 0x00008000) )
// etc
// Then repeat with 2 & 7
我相信展开循环将导致相同数量的按位and运算和相同数量的比较,但您将节省执行所有正确移位和存储结果的工作量

编辑:(响应条件和非条件跳转中的展开结果)

这将消除任何跳转,以牺牲额外的工作为代价。我已经有一段时间没有做过需要这种优化的事情了,但这应该不会导致任何跳转。(如果没有,请尝试将&&替换为&。&&可能会触发编译器生成短路逻辑,但&可能会使编译器始终计算后半部分,而不会跳转。)


您可能会更早地淘汰一些不匹配的候选人:

int nibble_check (uint32_t A, uint32_t B) 
{
    if ( !(A & B & 0x22222222) && !(A & B & 0x88888888))
       return 0;
    //rest of checking here...
}
静态内联整数半字节检查(uint32\u t A、uint32\u t B)
__属性_uuu((始终_内联))
{
//将x移位n个字节

#定义s(x,n)((x)基于表格的方法可以是:

static inline int has_zeros (uint32_t X)
{
    int H = (X >> 16);
    int L = X & 0xFFFF;
    return (ztmap[H>>3]&(1<<(H&7))) ||
           (ztmap[L>>3]&(1<<(L&7)));
}

static inline int nibble_check (uint32_t A, uint32_t B)
 __attribute__((always_inline))
{
  return has_zeros((A ^ 0xDDDDDDDDU)|(B ^ 0x88888888U)) ||
         has_zeros((A ^ 0x22222222U)|(B ^ 0x77777777U));
}
静态内联int有0(uint32\utx)
{
inth=(X>>16);
int L=X&0xFFFF;

return(ztmap[H>>3]&(13]&(1测试单词中的零字节有一些技巧(参见示例);快速方法是使用此表达式:

(x - 0x01010101) & ~x & 0x80808080
如果32位字中的4个字节中的任何一个为0,则其计算结果为非零值,否则为0

此方法适用于以下情况:

static inline int nibble_check(uint32_t A, uint32_t B)
{
  uint32_t tmp1, tmp2;

  tmp1 = (A ^ 0x22222222) | (B ^ 0x77777777);
  tmp2 = (A ^ 0xdddddddd) | (B ^ 0x88888888);

  return !!(((tmp1 - 0x11111111) & ~tmp1 & 0x88888888) |
            ((tmp2 - 0x11111111) & ~tmp2 & 0x88888888));
}

另外,这段代码是一些国际象棋解算器的一部分。这项检查是为了“两个国王是否互相攻击”。关于并行性。在其中一个循环的级别上进行并行处理是否有问题?如果没有看到代码,这似乎更为明显。@Keith,我想得到“nibble”正如Matthew所建议的,这里的级别并行。他的版本比原始版本快3.5倍,在单核处理器上,线程并行的程序不可能快3.5倍。我有一个内存,可以使用它。但是当我有一对整数时,我如何查找呢?而且,我认为,可以用布尔和bitw重写这个搜索ise logic进行并行搜索。JayM。谢谢!!第二个代码的速度与手动展开“| |”相同操作,但答案不正确。表初始化不正确。现在是正确的,但Matthew的实现在我的系统上仍然快了30%左右。是的,最好的文档配置是代码,但你能评论一下吗?我给出了相关的评论,但我可以添加一些。@osgx如果这还不够,我可以解释更多。我还没有测试当然可以。你应该尝试各种解决方案
static inline int nibble_check (uint32_t A, uint32_t B)
 __attribute__((always_inline))
{
    // shift x by n nibbles
    #define s(x, n) ((x) << 4 * (n))
    // mask the nth nibble of x
    #define m(x, n) ((x) & s(0xf, n))
    // D^8 and 2^7 both == 5, so check for that first, for speed
    // this is equivalent to
    // (A_nibble == 0XD && B_nibble == 0x8) || (A_nibble == 0x2 && B_nibble == 0x7)
    #define t(n) (m(AB,n) == s(5,n) && (m(B,n) == s(7,n) || m(B,n) == s(8,n))

    uint32_t AB x = A ^ B;

    return t(0) || t(1) || t(2) || t(3) || t(4) || t(5) || t(6) || t(7);
    #undef t
    #undef m
    #undef s
}
static inline int has_zeros (uint32_t X)
{
    int H = (X >> 16);
    int L = X & 0xFFFF;
    return (ztmap[H>>3]&(1<<(H&7))) ||
           (ztmap[L>>3]&(1<<(L&7)));
}

static inline int nibble_check (uint32_t A, uint32_t B)
 __attribute__((always_inline))
{
  return has_zeros((A ^ 0xDDDDDDDDU)|(B ^ 0x88888888U)) ||
         has_zeros((A ^ 0x22222222U)|(B ^ 0x77777777U));
}
(x - 0x01010101) & ~x & 0x80808080
static inline int nibble_check(uint32_t A, uint32_t B)
{
  uint32_t tmp1, tmp2;

  tmp1 = (A ^ 0x22222222) | (B ^ 0x77777777);
  tmp2 = (A ^ 0xdddddddd) | (B ^ 0x88888888);

  return !!(((tmp1 - 0x11111111) & ~tmp1 & 0x88888888) |
            ((tmp2 - 0x11111111) & ~tmp2 & 0x88888888));
}