В чем разница между двумя разными функциями обмена? - PullRequest
2 голосов
/ 19 октября 2019

Хотелось бы узнать разницу между 2 кодами в производительности. Каковы преимущества и недостатки?

Код 1:

temp  = a;
a  = b;
b  = temp;

Код 2:

a = a + b;
b = a - b;
a = a - b;

Ответы [ 2 ]

8 голосов
/ 19 октября 2019

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

Преимущества второго метода заключаются в том, что он избегает использования временной переменной и является очень вкуснымнеясный трюк, который нравится тем, кто непрерывно собирает неясные трюки и задает ошибочные вопросы на собеседовании с «непонятными словами», включающие неясные трюки, и (насколько я знаю), которые делают свои собственные программы менее обслуживаемыми, менее переносимыми и менее надежными, загромождая ихнеясные трюки.

Недостатки первого метода: Нет .
(Теоретически можно сказать, что есть недостаток в том, что он использует временную переменную, но на самом деле это не так. недостаток вообще, потому что временные переменные свободны. Я не думаю, что на планете есть кто-то, кто все еще кодирует процессор, настолько ограниченный в памяти, и регистрирует, что «сохранение» временной переменной таким способом - это то, что действительно беспокоито.)

Недостатки второго метода заключаются в том, что его труднее писать, труднее понять читателю и, вероятно, он менее эффективен (возможно, значительно). Он «работает» только на арифметических типах, а не на структурах или других типах. Он не будет работать (он будет незаметно повреждать данные), если это произойдет, если вы попытаетесь обменяться данными с самим собой. (Подробнее об этой возможности позже.) И если они не все достаточно плохие, то, скорее всего, это будет принципиально глючить даже при «обычных» обстоятельствах, так как он может переполниться, а с типами с плавающей точкой он может изменить одно или оба значениянезначительно из-за ошибки округления, а с типами указателей не определено, если поменяемые указатели не указывают в пределах одного и того же объекта.

Вы спрашивали конкретно о производительности, поэтому скажем еще несколько слов об этом. (Отказ от ответственности: я не эксперт в области микрооптимизации; я склонен думать об эффективности на уровне инструкций в довольно абстрактных, не очень понятных терминах.)

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


А теперь несколько отступлений. Вторая техника, представленная здесь, является менее вкусным вариантом традиционного, восхитительно неясного трюка для замены двух переменных без использования временного. Традиционный, очень непонятный трюк для замены двух переменных без использования временного:

a ^= b;
b ^= a;
a ^= b;

Когда-то в некоторых кругах было модно представлять эти методы еще более восхитительно непонятным образом:

a ^= b ^= a ^= b;       /* WRONG */
a += b -= a -= b;       /* WRONG */

Но у этих представлений (хотя, да, они абсолютно изящно восхитительно неясны, если вам нравятся такие вещи) есть дополнительный недостаток сбоя, что они представляют неопределенное поведение ,поскольку они пытаются модифицировать a несколько раз в одном и том же выражении без промежуточной точки последовательности. (См. Также канонический вопрос SO на эту тему .)


Справедливости ради, я должен упомянуть, что есть одно фактическое обстоятельство, при котором использование первого метода временной переменной может быть существенным недостатком, и поэтому отсутствие второго метода у одного может быть фактическим преимуществом. Это одно обстоятельство, если вы пытаетесь написать общий макрос 'swap', в соответствии с

#define Swap(a, b) (a = a + b, b = a - b, a = a - b)

Идея состоит в том, что вы можете использовать этот макрос где угодно и для переменных любого типа, и (поскольку это макрос и, следовательно, магия), вам даже не нужно использовать & в аргументах, с которыми вы его вызываете, как если бы это была функция. Но в традиционном C, по крайней мере, если вы хотели написать макрос Swap, подобный этому, это было практически невозможно сделать, используя технику 1, потому что не было способа объявить необходимую временную переменную.

Вы не спрашивали об этой подзадаче, но с тех пор, как я поднял ее, я должен сказать, что решение (хотя это вечно расстраивает любителей восхитительной безвестности) состоит в том, чтобы просто не пытаться написать "универсальный макрос для замены двух значений на первое место . Вы не можете сделать это в C. (На самом деле, вы могли бы сделать это в C ++, с новым определением auto, и в наши дни я думаю, что C также имеет какой-то новый способ написания универсальных макросов.)

И, на самом деле, при попытке написать макрос «swap» таким образом возникает дополнительная проблема, которая заключается в том, что он будет не работать - он установит одну или обе переменные в0 вместо обмена значениями - если вызывающий когда-либо пытается поменять значение с собой. Вы можете сказать, что это не проблема, поскольку, возможно, никто никогда не напишет Swap(x, x), но в менее чем оптимальной подпрограмме сортировки они могут очень легко написать Swap(a[i], a[j]), где иногда i оказывается равным j или Swap(*p, *q), где иногда указатель p оказался равным q.

См. также список часто задаваемых вопросов C , вопросы 3.3b , 10,3 и 20,15c .

3 голосов
/ 19 октября 2019

Всегда используйте первый. Второй может вводить тонкие ошибки. Если переменные имеют тип int и a+b больше INT_MAX, то добавление приведет к неопределенному поведению.

Когда дело доходит до производительности, разница едва ли измерима.

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