Параллельность - сложное взаимодействие между моделью памяти, оборудованием, кэшами и нашим кодом. В случае Java, по крайней мере, такие тесты были частично рассмотрены главным образом jcstress . Создатели этой библиотеки, как известно, являются авторами многих функций параллелизма JVM, GC и Java.
Но даже эта библиотека нуждается в хорошем знании спецификации модели Java-памяти, чтобы мы точно знали, что мы тестируем. Но я думаю, что в центре внимания этих усилий были mircobenchmarks. Не огромные бизнес-приложения.
Результаты профиля Greg являются большими для точного сценария, который он покрыл, но интересно, относительные затраты на различные методы изменяются существенно при считании многих различных факторов включая количество типов сравненными, и частотность и любые шаблоны в базовых данных.
простой ответ - то, что никто не может сказать Вам, чем различие в производительности будет в Вашем определенном сценарии, необходимо будет измерить уровень по-разному сами в собственной системе для получения точного ответа.
, Если/Еще цепочка является эффективным подходом для небольшого количества сравнений типов, или если можно надежно предсказать, который немного типов собираются составить большинство из тех, что Вы видите. Потенциальная проблема с подходом состоит в том, что как количество увеличений типов, количество сравнений, которые должны быть выполнены увеличения также.
, если я выполняю следующее:
int value = 25124;
if(value == 0) ...
else if (value == 1) ...
else if (value == 2) ...
...
else if (value == 25124) ...
каждый из предыдущих, если условия должны быть оценены перед корректным блоком, вводится. С другой стороны
switch(value) {
case 0:...break;
case 1:...break;
case 2:...break;
...
case 25124:...break;
}
выполнит один простой переход к корректному биту кода.
то, Где это становится более сложным в Вашем примере, - то, что Ваш другой метод использует переключатель на строках, а не целых числах, который становится немного более сложным. На низком уровне строки не могут быть включены таким же образом, что целочисленные значения могут так компилятор C# делать некоторое волшебство сделать эту работу для Вас.
, Если оператор переключения является "достаточно небольшим" (то, где компилятор делает то, что это думает, является лучшим автоматически), включение строк генерирует код, который совпадает с если/еще цепочка.
switch(someString) {
case "Foo": DoFoo(); break;
case "Bar": DoBar(); break;
default: DoOther; break;
}
совпадает с:
if(someString == "Foo") {
DoFoo();
} else if(someString == "Bar") {
DoBar();
} else {
DoOther();
}
, Как только список объектов в словаре становится "достаточно большим" компилятор, автоматически создаст внутренний словарь, который отображается от строк в переключателе к целочисленному индексу и затем переключателе на основе того индекса.
Это выглядит примерно так (Просто воображают больше записей, чем я собираюсь потрудиться вводить)
А, статическое поле определяется в "скрытом" месте, которое связано с классом, содержащим оператор переключения типа Dictionary<string, int>
, и дано скорректированное имя
//Make sure the dictionary is loaded
if(theDictionary == null) {
//This is simplified for clarity, the actual implementation is more complex
// in order to ensure thread safety
theDictionary = new Dictionary<string,int>();
theDictionary["Foo"] = 0;
theDictionary["Bar"] = 1;
}
int switchIndex;
if(theDictionary.TryGetValue(someString, out switchIndex)) {
switch(switchIndex) {
case 0: DoFoo(); break;
case 1: DoBar(); break;
}
} else {
DoOther();
}
В некоторых быстрых тестах, которые я просто запустил, Если/Еще метод о 3x с такой скоростью, как переключатель для 3 различных типов (где типы случайным образом распределяются). В 25 типах переключатель быстрее маленьким полем (16%) в 50 типах, которые переключатель более двух раз как быстро.
, Если бы Вы собираетесь быть включением большого количества типов, я предложил бы 3-й метод:
private delegate void NodeHandler(ChildNode node);
static Dictionary<RuntimeTypeHandle, NodeHandler> TypeHandleSwitcher = CreateSwitcher();
private static Dictionary<RuntimeTypeHandle, NodeHandler> CreateSwitcher()
{
var ret = new Dictionary<RuntimeTypeHandle, NodeHandler>();
ret[typeof(Bob).TypeHandle] = HandleBob;
ret[typeof(Jill).TypeHandle] = HandleJill;
ret[typeof(Marko).TypeHandle] = HandleMarko;
return ret;
}
void HandleChildNode(ChildNode node)
{
NodeHandler handler;
if (TaskHandleSwitcher.TryGetValue(Type.GetRuntimeType(node), out handler))
{
handler(node);
}
else
{
//Unexpected type...
}
}
Это подобно тому, что предложенный Ted Elliot, но использование типа выполнения обрабатывает вместо полных текстовых объектов, избегает издержек загрузки текстового объекта посредством отражения.
Вот некоторые быстрые синхронизации на моей машине:
Testing 3 iterations with 5,000,000 data elements (mode=Random) and 5 types Method Time % of optimal If/Else 179.67 100.00 TypeHandleDictionary 321.33 178.85 TypeDictionary 377.67 210.20 Switch 492.67 274.21 Testing 3 iterations with 5,000,000 data elements (mode=Random) and 10 types Method Time % of optimal If/Else 271.33 100.00 TypeHandleDictionary 312.00 114.99 TypeDictionary 374.33 137.96 Switch 490.33 180.71 Testing 3 iterations with 5,000,000 data elements (mode=Random) and 15 types Method Time % of optimal TypeHandleDictionary 312.00 100.00 If/Else 369.00 118.27 TypeDictionary 371.67 119.12 Switch 491.67 157.59 Testing 3 iterations with 5,000,000 data elements (mode=Random) and 20 types Method Time % of optimal TypeHandleDictionary 335.33 100.00 TypeDictionary 373.00 111.23 If/Else 462.67 137.97 Switch 490.33 146.22 Testing 3 iterations with 5,000,000 data elements (mode=Random) and 25 types Method Time % of optimal TypeHandleDictionary 319.33 100.00 TypeDictionary 371.00 116.18 Switch 483.00 151.25 If/Else 562.00 175.99 Testing 3 iterations with 5,000,000 data elements (mode=Random) and 50 types Method Time % of optimal TypeHandleDictionary 319.67 100.00 TypeDictionary 376.67 117.83 Switch 453.33 141.81 If/Else 1,032.67 323.04
На моей машине, по крайней мере, подход словаря дескриптора типа бьет всех другие для чего-либо более чем 15 различных типов, когда распределение типов, используемых в качестве входа к методу, случайно.
, Если, с другой стороны, вход составлен полностью типа, который проверяется сначала в, если/еще цепочка, что метод очень быстрее:
Testing 3 iterations with 5,000,000 data elements (mode=UniformFirst) and 50 types Method Time % of optimal If/Else 39.00 100.00 TypeHandleDictionary 317.33 813.68 TypeDictionary 396.00 1,015.38 Switch 403.00 1,033.33
С другой стороны, если вход всегда является последней вещью в, если/еще цепочка, это имеет противоположный эффект:
Testing 3 iterations with 5,000,000 data elements (mode=UniformLast) and 50 types Method Time % of optimal TypeHandleDictionary 317.67 100.00 Switch 354.33 111.54 TypeDictionary 377.67 118.89 If/Else 1,907.67 600.52
, Если можно сделать некоторые предположения о входе, Вы могли бы получить лучшую производительность от гибридного подхода, где Вы выполняете, если/еще проверки на несколько типов, которые наиболее распространены, и затем отступают к управляемому словарем подходу, если те перестали работать.
Я могу пропускать что-то, но разве Вы не могли сделать оператора переключения на типе вместо Строки? Таким образом,
switch(childNode.Type)
{
case Bob:
break;
case Jill:
break;
case Marko:
break;
}
Включите строку, в основном компилируется в if-else-if лестничную структуру. Попытайтесь декомпилировать простой. В любом случае тестирование строкового равенства должно быть более дешевым, так как они интернируются и все, что было бы необходимо, проверка ссылки. Сделайте то, что имеет смысл с точки зрения пригодности для обслуживания; если Вы - строки compring, сделайте строковый переключатель. Если Вы выбираете на основе типа, лестничная структура типа является более соответствующим.
Помните, профилировщик является Вашим другом. Любые догадки являются пустой тратой времени большую часть времени. BTW, у меня был хороший опыт с JetBrains профилировщик dotTrace .
Одна из проблем, которые Вы имеете с переключателем, использует строки, как "Bob", это вызовет намного больше циклов и строк в скомпилированном коде. IL, который сгенерирован, должен будет объявить строку, установить ее на "Bob", тогда используют его в сравнении. Таким образом с этим в памяти Ваши операторы IF будут работать быстрее.
пз. Привычка вечности в качестве примера работает, потому что Вы не можете включить Типы. (Не я не знаю, почему точно, но мы попробовали его, это не работает. Это имеет отношение к типу, являющемуся переменным)
, Если Вы хотите протестировать это, просто создать отдельное приложение и создать два простых метода, которые делают то, что описано выше, и используйте что-то как Ildasm.exe для наблюдения IL. Вы заметите намного меньше строк в IL Метода оператора IF.
Ildasm идет с VisualStudio...
страница ILDASM - http://msdn.microsoft.com/en-us/library/f7dy01k1 (По сравнению с 80) Учебным руководством по .aspx
ILDASM - http://msdn.microsoft.com/en-us/library/aa309387 (По сравнению с 71) .aspx
Три мысли:
1), Если Вы собираетесь сделать что-то другое на основе типов объектов, могло бы иметь смысл перемещать то поведение в те классы. Тогда вместо переключателя или если еще, Вы просто назвали бы childNode. DoSomething ().
2) Выдерживающие сравнение типы будут намного быстрее, чем сравнения строк.
3) В, если еще дизайн, Вы могли бы быть в состоянии использовать в своих интересах переупорядочение тестов. Если объекты "Jill" составляют 90% объектов, проходящих там, теста для них сначала.
Конечно, переключатель на Строке скомпилировал бы вниз в Сравнение строк (один на случай), который медленнее, чем сравнение типов (и намного медленнее, чем типичное целое число выдерживает сравнение, который используется для переключателя/случая)?
Если типы, которые Вы включаете, являются примитивными типами.NET, можно использовать Тип. GetTypeCode (Тип), но если они - пользовательские типы, они все возвратятся как TypeCode. Объект.
словарь А с делегатами или классами обработчика мог бы работать также.
Dictionary<Type, HandlerDelegate> handlers = new Dictionary<Type, HandlerDelegate>();
handlers[typeof(Bob)] = this.HandleBob;
handlers[typeof(Jill)] = this.HandleJill;
handlers[typeof(Marko)] = this.HandleMarko;
handlers[childNode.GetType()](childNode);
/// ...
private void HandleBob(Node childNode) {
// code to handle Bob
}
Я вспоминаю чтение в нескольких справочниках, что, если/еще ветвление более быстро, чем оператор переключения. Однако немного исследования в области Blackwasp показывает, что оператор переключения на самом деле быстрее: http://www.blackwasp.co.uk/SpeedTestIfElseSwitch.aspx
В действительности, если Вы сравниваете типичные 3 - 10 (или так) операторы, я серьезно сомневаюсь, что существует любое реальное увеличение производительности с помощью один или другой.
, Поскольку Chris уже сказал, пойдите для удобочитаемости: , Что более быстро, включите строку или elseif на типе?
Если Вы уже не записали это и находите, что у Вас есть проблема производительности, я не волновался бы, о котором более быстро. Пойдите с тем, который это более читаемо. Помните, "Преждевременная оптимизация является корнем всего зла". - Donald Knuth
Попытайтесь использовать перечисления для каждого объекта, можно включить перечисления быстро и легко.
Оператор переключения быстрее для выполнения, чем if-else-if лестничная структура. Это происходит из-за способности компилятора оптимизировать оператор переключения. В случае if-else-if лестничной структуры код должен обработать каждого если оператор в порядке, определенном программистом. Однако, потому что каждый случай в операторе переключения не полагается на более ранние случаи, компилятор в состоянии переупорядочить тестирование таким способом как для обеспечения самого быстрого выполнения.
Если бы Вам сделали классы, я предложил бы использовать шаблон разработки Стратегии вместо переключателя или elseif.
Конструкция ПЕРЕКЛЮЧАТЕЛЯ была первоначально предназначена для целочисленных данных; это полно решимости, должен был использовать аргумент непосредственно в качестве индекса в "таблицу отправки", таблицу указателей. По сути, был бы единственный тест, затем запустился бы непосредственно к соответствующим нормам, а не серии тестов.
трудность здесь состоит в том, что это - использование, был обобщен для "представления в виде строки" типов, которые, очевидно, не могут использоваться в качестве индекса, и все преимущество конструкции ПЕРЕКЛЮЧАТЕЛЯ потеряно.
, Если скорость является Вашей намеченной целью, проблемой НЕ является Ваш код, но Ваша структура данных. Если пространство "имени" так просто, как Вы показываете его, лучше для кодирования его в целочисленное значение (когда данные создаются, например), и используйте это целое число в "много раз в медленной части приложения".
Во-первых, Вы сравниваете яблоки и апельсины. Необходимо было бы сначала выдержать сравнение, включают тип по сравнению с, включают строку, и затем если на типе по сравнению с если на строке, и затем сравнивают победителей.
, Во-вторых, это - вид вещи, для которой было разработано OO. На языках, которые поддерживают OO, включая тип (любого вида) запах кода, который указывает на плохой дизайн. Решение состоит в том, чтобы произойти из общей базы с абстрактным или виртуальным методом (или подобная конструкция, в зависимости от Вашего языка)
, например,
class Node
{
public virtual void Action()
{
// Perform default action
}
}
class Bob : Node
{
public override void Action()
{
// Perform action for Bill
}
}
class Jill : Node
{
public override void Action()
{
// Perform action for Jill
}
}
Затем вместо того, чтобы делать оператор переключения, Вы просто называете childNode. Действие ()
Я просто реализовал быстрое тестовое приложение и представил его с МУРАВЬЯМИ 4.
Спецификация:.Net 3.5 sp1 в Windows XP на 32 бита, код создается в режиме выпуска.
3 миллиона тестов:
, Кроме того, результаты оператора переключения показывают (неудивительно), что более длинные имена занимают больше времени.
1 миллион тестов
я похож, "Если Еще" быстрее, по крайней мере, сценарий, я создал.
class Program
{
static void Main( string[] args )
{
Bob bob = new Bob();
Jill jill = new Jill();
Marko marko = new Marko();
for( int i = 0; i < 1000000; i++ )
{
Test( bob );
Test( jill );
Test( marko );
}
}
public static void Test( ChildNode childNode )
{
TestSwitch( childNode );
TestIfElse( childNode );
}
private static void TestIfElse( ChildNode childNode )
{
if( childNode is Bob ){}
else if( childNode is Jill ){}
else if( childNode is Marko ){}
}
private static void TestSwitch( ChildNode childNode )
{
switch( childNode.Name )
{
case "Bob":
break;
case "Jill":
break;
case "Marko":
break;
}
}
}
class ChildNode { public string Name { get; set; } }
class Bob : ChildNode { public Bob(){ this.Name = "Bob"; }}
class Jill : ChildNode{public Jill(){this.Name = "Jill";}}
class Marko : ChildNode{public Marko(){this.Name = "Marko";}}
Я думаю, что основная проблема производительности здесь, что в блоке переключателя, Вы сравниваете строки, и что в, если еще блок, Вы проверяете на типы... Те два не являются тем же, и поэтому, я сказал бы, что Вы "сравниваете картофель с бананами".
я запустил бы путем сравнения этого:
switch(childNode.Name)
{
case "Bob":
break;
case "Jill":
break;
case "Marko":
break;
}
if(childNode.Name == "Bob")
{}
else if(childNode.Name == "Jill")
{}
else if(childNode.Name == "Marko")
{}
Сравнение строк будет всегда полагаться полностью на среду выполнения (если строки не будут статически выделены, хотя потребность сравнить тех друг с другом спорна). Сравнение типов, однако, может быть сделано посредством динамического или статического связывания, и так или иначе это более эффективно для среды выполнения, чем сравнение отдельных символов в строке.
Переключатель () скомпилирует еще для кодирования эквивалентный ряду IFS. Сравнения строк будут намного медленнее, чем сравнения типов.