Между чем различия
int size = (int)((length * 200L) / 100L); // (1)
и
int size = length << 1; // (2)
(длина является интервалом в обоих случаях),
Я предполагаю, что оба фрагмента кода хотят удвоить параметр длины.
Я испытал бы желание использовать (2)... так там какие-либо преимущества для использования (1)? Я посмотрел на пограничные случаи, когда переполнение происходит, и обе версии, кажется, имеют то же поведение.
Скажите мне, что является мной пропавшие без вести.
И вот третий вариант:
int size = length * 2; // Comment explaining what is 2 or what means this multiplication
И это, должно быть, лучший вариант. Поскольку он читабелен и легко понять, что вы хотите сделать.
Что касается производительности, компиляторы генерируют довольно оптимизированный код, поэтому не нужно беспокоиться о такой простой операции.
Если вас беспокоит переполнение, вы можете использовать проверенный блок
.
РЕДАКТИРОВАТЬ Как упоминалось многими другими, просто используйте здесь любую значимую переменную вместо 2
.
Что более читаемо для вашего среднего программиста:
int size = length * 2;
int size = length << 1;
Если они не исходят из сильного опыта настройки битов C ++, я готов поспорить, что ваш средний программист знает немедленно , что делает первая строка (в ней даже есть число "2" для "двойного" в нем), но придется остановиться и сделать паузу для второй строки.
На самом деле я был бы склонен прокомментировать вторую строку, объясняющую, что она делает, что кажется излишним, когда вы можете заставить код говорить, как в первой строке.
Что означают 200L
и 100L
? Мне кажется, вы используете магические числа . Вы должны попытаться написать свою программу таким образом, чтобы она как можно лучше описывала ее намерения; сделать его максимально читаемым. Использование именованных констант для этих значений вместо магических чисел - хороший способ сделать это. Также помогает извлечение расчета в его собственном хорошо названном методе. Когда вы это сделаете, вы сразу увидите, что переписать его на x << 1
невозможно. Не потому, что результаты будут другими, а потому, что пострадает ремонтопригодность. Когда вы пишете код вроде x << 1
, следующий программист понятия не имеет, что это на самом деле означает, и он увеличит знаменитые WTF в минуту :
(источник: ] osnews.com )
int size = CalculateSizeOfThing(length);
private static int CalculateSizeOfThing(int length)
{
const long TotalArraySize = 200L;
const long BytesPerElement = 100L;
return (length * TotalArraySize) / BytesPerElement;
}
Конечно, названия этих const
значений - это догадка: -)
Чтобы ответить на ваш вопрос, похоже, что между этими двумя фрагментами кода нет различий в поведении, когда length
является неотрицательным int
и текст кода находится в непроверенном
контексте.
В контексте checked
(int.MaxValue * 200L)
никогда не переполнится, потому что результат легко помещается в long
. (int)((length * 200L) / 100L
переполнится только при переполнении length * 2
. Ни при каких обстоятельствах оператор сдвига не переполнится. Поэтому фрагменты кода ведут себя по-разному в контексте checked
.
Если length
отрицательна, то операция сдвига даст неверный результат, а умножение будет работать правильно. Поэтому эти два фрагмента кода отличаются, если переменной length
разрешено быть отрицательной.
(Вопреки распространенному мнению x << 1 - это не то же самое, что x * 2. Они одинаковы, только если x - беззнаковое целое число.)
.В любом современном компиляторе сдвиг влево на 1 или умножение на 2 будет генерировать тот же код. Вероятно, это будет инструкция добавления, добавляющая номер самому себе, но это зависит от вашей цели.
С другой стороны, выполнение (x * 200) / 100 не будет таким же, как удвоение числа, поскольку первое умножение может привести к переполнению, поэтому его нельзя безопасно оптимизировать для удвоения числа.
Идея о том, что <<
быстрее, чем умножение, основывается на том, что jit-компилятор .NET на самом деле является плохо оптимизированным компилятором C, написанным в 1970-х годах. Даже если бы это было правдой, разница была бы измерена в пикосекундах в этот момент времени, даже если бы была разница, которой, вероятно, нет.
Напишите код так, чтобы его было легко читать .Пусть компилятор позаботится о пикооптимизациях. Оптимизируйте свой код на основе реалистичных сценариев профилирования, а не на повторном предположении, что сгенерирует компилятор.
Кроме того, операторы сдвига не имеют ту же семантику, что и умножение. Например, рассмотрим следующую последовательность правок:
Исходная программа Джилл:
int x = y * 2;
Правка Боба: Глупая Джилл, я сделаю это «быстрее»:
int x = y << 1;
Правка Ларри-интерна: О, у нас есть ошибка, мы разошлись по очереди, позвольте мне исправить это:
int x = y << 1 + 1;
и Ларри только что представил новую ошибку. y * 2 + 1 отличается от y << 1 + 1; последнее на самом деле y * 4.
Я видел эту ошибку в реальном производственном коде . Очень легко мысленно сформировать образ мышления, что «сдвиг - это умножение», и забыть, что сдвиг имеет более низкий приоритет , чем сложение, тогда как умножение имеет более высокий приоритет .
Я ни разу не видел, чтобы кто-то ошибся в арифметическом приоритете, умножив его на два, написав x * 2. Люди понимают приоритет + и *. Многие люди забывают, каков приоритет переключения передач. Стоят ли пикосекунды, которые вы не спасают , какого-либо числа потенциальных ошибок? Я говорю нет.
Звучит как преждевременная оптимизация. Плюс простого выполнения length * 2 в том, что компилятор, безусловно, оптимизирует это, и его легче поддерживать.
Интересно, что в большинстве ответов утверждается, что компилятор оптимизирует умножение на степень 2 в битовый сдвиг. Очевидно, что никто из ответивших не пробовал компилировать битовый сдвиг в сравнении с умножением, чтобы увидеть, что на самом деле получается у компилятора.
Это чисто академическое упражнение; как почти все отметили, умножение легче читать (хотя зачем там часть "*200L / 100L", никто не знает - она просто служит для запутывания). Также очевидно, что замена умножения на битовый сдвиг в C# не даст существенного прироста производительности даже в узких циклах. Если вам нужна такая оптимизация, то вы используете не ту платформу и не тот язык.
Давайте посмотрим, что произойдет, если мы скомпилируем простую программу с помощью CSC (компилятора C#), входящего в состав Visual Studio 2010, с включенными оптимизациями. Вот первая программа:
static void Main(string[] args)
{
int j = 1;
for (int i = 0; i < 100000; ++i)
{
j *= 2;
}
}
Используя ildasm для декомпиляции полученного исполняемого файла, мы получим следующий листинг CIL:
.method private hidebysig static void Main(string[] args) cil managed
{
.entrypoint
// Code size 23 (0x17)
.maxstack 2
.locals init ([0] int32 j,
[1] int32 i)
IL_0000: ldc.i4.1
IL_0001: stloc.0
IL_0002: ldc.i4.0
IL_0003: stloc.1
IL_0004: br.s IL_000e
IL_0006: ldloc.0
IL_0007: ldc.i4.2
IL_0008: mul
IL_0009: stloc.0
IL_000a: ldloc.1
IL_000b: ldc.i4.1
IL_000c: add
IL_000d: stloc.1
IL_000e: ldloc.1
IL_000f: ldc.i4 0x186a0
IL_0014: blt.s IL_0006
IL_0016: ret
} // end of method Program::Main
Вот вторая программа:
static void Main(string[] args)
{
int j = 1;
for (int i = 0; i < 100000; ++i)
{
j <<= 1;
}
}
Декомпилировав ее, мы получим следующий листинг CIL:
.method private hidebysig static void Main(string[] args) cil managed
{
.entrypoint
// Code size 23 (0x17)
.maxstack 2
.locals init ([0] int32 j,
[1] int32 i)
IL_0000: ldc.i4.1
IL_0001: stloc.0
IL_0002: ldc.i4.0
IL_0003: stloc.1
IL_0004: br.s IL_000e
IL_0006: ldloc.0
IL_0007: ldc.i4.2
IL_0008: shl
IL_0009: stloc.0
IL_000a: ldloc.1
IL_000b: ldc.i4.1
IL_000c: add
IL_000d: stloc.1
IL_000e: ldloc.1
IL_000f: ldc.i4 0x186a0
IL_0014: blt.s IL_0006
IL_0016: ret
} // end of method Program::Main
Обратите внимание на разницу в строке 8. В первой версии программы используется умножение (mul), а во второй - сдвиг влево (shl).
Что именно JIT делает с этим при выполнении кода, я не знаю, но сам компилятор C#, очевидно, не оптимизирует умножение на степень двойки в битовые сдвиги.
Когда я вижу идиому:
int val = (someval * someConstant) / someOtherConstant;
, я думаю, что цель кода - каким-то образом выполнить масштабирование. Это может быть код с фиксированной запятой вручную или можно избежать проблем с целочисленным делением. Конкретный пример:
int val = someVal * (4/5); // oops, val = 0 - probably not what was intended
или написано, чтобы избежать перехода к плавающей запятой:
int val = (int)(someVal * .8); // better than 0 - maybe we wanted to round though - who knows?
, когда я вижу эту идиому:
int val = someVal << someConstant;
Я вкратце задаюсь вопросом, имеют ли отдельные биты в someVal какое-то более глубокое значение, требующее сдвига, а затем Я начинаю смотреть на окружающий контекст, чтобы понять, почему это было сделано именно так.
Следует помнить, что когда вы пишете код, который имеет что-то вроде этого:
int val = expr;
, существует бесконечное количество способов создать expr, так что val всегда будет иметь одно и то же значение. Для тех, кто последует, важно учитывать, какое намерение вы выражаете в expr.