进行预先计算的真值检查的不同方法

时间:2014-09-22 14:43:14

标签: c arrays

我正在开发一个为一个函数生成C代码的程序。这个生成的C函数驻留在另一个目标程序的中心循环中;此功能对性能敏感。生成的函数用于根据bool值调用另一个函数 - 使用传递给生成函数的2个int来获取此布尔值:状态编号和模式编号。生成的函数如下所示:

void dispatch(System* system, int state, int mode) {
    // Some other code here...
    if (truthTable[state][mode]) {
        doExpensiveCall(system, state, mode);
    }
}

一些事实:

  • 'state'和'mode'值的范围从0开始,以某个数字结束< 10,000。它们的可能值是连续的,两者之间没有间隙。因此,例如,如果'state'的结束值是1000,那么我们知道有1001和状态(包括状态0)。
  • 代码生成器知道状态和模式,并且它提前知道状态+模式的哪个组合将产生值true。从理论上讲,状态+模式的任何组合都可以产生真值,从而调用doExpensiveCall,但实际上它将主要是少数状态+模式组合,它们将产生一个真值。同样,这些信息在代码生成期间是已知的。
  • 由于此函数将被调用很多,我想优化对真值的检查。在一般情况下,我预计测试会在很长的时间内产生错误。平均而言,我预计不到1%的调用会产生真值。但是,理论上,它可能会像100%的时间一样高(这一点取决于最终用户)。

我正在探索不同的方法来计算状态+模式是否会调用doExpensiveCall()。最后,我将不得不选择一些东西,所以我现在正在探索我的选择。到目前为止,这些是我能想到的不同方式:

1)创建一个预先计算的双维数组,其中包含布尔值。这就是我在上面的例子中使用的内容。这产生了我能想到的最快的检查。问题是如果状态和模式具有较大的范围(比如10,000x1000),则生成的表开始非常大(在10,000x1000的情况下,仅为该表的10MB)。例如:

// STATE_COUNT=4, MODE_COUNT=3
static const char truthTable[STATE_COUNT][MODE_COUNT] = {
  {0,1,0},
  {0,0,0},
  {1,1,0},
  {0,0,1}
}

2)创建一个像#1这样的表,但是压缩:而不是每个数组条目都是一个布尔值,它将是一个char位域。然后,在检查期间,我将使用state + mode进行一些计算,以决定如何索引数组。这通过MODE_MODE / 8减小了预计算表的大小。缺点是减少不是那么多,现在现在需要计算位域表中布尔值的索引,而不是像#1中那样只是一个简单的数组访问。

3)由于预期产生值为true的状态+模式组合的数量很小,因此也可以使用switch语句(使用#1中的truthTable作为参考):

switch(state){
case 0: // row
 switch(mode){ // col
  case 1: doExpensiveCall(system, state, mode);
  break;
 }
break;
case 2:
 switch(mode){
    case 0:
    case 1: doExpensiveCall(system, state, mode);
    break;
 }
break;
case 3:
 switch(mode){
    case 2: doExpensiveCall(system, state, mode);
    break;
 }
break;
}

问题:

根据上述事实,有哪些其他方法可以用来计算调用doExpensiveCall()所需的布尔值?

由于

编辑: 我虽然关于Jens示例代码,但发生了以下情况。为了只有一个switch语句,我可以在生成的代码中进行这个计算:

// #if STATE_COUNT > MODE_COUNT
int i = s * STATE_COUNT + m;
// #else 
int i = m * MODE_COUNT + s;
// #endif

switch(i) {
case 1: // use computed values here, too.
case 8:
case 9:
case 14:
     doExpensiveCall(system, s, m);

}

2 个答案:

答案 0 :(得分:0)

我尝试使用(3)的修改版本,其中您实际上只有一个呼叫,并且所有switch/case内容都会导致该呼叫。通过这种方式,您可以确保编译器将选择他所拥有的任何启发式来优化它。

中的某些内容
switch(state) {
 default: return;
 case 0: // row
   switch(mode){ // col
   default: return;
   case 1: break;
   }
   break;
 case 2:
   switch(mode){
   default: return;
   case 0: break;
   case 1: break;
   }
   break;
 case 3:
   switch(mode){
   default: return;
   case 2: break;
   }
   break;
 }

doExpensiveCall(system, state, mode);

也就是说,switch内只有“控制”。编译器应该能够很好地对它进行排序。

这些启发式方法在架构和编译选项之间可能会有所不同(例如-O3-Os),但这就是编译器的用途,根据平台特定的知识做出选择。

为了您提及时间效率,如果您的功能电话真的很贵,那么这部分只会被噪音所掩盖,不用担心。 (或以其他方式对您的代码进行基准测试以确定。)

答案 1 :(得分:0)

如果代码生成器知道正在使用的表的百分比,它可以在构建时选择算法。

因此,如果大约50%真/假使用10 MB表。

否则使用哈希表或基数树。

哈希表会选择哈希函数和多个存储桶。您将计算哈希值,找到存储桶并在链中搜索真(或假)值。

基数树将选择一个基数(如10)并且你有10个条目指向NULL(在那里没有真值),一个指针指向另外10个条目,直到你最终达到一个值。