На самом ли деле быстрее использовать say (i ‹< 3) + (i ‹< 1) для умножения на 10, чем использовать i * 10 напрямую?
Он может быть, а может и не быть на вашем компьютере - если вам интересно, измерьте его в реальном мире.
Пример использования - от 486 до Core i7
Сравнительный анализ очень сложно провести осмысленно, но мы можем взглянуть на несколько фактов. Из http://www.penguin.cz/~literakl/intel/s.html#SAL и http://www.penguin.cz/~literakl/intel/i.html#IMUL мы получаем представление о тактах x86, необходимых для арифметического сдвига и умножения. Скажем, мы придерживаемся «486» (самый новый из перечисленных), 32-битных регистров и немедленных, IMUL занимает 13-42 цикла, а IDIV 44. Каждая SAL занимает 2, добавляя 1, так что даже с некоторыми из них, сдвигающимися вместе, внешне выглядит как победитель.
В наши дни с Core i7:
(из http://software.intel.com/en-us/forums/showthread.php?t=61481)
Задержка составляет 1 цикл для целочисленного сложения и 3 цикла для целочисленного умножения. Вы можете найти информацию о задержках и пропускной способности в Приложении C «Справочного руководства по оптимизации архитектур Intel® 64 и IA-32», которое находится по адресу http://www.intel.com/products/processor/manuals./.
(из рекламного объявления Intel)
Используя SSE, Core i7 может одновременно выдавать инструкции сложения и умножения, в результате чего максимальная скорость составляет 8 операций с плавающей запятой (FLOP) за такт.
Это дает вам представление о том, как далеко все зашло. Мелочи оптимизации - например, сдвиг бит по сравнению с *
, которые воспринимались серьезно даже в 90-е годы, сейчас просто устарели. Битовый сдвиг все еще быстрее, но для множителей без степени двойки к тому времени, когда вы сделаете все свои сдвиги и добавите результаты, он снова станет медленнее. Затем, чем больше инструкций, тем больше ошибок кеша, больше потенциальных проблем с конвейерной обработкой, больше использования временных регистров может означать большее сохранение и восстановление содержимого регистров из стека ... быстро становится слишком сложно количественно оценить все воздействия окончательно, но они преимущественно отрицательный.
функциональность в исходном коде против реализации
В общем, ваш вопрос помечен как C и C ++. Как языки третьего поколения, они специально разработаны, чтобы скрыть детали базового набора инструкций ЦП. Чтобы соответствовать стандартам своего языка, они должны поддерживать операции умножения и сдвига (и многие другие) , даже если базовое оборудование не поддерживает. В таких случаях они должны синтезировать требуемый результат, используя множество других инструкций. Точно так же они должны обеспечивать программную поддержку операций с плавающей запятой, если она отсутствует в ЦП и нет FPU. Все современные процессоры поддерживают *
и <<
, поэтому это может показаться абсурдным теоретическим и историческим, но важно то, что свобода выбора реализации идет в обоих направлениях: даже если у ЦП есть инструкция, которая реализует операцию, запрошенную в исходном коде в В общем случае компилятор может выбрать что-то еще, что он предпочитает, потому что это лучше для конкретного случая, с которым компилятор сталкивается.
Примеры (с гипотетическим языком ассемблера)
source literal approach optimised approach
#define N 0
int x; .word x xor registerA, registerA
x *= N; move x -> registerA
move x -> registerB
A = B * immediate(0)
store registerA -> x
...............do something more with x...............
Такие инструкции, как эксклюзивное или (xor
) не имеют отношения к исходному коду, но xor-ing что-либо с собой очищает все биты, поэтому его можно использовать для установки чего-либо в 0. Исходный код, который подразумевает адреса памяти, может не повлечь за собой использование каких-либо .
Такого рода взломы использовались с тех пор, как появились компьютеры. На заре существования 3GL, чтобы обеспечить понимание разработчиками, выходные данные компилятора должны были удовлетворять потребности существующего хардкорного разработчика, оптимизирующего язык ассемблера вручную. сообществу, что созданный код не был медленнее, многословнее или хуже. Компиляторы быстро внедрили множество отличных оптимизаций - они стали лучшим централизованным хранилищем, чем мог бы быть любой отдельный программист на языке ассемблера, хотя всегда есть шанс, что они пропустят конкретную оптимизацию, которая имеет решающее значение в конкретном случае - люди иногда могут заткнись и ищи что-нибудь получше, в то время как компиляторы просто делают то, что им говорят, пока кто-нибудь не вернет им этот опыт.
Таким образом, даже если смещение и добавление все еще быстрее на каком-то конкретном оборудовании, то разработчик компилятора, вероятно, сработал именно тогда, когда это безопасно и полезно.
Ремонтопригодность
Если ваше оборудование изменится, вы можете перекомпилировать, и он посмотрит на целевой ЦП и сделает другой лучший выбор, в то время как вы вряд ли когда-нибудь захотите пересмотреть свои «оптимизации» или перечислить, какие среды компиляции должны использовать умножение, а какие должны измениться. Подумайте обо всех «оптимизациях» с битовым сдвигом, не использующих степень двойки, написанных более 10 лет назад, которые теперь замедляют код, в котором они находятся, поскольку он выполняется на современных процессорах ...!
К счастью, хорошие компиляторы, такие как GCC, обычно могут заменить серию битовых сдвигов и арифметики прямым умножением, когда включена какая-либо оптимизация (например, ...main(...) { return (argc << 4) + (argc << 2) + argc; }
-> imull $21, 8(%ebp), %eax
), поэтому перекомпиляция может помочь даже без исправления кода, но это не гарантируется.
Странный код сдвига битов, реализующий умножение или деление, гораздо менее выразит то, что вы концептуально пытались достичь, поэтому другие разработчики будут сбиты с толку этим, а сбитый с толку программист с большей вероятностью внесет ошибки или удалит что-то важное, чтобы восстановить кажущееся здравомыслие. Если вы делаете неочевидные вещи только тогда, когда они действительно ощутимо полезны, а затем хорошо их документируете (но в любом случае не документируете другие интуитивно понятные вещи), все будут счастливы.
Общие решения против частичных решений
Если у вас есть дополнительные знания, например, что ваш int
на самом деле будет хранить только значения x
, y
и z
, тогда вы сможете разработать некоторые инструкции, которые работают для этих значений и получить результат быстрее, чем когда компилятор не обладает таким пониманием и нуждается в реализации, которая работает для всех int
значений. Например, рассмотрим свой вопрос:
Умножение и деление можно выполнить с помощью битовых операторов ...
Вы иллюстрируете умножение, но как насчет деления?
int x;
x >> 1; // divide by 2?
Согласно стандарту C ++ 5.8:
-3- Значение E1 >> E2 - это битовые позиции E2 со сдвигом вправо E1. Если E1 имеет беззнаковый тип или если E1 имеет знаковый тип и неотрицательное значение, значение результата представляет собой целую часть частного от E1, деленного на количество 2, возведенное в степень E2. Если E1 имеет тип со знаком и отрицательное значение, результирующее значение определяется реализацией.
Итак, ваш битовый сдвиг имеет результат, определенный реализацией, когда x
отрицательно: он может работать по-разному на разных машинах. Но /
работает гораздо более предсказуемо. (Это также может быть не идеально согласованным, поскольку разные машины могут иметь разные представления отрицательных чисел и, следовательно, разные диапазоны, даже если есть одно и то же число битов, составляющих представление.)
Вы можете сказать: «Меня не волнует ... что int
хранит возраст сотрудника, он никогда не может быть отрицательным». Если у вас есть такое особое понимание, тогда да - ваша >>
безопасная оптимизация может быть проигнорирована компилятором, если вы явно не сделаете это в своем коде. Но это рискованно и редко полезно, поскольку большую часть времени у вас не будет такого понимания, и другие программисты, работающие над тем же кодом, не будут знать, что вы поставили дом на некоторые необычные ожидания в отношении данных, которые вы будете обрабатывать ... то, что кажется им полностью безопасным, может иметь неприятные последствия из-за вашей "оптимизации".
Есть ли какие-то данные, которые нельзя умножить или разделить таким образом?
Да ... как упоминалось выше, отрицательные числа имеют поведение, определяемое реализацией, когда они «делятся» битовым сдвигом.
person
Tony Delroy
schedule
17.06.2011