Является x + = быстрее, чем x = x + a?

Я читал Stroustrup "Язык программирования С++", где он говорит, что из двух способов добавить что-то к переменной

x = x + a;

и

x += a;

Он предпочитает +=, потому что он, скорее всего, лучше реализован. Я думаю, что он означает, что он работает быстрее.
Но действительно ли это? Если это зависит от компилятора и других вещей, как я могу проверить?

Ответ 1

Любой компилятор, заслуживающий своей соли, генерирует точно такую ​​же последовательность машинного языка для обеих конструкций для любого встроенного типа (int, float и т.д.), если оператор действительно так же прост, как x = x + a; и оптимизация включена. (Примечательно, что GCC -O0, который является режимом по умолчанию, выполняет анти-оптимизации, такие как вставка полностью ненужных хранилищ в память, чтобы гарантировать, что отладчики всегда могут найти значения переменных.)

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

*f() += a;

вызывает f только один раз, тогда как

*f() = *f() + a;

вызывает его дважды. Если f имеет побочные эффекты, один из двух будет неправильным (возможно, последним). Даже если f не имеет побочных эффектов, компилятор не сможет устранить второй вызов, поэтому последний может быть медленнее.

И поскольку мы говорим о С++ здесь, ситуация совершенно различна для типов классов, которые перегружают operator+ и operator+=. Если x - такой тип, то - перед оптимизацией - x += a переводится на

x.operator+=(a);

тогда как x = x + a переводится на

auto TEMP(x.operator+(a));
x.operator=(TEMP);

Теперь, если класс правильно написан, а оптимизатор компилятора достаточно хорош, оба завершат создание одного и того же машинного языка, но это не так, как для встроенных типов. Вероятно, это то, о чем думает Страуструп, когда он поощряет использование +=.

Ответ 2

Вы можете проверить, посмотрев на разборку, которая будет одинаковой.

Для базовых типов обе одинаково быстрые.

Это вывод, сгенерированный с помощью отладочной сборки (т.е. без оптимизации):

    a += x;
010813BC  mov         eax,dword ptr [a]  
010813BF  add         eax,dword ptr [x]  
010813C2  mov         dword ptr [a],eax  
    a = a + x;
010813C5  mov         eax,dword ptr [a]  
010813C8  add         eax,dword ptr [x]  
010813CB  mov         dword ptr [a],eax  

Для пользовательских типов, где вы можете перегрузить operator + и operator +=, это зависит от их соответствующих реализаций.

Ответ 3

Да! Это быстрее писать, быстрее читать и быстрее выяснять, для последнего в случае, если x может иметь побочные эффекты. Так что это быстрее для людей. Человеческое время вообще стоит гораздо больше, чем компьютерное время, так что это должно быть то, о чем вы просили. Правильно?

Ответ 4

Разница между x = x + a и x += a - это объем работы, которую должен пройти компьютер - некоторые компиляторы могут (и обычно) оптимизировать его, но обычно, если мы игнорируем оптимизацию на какое-то время, что происходит заключается в том, что в предыдущем фрагменте кода машина должна дважды искать значение для x, тогда как в последнем случае этот поиск должен выполняться только один раз.

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

PS: первый ответ на переполнение стека!

Ответ 5

Это действительно зависит от типа x и a и реализации+. Для

   T x, a;
   ....
   x = x + a;

компилятор должен создать временный T, чтобы содержать значение x + a, пока он его оценивает, и затем он может назначить x. (В течение этой операции он не может использовать x или как рабочее пространство).

При x + = a ему не требуется временное.

Для тривиальных типов нет разницы.

Ответ 6

Как вы отметили этот С++, нет никакого способа узнать из двух утверждений, которые вы опубликовали. Вам нужно знать, что такое "x" (это немного похоже на ответ "42" ). Если x является POD, то это не имеет особого значения. Однако если x является классом, могут быть перегрузки для методов operator + и operator +=, которые могут иметь разные типы поведения, которые приводят к очень различным временам выполнения.

Ответ 7

Вы задаете неправильный вопрос.

Это вряд ли повлияет на производительность приложения или функции. Даже если бы это было так, способ узнать это профиль кода и знать, как он влияет на вас наверняка. Вместо того, чтобы беспокоиться об этом уровне, о котором идет быстрее, гораздо важнее думать с точки зрения ясности, корректности и удобочитаемости.

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

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

Ответ 8

Если вы говорите +=, вы делаете жизнь намного проще для компилятора. Чтобы компилятор узнал, что x = x+a совпадает с x += a, компилятор должен

  • проанализируйте левую сторону (x), чтобы убедиться, что она не имеет побочных эффектов и всегда относится к одному и тому же значению l. Например, это может быть z[i], и он должен убедиться, что оба z и i не изменяются.

  • проанализируйте правую сторону (x+a) и убедитесь, что это суммирование, и что левая сторона происходит один раз и только один раз с правой стороны, даже если она может быть преобразована, как в z[i] = a + *(z+2*0+i).

Если вы хотите добавить a в x, автор компилятора оценит это, когда вы просто скажете, что вы имеете в виду. Таким образом, вы не выполняете часть компилятора, что его писатель надеется, что у него или ее есть все ошибки, и это на самом деле не облегчает вам жизнь, если вы честно не можете получить свою голову режима Fortran.

Ответ 9

Для конкретного примера представьте простой сложный тип номера:

struct complex {
    double x, y;
    complex(double _x, double _y) : x(_x), y(_y) { }
    complex& operator +=(const complex& b) {
        x += b.x;
        y += b.y;
        return *this;
    }
    complex operator +(const complex& b) {
        complex result(x+b.x, y+b.y);
        return result;
    }
    /* trivial assignment operator */
}

Для случая a = a + b он должен сделать дополнительную временную переменную, а затем скопировать ее.

Ответ 10

Я думаю, что это должно зависеть от машины и ее архитектуры. Если его архитектура допускает косвенную адресацию памяти, компилятор MIGHT просто использует этот код (для оптимизации):

mov $[y],$ACC

iadd $ACC, $[i] ; i += y. WHICH MIGHT ALSO STORE IT INTO "i"

В то время как i = i + y может быть переведен на (без оптимизации):

mov $[i],$ACC

mov $[y],$B 

iadd $ACC,$B

mov $B,[i]


Тем не менее, следует учитывать и другие осложнения, например, если i является функцией, возвращающей указатель и т.д. Большинство компиляторов уровня производства, включая GCC, создают одинаковый код для обоих операторов (если они целые).

Ответ 11

Нет, оба способа получают то же самое.