Как я могу исключить вызов, если во время компиляции известно условие ребра? - PullRequest
8 голосов
/ 05 октября 2011

У меня следующая ситуация: существует огромный набор шаблонов, таких как 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 неизвестно во время компиляции, что мне не нужно.

Возможно ли как-то использовать __ предположить подсказку чтобы указать компилятору, что если он точно знает, что count равен нулю, он должен исключить memmove()?

Ответы [ 4 ]

4 голосов
/ 05 октября 2011

В этом решении используется прием, описанный в Обнаружение констант во время компиляции C ++ - прием использует тот факт, что целое ноль времени компиляции может быть преобразовано в указатель, и его можно использовать вместе с перегрузкой для проверки свойство «время компиляции известно».

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)))
3 голосов
/ 05 октября 2011

Смысл __assume состоит в том, чтобы указывать компилятору пропускать части кода при оптимизации.В предоставленной вами ссылке пример приведен с предложением default конструкции switch - там подсказка говорит компилятору, что предложение никогда не будет достигнуто, хотя теоретически это возможно.Вы говорите оптимизатору, в основном: «Эй, я знаю лучше, выбросьте этот код».

Для default вы не можете не написать его (если вы не охватите весь диапазон в case s, что иногда проблематично), поскольку это может привести к ошибке компиляции.Таким образом, вам нужна подсказка для оптимизации кода , который вы знаете, что он не нужен.

В вашем случае - код может быть достигнут , но не всегда, поэтому__assume намек не сильно тебе поможет.Вы должны проверить, действительно ли count равно 0. Если вы не уверены, что оно никогда не может быть ничем, кроме 0, просто не записывайте его.

1 голос
/ 08 октября 2011

Если возможно переименовать 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 );
}

Я думаю, что то же самое возможно без класса - нужно посмотреть правила преобразования, чтобы подтвердитьit.

Также должна быть возможность перегрузить оригинальный memmove (), например.передавая объект (например, Temp (sizeof (a)) в качестве 3-го аргумента.

Не уверен, какой путь будет более удобным.

1 голос
/ 05 октября 2011

Я думаю, что вы неправильно поняли значение __assume.Он не сообщает компилятору об изменении своего поведения, когда он знает , что это за значения, а сообщает ему, какими будут значения, когда он не может вывести его сам.

В вашемВ случае, если вы сказали __assume, что count > 0 он пропустит тест, как вы уже сказали, что результат всегда будет true, он удалит условие и будет вызывать memmove всегда, это именно то, что вы хотите избежать.

Я не знаю внутреннюю сущность VS, но в GCC есть вероятная / маловероятная внутренняя (__builtin_expect((x),1))это может быть использовано для подсказки компилятора, который является наиболее вероятным результатом теста.это не удалит тест, но создаст код так, чтобы наиболее эффективная ветвь (как в по вашему определению ) была более эффективной (ветвь не будет).

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...