#c# #.net #performance #portability #unchecked
#c# #.net #Производительность #переносимость #непроверенный
Вопрос:
Я хочу реализовать быструю хеш-функцию, которая будет использовать int
тип данных и полагаться на переполнение целых чисел. В MSDN говорится, что для гарантии того, что переполнения не вызывают исключений, я должен использовать unchecked
блоки для этого кода.
Предположим, я помещаю только это вычисление в unchecked
блок. Будут ли у моего кода какие-либо проблемы с производительностью или переносимостью из-за этого?
Комментарии:
1. @ Marc Gravell: Да, я это знаю, но есть вероятность, что мой код будет повторно использован в проекте с
checked
принудительным применением, и я хочу, чтобы мой код работал правильно.2. @ Marc Gravell: На самом деле меня беспокоит то, что у меня останутся все приятные преимущества комбинации MSIL JIT, если я использую
unchecked
?3.вы правы, добавляя
unchecked
к этому; я пытался подчеркнуть, что он будет вести себя точно так же, как и раньше — да, вы получаете то же поведение MSIL JIT. Действительно, это помогает гарантировать, что вы получите то, что хотите. Как уже отмечалось, теоретически возможно, что какой-то процессор (возможно, где-то на микро-платформе) должен выполнять больше работы в случаеchecked
, но это a: вряд ли будет проблемой, и b: на самом деле не влияет на вашиunchecked
вещи — и это полностью деталь реализации.4. острозубый, похоже, ты путаешь
unchecked
unsafe
. Использованиеunchecked
по-прежнему безопасно и оказывает очень незначительное влияние.
Ответ №1:
проверено добавление только одной инструкции процесса:
checked
{
int y = x * x;
05297978 mov eax,dword ptr [ebp-10h]
0529797B imul eax,dword ptr [ebp-10h]
0529797F jno 05297986 //if not overflow: goto 05297986
05297981 call 72A29522 //invoke exception
05297986 mov dword ptr [ebp-14h],eax
}
unchecked
{
int yy = xx * xx;
0529799E mov eax,dword ptr [ebp-18h]
052979A1 imul eax,dword ptr [ebp-18h]
052979A5 mov dword ptr [ebp-1Ch],eax
}
Ответ №2:
Технически checked
замедляться должны только блоки. Поэтому я не думаю unchecked
, что блок (блок, в котором фреймворк должен выполнять меньше проверок) может замедлиться. Это не переключение контекста или что-то подобное. JIT просто не выдает инструкции для проверки переполнения / переполнения. Теперь ясно, что если бы кто-то создал «специальный» процессор, в котором необходимо имитировать переполнение, и перенес на него Mono, или где переполнение приводит к другим результатам, чем на процессорах Intel, unchecked
блок был бы медленнее (потому что JIT должен был бы «имитировать» его). Но обратите внимание, что базовые типы .NET определены в стандарте ECMA. Подписанные целые числа должны быть основаны, например, на двухкомпонентности, а их размер должен составлять 8, 16, 32, 64 бита. Для «странных» процессоров, использующих 36-битные целые числа, не так много места.
Ответ №3:
Как правило, вы можете ожидать, что непроверенная арифметика будет немного быстрее, но практически никогда не стоит задавать противоположный вопрос вашему (а именно: «повлияет ли использование checked на мою производительность?»).
Отмеченные и непроверенные просто означают, что существуют несколько разные правила относительно того, как обрабатываются операторы
, *
, и -
, и вы должны использовать тот, который подходит для данного случая.
В этом случае вы определенно хотите непроверенный, поэтому вы должны указать это в своем коде. Это фактически повышает переносимость, поскольку тогда у вас будет одинаковое поведение, независимо от того, какие переключатели компилятора используются с ним.
Ответ №4:
Я создал два метода, один с помощью checked
, а другой с помощью unchecked
. При изучении IL только одно отличие — mul
операция (которая выполняет операцию умножения), для checked mul.ovf
генерируется, а для unchecked — mul
.
Подводя итог, я считаю, что разница в одной операции процессора не оказывает никакого влияния на производительность, единственное отличие будет в случае использования переполнения checked
— в этом случае будет сгенерировано исключение OverflowException, которое, очевидно, замедляет выполнение.
Следующие инструкции Microsoft intermediate language (MSIL) вызывают исключение OverflowException:
- mul.ovf.
- …
[Test]
public void Checked()
{
checked
{
int i = int.MaxValue;
i = i * 100;
Debug.WriteLine(i);
}
}
[Test]
public void UnChecked()
{
unchecked
{
int i = int.MaxValue;
i = i * 100;
Debug.WriteLine(i);
}
}
А затем с помощью ILDASM см. IL:
ПРОВЕРЕНО ():
// Code size 27 (0x1b)
.maxstack 2
.locals init ([0] int32 i)
IL_0000: nop
IL_0001: nop
IL_0002: ldc.i4 0x7fffffff
IL_0007: stloc.0
IL_0008: ldloc.0
IL_0009: ldc.i4.s 100
**IL_000b: mul.ovf** !!!
IL_000c: stloc.0
IL_000d: ldloc.0
IL_000e: box [mscorlib]System.Int32
IL_0013: call void [System]System.Diagnostics.Debug::WriteLine ...
НЕПРОВЕРЕННЫЙ ():
// Code size 27 (0x1b)
.maxstack 2
.locals init ([0] int32 i)
IL_0000: nop
IL_0001: nop
IL_0002: ldc.i4 0x7fffffff
IL_0007: stloc.0
IL_0008: ldloc.0
IL_0009: ldc.i4.s 100
**IL_000b: mul** !!!
IL_000c: stloc.0
IL_000d: ldloc.0
IL_000e: box [mscorlib]System.Int32
IL_0013: call void [System]System.Diagnostics.Debug::WriteLine(...)
Комментарии:
1. Я бы назвал махинации с декомпиляцией. Вместо этого посмотрите на IL — обычно это просто небольшая разница в коде операции. Кроме того, использование
byte
в одном иint
в другом вводит в заблуждение. Если я используюInt32
код для обоих, единственная разница —add
vsadd.ovf
2. @Marc Gravell ♦: да, я просто изменил это, чтобы оно было более актуальным, в любом случае спасибо за nte!
3. Я считаю, что нам нужно более глубокое описание для обоих mul / mul.ovf относительно того, как они генерируются в инструкциях процессора, и только тогда мы сможем сравнить. Но в целом я считаю, что это показывает, что разница очень мала, одна инструкция на одну операцию — это не так уж много