如果在编译时知道边缘条件,我怎么能忽略一个调用呢?

时间:2011-10-05 07:32:35

标签: c++ visual-c++ optimization compiler-optimization

我有以下情况:有一大堆模板,例如std::vector,会调用memmove()来移动数组的部分。有时他们会想要“移动”零长度的部分 - 例如,如果删除了数组尾部(如std::vector::erase()),他们将希望移动数组的其余部分碰巧长度为零,并且在编译时将知道零(我看到了反汇编 - 编译器知道)但编译器仍会发出memmove()调用。

所以基本上我可以有一个包装器:

inline void callMemmove( void* dest, const void* source, size_t count )
{
   if( count > 0 ) {
       memmove( dest, source, count );
   }
}

但是如果在我不想要的编译时间内count未知,则会引入额外的运行时检查。

是否有可能使用__assume hint向编译器表明如果它确定count为零,那么它应该消除memmove()

4 个答案:

答案 0 :(得分:4)

此解决方案使用C++ compile-time constant detection中描述的技巧 - 该技巧使用事实编译时整数零可以转换为指针,这可以与重载一起使用来检查“编译时间已知”属性

struct chkconst {
  struct Small {char a;};
  struct Big: Small {char b;};
  struct Temp { Temp( int x ) {} };
  static Small chk2( void* ) { return Small(); }
  static Big chk2( Temp  ) { return Big(); }
};

#define is_const_0(X) (sizeof(chkconst::chk2(X))<sizeof(chkconst::Big))
#define is_const(X) is_const_0( int(X)-int(X) )

#define memmove_smart(dst,src,n) do { \
    if (is_const(n)) {if (n>0) memmove(dst,src,n);} \
    else memmove(dst,src,n); \
  } while (false)

或者,在您的情况下,因为您只想检查零,可以直接使用is_const_0来实现最大的简单性和可移植性:

#define memmove_smart(dst,src,n) if (is_const_0(n)) {} else memmove(dst,src,n)

注意:此处的代码使用的is_const版本比链接问题更简单。这是因为在这种情况下,Visual Studio比GCC更符合标准。如果定位gcc,您可以使用以下is_const变体(适用于处理所有可能的整数值,包括负数和INT_MAX):

#define is_const_0(X) (sizeof(chkconst::chk2(X))<sizeof(chkconst::Big))
#define is_const_pos(X) is_const_0( int(X)^(int(X)&INT_MAX) )
#define is_const(X) (is_const_pos(X)|is_const_pos(-int(X))|is_const_pos(-(int(X)+1)))

答案 1 :(得分:3)

__assume的要点是告诉编译器在优化时跳过部分代码。在您提供的链接中,示例是使用default构造的switch子句给出的 - 提示告诉编译器即使理论上它也可以永远不会到达该子句。你告诉优化器,基本上,“嘿,我知道的更好,抛弃这个代码”。

对于default,您不能不写入(除非您覆盖case中的整个范围,这有时会出现问题)因为它会导致编译错误。因此,您需要提示来优化代码知道不需要的代码。

在您的情况下 - 代码可以,但并非总是如此,因此__assume提示对您没有多大帮助。你必须检查count是否真的为0.除非你确定它永远不会是0,否则就不要把它写进来。

答案 2 :(得分:1)

我认为你误解了__assume的含义。当它知道值是什么时,它不会告诉编译器改变它的行为,而是告诉它当它无法自己推断它时它将是什么值。

在您的情况下,如果您告诉__assume count > 0它将跳过测试,因为您已经告诉它结果将始终为true,它将删除条件并将memmove 始终,这正是您要避免的。

我不知道VS的内在函数,但在GCC中有一个可能/不太可能的内在函数(__builtin_expect((x),1))可用于提示编译器哪个是测试最可能的结果。这将不会删除测试,但会布局代码,以便大多数可能的(如在中由您的定义)分支更有效(不会分支)。

答案 3 :(得分:1)

如果可以重命名memmove,我想是这样的 会做 - http://codepad.org/s974Fp9k

struct Temp {
  int x;
  Temp( int y ) { x=y; }
  operator int() { return x; };
};

void memmove1( void* dest, const void* source, void* count ) {
  printf( "void\n" );
}

void memmove1( void* dest, const void* source, Temp count ) {
  memmove( dest, source, count );
  printf( "temp\n" );
}

int main( void ) {
  int a,b;
  memmove1( &a,&b, sizeof(a) );
  memmove1( &a,&b, sizeof(a)-4 );
}

我认为没有课程可能也是如此 - 必须查看转换规则 确认一下。

也应该可以重载原始的memmove(),例如。通过一个 对象(如Temp(sizeof(a))作为第三个参数。

不确定哪种方式会更方便。