Я смотрел на некоторый код с огромным оператором переключения и, если еще оператор на каждом случае и немедленно чувствовал желание оптимизировать. Поскольку хороший разработчик всегда должен делать, я намеревался получать некоторые трудные факты синхронизации и запустился с трех вариантов:
Исходный код похож на это:
public static bool SwitchIfElse(Key inKey, out char key, bool shift)
{
switch (inKey)
{
case Key.A: if (shift) { key = 'A'; } else { key = 'a'; } return true;
case Key.B: if (shift) { key = 'B'; } else { key = 'b'; } return true;
case Key.C: if (shift) { key = 'C'; } else { key = 'c'; } return true;
...
case Key.Y: if (shift) { key = 'Y'; } else { key = 'y'; } return true;
case Key.Z: if (shift) { key = 'Z'; } else { key = 'z'; } return true;
...
//some more cases with special keys...
}
key = (char)0;
return false;
}
Второй вариант, преобразованный для использования условного оператора:
public static bool SwitchConditionalOperator(Key inKey, out char key, bool shift)
{
switch (inKey)
{
case Key.A: key = shift ? 'A' : 'a'; return true;
case Key.B: key = shift ? 'B' : 'b'; return true;
case Key.C: key = shift ? 'C' : 'c'; return true;
...
case Key.Y: key = shift ? 'Y' : 'y'; return true;
case Key.Z: key = shift ? 'Z' : 'z'; return true;
...
//some more cases with special keys...
}
key = (char)0;
return false;
}
Скручивание с помощью словаря, предварительно заполненного ключевыми/символьными парами:
public static bool DictionaryLookup(Key inKey, out char key, bool shift)
{
key = '\0';
if (shift)
return _upperKeys.TryGetValue(inKey, out key);
else
return _lowerKeys.TryGetValue(inKey, out key);
}
Примечание: эти два оператора переключения имеют те же самые случаи, и словари имеют равное количество символов.
Я ожидал, что 2) 1) и 2) было несколько подобно в производительности и который 3) будет немного медленнее.
Для каждого метода, выполняющего два раза 10 000 000 повторений для прогрева и затем синхронизированный, к моему изумлению, я получаю следующие результаты:
Как это может быть? Условный оператор в четыре раза медленнее, чем если еще операторы и почти в два раза медленнее, чем поиски по словарю. Я пропускаю что-то существенное здесь, или условный оператор является по сути медленным?
Обновление 1: Несколько слов о моей тестовой обвязке. Я работаю, следующий (псевдо) код для каждого из вышеупомянутых вариантов при Выпуске скомпилировал проект.Net 3.5 в Visual Studio 2010. Оптимизация кода включена, и константы ОТЛАДКИ/ТРАССИРОВКИ выключены. Я выполняю метод при измерении однажды для прогрева прежде, чем сделать синхронизированное выполнение. Метод выполнения выполнил метод для большого количества повторений, с shift
набор и к истине и лжи и с избранным набором входных ключей:
Run(method);
var stopwatch = Stopwatch.StartNew();
Run(method);
stopwatch.Stop();
var measure = stopwatch.ElapsedMilliseconds / iterations;
Метод Выполнения похож на это:
for (int i = 0; i < iterations / 4; i++)
{
method(Key.Space, key, true);
method(Key.A, key, true);
method(Key.Space, key, false);
method(Key.A, key, false);
}
Обновление 2: Роя далее, я посмотрел на IL, сгенерированный для 2) 1) и 2) и находят, что структуры главного выключателя идентичны, как я ожидал бы, все же тела случая имеют незначительные различия. Вот IL, на который я смотрю:
1) Если/еще оператор:
L_0167: ldarg.2
L_0168: brfalse.s L_0170
L_016a: ldarg.1
L_016b: ldc.i4.s 0x42
L_016d: stind.i2
L_016e: br.s L_0174
L_0170: ldarg.1
L_0171: ldc.i4.s 0x62
L_0173: stind.i2
L_0174: ldc.i4.1
L_0175: ret
2) Условный оператор:
L_0165: ldarg.1
L_0166: ldarg.2
L_0167: brtrue.s L_016d
L_0169: ldc.i4.s 0x62
L_016b: br.s L_016f
L_016d: ldc.i4.s 0x42
L_016f: stind.i2
L_0170: ldc.i4.1
L_0171: ret
Некоторые наблюдения:
shift
равняется верный в то время как если/еще ответвления когда shift
ложь. shift
любой TRUE или FALSE, равны для двух.Какое-либо из этих наблюдений подразумевает, что условный оператор будет работать медленнее? Есть ли другие побочные эффекты, которые играют роль?
Очень странно, возможно, оптимизация .NET дает обратный эффект в вашем случае:
Автор разобрал несколько версии троичных выражений и обнаружил, что они идентичны if-выражения, с одним небольшим отличием. Тернарное выражение иногда производит код, который проверяет противоположное условие, которое вы ожидать, например, проверяется, что подвыражение ложно вместо того, чтобы вместо того, чтобы проверить, истинно ли оно. Это изменяет порядок некоторые инструкции и может иногда увеличивает производительность.
http://dotnetperls.com/ternary
Вы могли бы рассмотреть ToString на значении перечисления (для неспециальных случаев):
string keyValue = inKey.ToString();
return shift ? keyValue : keyValue.ToLower();
EDIT:
Я сравнил метод if-else с тернарным оператором и при 1000000 циклах тернарный оператор всегда по крайней мере так же быстр, как метод if-else (иногда на несколько миллисекунд быстрее, что подтверждает текст выше). Я думаю, что вы допустили какую-то ошибку при измерении времени, которое потребовалось.
Я не совсем понимаю, почему вы ожидаете, что оператор if будет медленнее, чем поиск по словарю. По крайней мере, необходимо вычислить хэш-код, а затем его нужно найти в списке. Я не понимаю, почему вы думаете, что это быстрее, чем cmp / jmp.
В частности, я даже не думаю, что метод, который вы оптимизируете, настолько хорош; кажется, что это можно было бы улучшить на этапе вызова (хотя я не уверен, так как вы не предоставили контекст).
Используется для указания атрибута keyIdentifier
объекта .
<html>
<head>
<script type="text/javascript">
document.onkeydown = function() {
alert (event.keyIdentifier);
};
</script>
</head>
<body>
</body>
</html>
Тогда вы можете использовать логику if, чтобы игнорировать все клавиши, которые вас не интересуют, и направить правильное поведение к тем, кто вы есть.
Ниже приведены ссылки со стрелками влево и вправо (на основе идентификатора элементов привязки/ссылки).
<html>
<head>
<script type="text/javascript" src="http://ajax.googleapis.com/ajax/libs/jquery/1.4.1/jquery.min.js"></script>
<script type="text/javascript">
$(document).ready(function() {
document.onkeydown = function()
{
var j = event.keyIdentifier
if (j == "Right")
window.location = nextUrl
else if (j == "Left")
window.location = prevUrl
}
});
$(document).ready(function() {
var nextPage = $("#next_page_link")
var prevPage = $("#previous_page_link")
nextUrl = nextPage.attr("href")
prevUrl = prevPage.attr("href")
});
</script>
</head>
<body>
<p>
<a id="previous_page_link" href="http://www.google.com">Google</a>
<a id="next_page_link" href="http://www.yahoo.com">Yahoo</a>
</p>
</body>
</html>
-121--2655729- Создание экземпляра шаблона
с последовательностью для использования в качестве шаблона.
Я бы ожидал, что # 1 и # 2 будут одинаковыми. Оптимизатор должен иметь тот же код. Словарь в # 3 должен быть медленным, если он не оптимизирован так или иначе, чтобы не использовать хэш.
При кодировании систем реального времени мы всегда использовали таблицу поиска - простой массив - для перевода, как указано в вашем примере. Это самое быстрое, когда диапазон входных данных довольно мал.
Создание экземпляра шаблона
с последовательностью для использования в качестве шаблона.
Я бы ожидал, что # 1 и # 2 будут одинаковыми. Оптимизатор должен иметь тот же код. Словарь в # 3 должен быть медленным, если он не оптимизирован так или иначе, чтобы не использовать хэш.
При кодировании систем реального времени мы всегда использовали таблицу поиска - простой массив - для перевода, как указано в вашем примере. Это самое быстрое, когда диапазон входных данных довольно мал.
-121--1496344-Я бы выбрал третий вариант только потому, что он более удобочитаемый/ремонтопригодный. Спорим, этот код не является узким местом в производительности вашего приложения.
Мне было бы любопытно узнать, тестируете ли вы это с помощью сборки отладки или выпуска. Если это отладочная сборка, то разница, скорее всего, может быть различием из-за ОТСУТСТВИЯ низкоуровневых оптимизаций, которые компилятор добавляет, когда вы используете режим выпуска (или вручную отключите режим отладки и включите оптимизацию компилятора)
I Однако при включенной оптимизации можно ожидать, что тернарный оператор будет иметь ту же скорость или немного быстрее, чем оператор if / else, в то время как поиск по словарю будет самым медленным. Вот мои результаты, 10 миллионов итераций прогрева, за которыми следуют 10 миллионов синхронизированных по времени, для каждой:
РЕЖИМ ОТЛАДКИ
If/Else: 00:00:00.7211259
Ternary: 00:00:00.7923924
Dictionary: 00:00:02.3319567
РЕЖИМ ВЫПУСКА
If/Else: 00:00:00.5217478
Ternary: 00:00:00.5050474
Dictionary: 00:00:02.7389423
Я думаю, что здесь интересно отметить, что до включения оптимизаций троичные вычисления медленнее, чем if / else, а после - быстрее.
РЕДАКТИРОВАТЬ:
После небольшого дополнительного тестирования, в практическом смысле, нет почти никакой разницы между if / else и тернарным. Хотя троичный код приводит к уменьшению IL, они работают почти так же, как друг друга. В десятке различных тестов с двоичным кодом режима выпуска результаты if / else и троичные были либо идентичны, либо отклонялись на доли миллисекунды для 10 000 000 итераций. Иногда if / else было немного быстрее, иногда было тройным, но с практической точки зрения они работают одинаково.
С другой стороны, словарь работает значительно хуже. Когда дело доходит до таких оптимизаций, я бы не стал тратить время на выбор между if / else и тернарным, если код уже существует.Однако, если у вас в настоящее время есть реализация словаря, я бы определенно реорганизовал ее, чтобы использовать более эффективный подход и улучшить вашу производительность примерно на 400% (во всяком случае, для данной функции).
Интересно, я пошел и разработал небольшой класс IfElseTernaryTest
здесь, хорошо, код на самом деле не «оптимизирован» и не является хорошим примером, но тем не менее ... ради обсуждения:
public class IfElseTernaryTest
{
private bool bigX;
public void RunIfElse()
{
int x = 4; int y = 5;
if (x > y) bigX = false;
else if (x < y) bigX = true;
}
public void RunTernary()
{
int x = 4; int y = 5;
bigX = (x > y) ? false : ((x < y) ? true : false);
}
}
Это был дамп кода IL ... интересная часть заключалось в том, что тернарные инструкции в IL были на самом деле короче, чем if
....
.class /*02000003*/ public auto ansi beforefieldinit ConTern.IfElseTernaryTest
extends [mscorlib/*23000001*/]System.Object/*01000001*/
{
.field /*04000001*/ private bool bigX
.method /*06000003*/ public hidebysig instance void
RunIfElse() cil managed
// SIG: 20 00 01
{
// Method begins at RVA 0x205c
// Code size 44 (0x2c)
.maxstack 2
.locals /*11000001*/ init ([0] int32 x,
[1] int32 y,
[2] bool CS$4$0000)
.line 19,19 : 9,10 ''
//000013: }
//000014:
//000015: public class IfElseTernaryTest
//000016: {
//000017: private bool bigX;
//000018: public void RunIfElse()
//000019: {
IL_0000: /* 00 | */ nop
.line 20,20 : 13,23 ''
//000020: int x = 4; int y = 5;
IL_0001: /* 1A | */ ldc.i4.4
IL_0002: /* 0A | */ stloc.0
.line 20,20 : 24,34 ''
IL_0003: /* 1B | */ ldc.i4.5
IL_0004: /* 0B | */ stloc.1
.line 21,21 : 13,23 ''
//000021: if (x > y) bigX = false;
IL_0005: /* 06 | */ ldloc.0
IL_0006: /* 07 | */ ldloc.1
IL_0007: /* FE02 | */ cgt
IL_0009: /* 16 | */ ldc.i4.0
IL_000a: /* FE01 | */ ceq
IL_000c: /* 0C | */ stloc.2
IL_000d: /* 08 | */ ldloc.2
IL_000e: /* 2D | 09 */ brtrue.s IL_0019
.line 21,21 : 24,37 ''
IL_0010: /* 02 | */ ldarg.0
IL_0011: /* 16 | */ ldc.i4.0
IL_0012: /* 7D | (04)000001 */ stfld bool ConTern.IfElseTernaryTest/*02000003*/::bigX /* 04000001 */
IL_0017: /* 2B | 12 */ br.s IL_002b
.line 22,22 : 18,28 ''
//000022: else if (x < y) bigX = true;
IL_0019: /* 06 | */ ldloc.0
IL_001a: /* 07 | */ ldloc.1
IL_001b: /* FE04 | */ clt
IL_001d: /* 16 | */ ldc.i4.0
IL_001e: /* FE01 | */ ceq
IL_0020: /* 0C | */ stloc.2
IL_0021: /* 08 | */ ldloc.2
IL_0022: /* 2D | 07 */ brtrue.s IL_002b
.line 22,22 : 29,41 ''
IL_0024: /* 02 | */ ldarg.0
IL_0025: /* 17 | */ ldc.i4.1
IL_0026: /* 7D | (04)000001 */ stfld bool ConTern.IfElseTernaryTest/*02000003*/::bigX /* 04000001 */
.line 23,23 : 9,10 ''
//000023: }
IL_002b: /* 2A | */ ret
} // end of method IfElseTernaryTest::RunIfElse
.method /*06000004*/ public hidebysig instance void
RunTernary() cil managed
// SIG: 20 00 01
{
// Method begins at RVA 0x2094
// Code size 27 (0x1b)
.maxstack 3
.locals /*11000002*/ init ([0] int32 x,
[1] int32 y)
.line 25,25 : 9,10 ''
//000024: public void RunTernary()
//000025: {
IL_0000: /* 00 | */ nop
.line 26,26 : 13,23 ''
//000026: int x = 4; int y = 5;
IL_0001: /* 1A | */ ldc.i4.4
IL_0002: /* 0A | */ stloc.0
.line 26,26 : 24,34 ''
IL_0003: /* 1B | */ ldc.i4.5
IL_0004: /* 0B | */ stloc.1
.line 27,27 : 13,63 ''
//000027: bigX = (x > y) ? false : ((x < y) ? true : false);
IL_0005: /* 02 | */ ldarg.0
IL_0006: /* 06 | */ ldloc.0
IL_0007: /* 07 | */ ldloc.1
IL_0008: /* 30 | 0A */ bgt.s IL_0014
IL_000a: /* 06 | */ ldloc.0
IL_000b: /* 07 | */ ldloc.1
IL_000c: /* 32 | 03 */ blt.s IL_0011
IL_000e: /* 16 | */ ldc.i4.0
IL_000f: /* 2B | 01 */ br.s IL_0012
IL_0011: /* 17 | */ ldc.i4.1
IL_0012: /* 2B | 01 */ br.s IL_0015
IL_0014: /* 16 | */ ldc.i4.0
IL_0015: /* 7D | (04)000001 */ stfld bool ConTern.IfElseTernaryTest/*02000003*/::bigX /* 04000001 */
.line 28,28 : 9,10 ''
//000028: }
IL_001a: /* 2A | */ ret
} // end of method IfElseTernaryTest::RunTernary
Таким образом, кажется, что тернарный оператор короче и, я думаю, быстрее, чем меньше инструкций ... но на этом основании это, кажется, противоречит вашему случаю № 2, что удивительно ...
Edit: После комментария Ская, предлагающего «раздувание кода для № 2», это опровергнет то, что сказал Скай !!! Хорошо, код другой, контекст другой, это пример упражнения по проверке дампа IL, чтобы увидеть ...
Предполагая, что вас беспокоит производительность этого метода (а если нет, то зачем его публиковать?), вы должны рассмотреть возможность хранения значений char
в массиве и преобразования значений Key
в индекс массива.
У меня нет VS под рукой, но наверняка есть простой встроенный способ получить ключ в качестве персонажа? Что-то вроде метода toString
, чтобы вы могли заменить этот чудовищный переключатель
следующим:
if (shift)
return inKey.toString().toUppercase();
else
return inKey.toString().toLowercase();