В утверждении C if-else должно ли условие, которое с большей вероятностью будет истинным, быть первым?


Мне случилось написать оператор if-else, условие будет ложным в большинстве случаев(проверьте, назначен статический указатель или нет). Какой из них лучше оптимизировать компилятору? Или они просто равны?. Функция будет вызываться так много раз, поэтому очень важно оптимизировать ее производительность.

void foo() {
  static int * p = NULL;
  if (p == NULL) {
     p = (int *) malloc( SIZE * sizeof(int)); 
  }
  //do something here
} 

void foo() {
  static int * p = NULL;
  if (p != NULL) {
    //do something here 
  } else {
    p = (int *) malloc( SIZE * sizeof(int));  
    //do something
  }
}
6 6

6 ответов:

Некоторые компиляторы могут позволить разработчику указать, какое условие является более вероятным или маловероятным. Это широко используется в ядре Linux.

В gcc есть вероятные(x) или маловероятные (x) макросы. Пример:

if (unlikely(p == NULL)) {
    p = malloc(10);
}

Если функция вызывается достаточно часто, чтобы не быть вытесненной из предсказателя ветвей, то предсказатель позаботится обо всем за вас, так как он очень быстро узнает, какая ветвь является "вероятной".

Некоторые компиляторы позволяют украсить условные обозначения подсказками для предсказания. Проверьте документацию вашего поставщика и добавьте подсказку, если можете.

Определенные платформы документируют поведение предиктора по умолчанию (например, см. Руководство по оптимизации Intel для x86), но лучше оставить его чтобы ваш компилятор реализовал это с помощью вышеупомянутых подсказок. На самом деле, у вас действительно нет выбора, поскольку вы все равно не контролируете генерацию кода компиляторов, поэтому единственной последней мерой, оставшейся вам, было бы написать код в машинном коде самостоятельно и реализовать рекомендации платформы по прогнозированию по умолчанию.

В конце концов, это зависит от компилятора и оптимизации, которую он выполняет, которая изменяется от версии компилятора, процессора, семейства компиляторов...

Мое эмпирическое правило по такого рода предметам состоит в том, чтобы проверить вашу фактическую платформу, сделав некоторое обширное профилирование. Я обычно также следую правилу "сначала код правильно, а потом оптимизировать только в случае необходимости". Попытка оптимизировать заранее часто приводит к менее удобочитаемому коду и дизайну (а иногда даже не к лучшей производительности).

Условия проверяются в порядке слева направо, это в стандарте C, что позволяет использовать такие условные выражения, как

if( ptr != NULL && ptr->member == value )
Это также означает, что имеет смысл сначала поставить условие, которое с наибольшей вероятностью окажется ложным.

Http://msdn.microsoft.com/en-us/library/2bxt6kc4.aspx

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

Если вы ищете производительность, я бы вообще не использовал malloc. Получить огромный кусок оперативной памяти и использовать его непосредственно без выделения обертки освобождения. Также рассмотрите возможность использования макросов код будет больше, но производительность также увеличится.

#define ASSURE_PTR(a)      if (!a){ \
        a = (int *) malloc (SIZE * sizeof(int)); \
    }