2015-12-12 1 views
4

Я знаю, что есть большая разница между IComparable и IComparable<T> в целом, см this, но в этом методе поиска не будет никакой разницы, или это будет?разница между IComparable и IComparable <T> в поисковом методе

public static int Search<T>(List<T> a, T target) where T : IComparable 
{ 
    for (int i = 0; i < a.Count; i++) 
    { 
     if (target.CompareTo(a[i]) == 0) 
      return i; 
    } 
    return -1; 
} 

по сравнению с:

public static int Search<T>(List<T> a, T target) where T : IComparable<T> 
{ 
    ... 
} 

Оба будут работать и дают тот же результат, или нет?

ответ

8

Оба будут работать и давать одинаковый результат, или нет?

Давайте посмотрим на то, что выделяет компилятор для обоих методов поиска:

Search: 
IL_0000: ldc.i4.0  
IL_0001: stloc.0  // i 
IL_0002: br.s  IL_0025 
IL_0004: ldarga.s 01 
IL_0006: ldarg.0  
IL_0007: ldloc.0  // i 
IL_0008: callvirt 06 00 00 0A 
IL_000D: box   03 00 00 1B <---- Notice this! 
IL_0012: constrained. 03 00 00 1B 
IL_0018: callvirt System.IComparable.CompareTo 
IL_001D: brtrue.s IL_0021 
IL_001F: ldloc.0  // i 
IL_0020: ret   
IL_0021: ldloc.0  // i 
IL_0022: ldc.i4.1  
IL_0023: add   
IL_0024: stloc.0  // i 
IL_0025: ldloc.0  // i 
IL_0026: ldarg.0  
IL_0027: callvirt 08 00 00 0A 
IL_002C: blt.s  IL_0004 
IL_002E: ldc.i4.m1 
IL_002F: ret   

SearchGeneric: 
IL_0000: ldc.i4.0  
IL_0001: stloc.0  // i 
IL_0002: br.s  IL_0020 
IL_0004: ldarga.s 01 
IL_0006: ldarg.0  
IL_0007: ldloc.0  // i 
IL_0008: callvirt 06 00 00 0A 
IL_000D: constrained. 03 00 00 1B 
IL_0013: callvirt 09 00 00 0A 
IL_0018: brtrue.s IL_001C 
IL_001A: ldloc.0  // i 
IL_001B: ret   
IL_001C: ldloc.0  // i 
IL_001D: ldc.i4.1  
IL_001E: add   
IL_001F: stloc.0  // i 
IL_0020: ldloc.0  // i 
IL_0021: ldarg.0  
IL_0022: callvirt 08 00 00 0A 
IL_0027: blt.s  IL_0004 
IL_0029: ldc.i4.m1 
IL_002A: ret  

Если вы посмотрите внимательно, вы увидите, что главное различие заключается в том, что каждый вызов в Search, до вызова CompareTo имеет значение box значение (это примечание с операцией box), так как нестандартная версия принимает тип object.

Давайте попробуем проанализировать различия в производительности между ними с помощью value type. Я собираюсь использовать BenchmarkDotNet, которая немного (и действительно удивительным) бенчмаркинг рамки, которые берет на себя JITing, разминка CPU, и т.д.

Тест:

[BenchmarkTask(platform: BenchmarkPlatform.X86)] 
[BenchmarkTask(platform: BenchmarkPlatform.X64)] 
public class Test 
{ 
    private readonly List<int> list = Enumerable.Range(0, 1000000).ToList(); 

    [Benchmark] 
    public void TestSearch() 
    { 
     Search(list, 999999); 
    } 

    [Benchmark] 
    public void TestSearchGeneric() 
    { 
     SearchGeneric(list, 999999); 
    } 

    public static int Search<T>(List<T> a, T target) where T : IComparable 
    { 
     for (int i = 0; i < a.Count; i++) 
     { 
      if (target.CompareTo(a[i]) == 0) 
       return i; 
     } 
     return -1; 
    } 
    public static int SearchGeneric<T>(List<T> a, T target) where T : IComparable<T> 
    { 
     for (int i = 0; i < a.Count; i++) 
     { 
      if (target.CompareTo(a[i]) == 0) 
       return i; 
     } 
     return -1; 
    } 
} 

Результаты:

***** Competition: Finish ***** 

BenchmarkDotNet=v0.7.8.0 
OS=Microsoft Windows NT 6.1.7601 Service Pack 1 
Processor=Intel(R) Core(TM) i7-4600U CPU @ 2.10GHz, ProcessorCount=4 
HostCLR=MS.NET 4.0.30319.42000, Arch=32-bit 
Type=Test Mode=Throughput Jit=HostJit .NET=HostFramework 


      Method | Platform | AvrTime | StdDev | op/s | 
------------------ |--------- |----------- |---------- |------- | 
     TestSearch |  X64 | 35.8065 ms | 3.3439 ms | 27.93 | 
TestSearchGeneric |  X64 | 4.6427 ms | 0.3075 ms | 215.40 | 
     TestSearch |  X86 | 26.4876 ms | 1.4776 ms | 37.75 | 
TestSearchGeneric |  X86 | 6.6500 ms | 0.1664 ms | 150.38 | 

***** Competition: End ***** 

Смотрите, не-шаблонный метод подвергаясь операции бокс является более 4x медленнее на x86 и более 8x медленнее на x64. Это может повлиять на производительность ваших приложений.

Я обычно не использовал бы не общий IComparable, который в основном предназначен для обратной совместимости с деньгами до дженериков. Обратите внимание, что еще одним не менее важным фактором является безопасность типов, которую вы получаете с помощью генериков.

+2

Очень подробный ответ, спасибо. –

3

Это огромная разница, первый метод - это типы значений бокса без причины.

+0

Хорошо, это означает, что разница в производительности? –

+0

@DieterMeemken, только для типов значений –

+0

@DieterMeemken, а не только для типов значений, с помощью 'IEnumerable' также приведет к отбрасыванию, чтобы получить нужные объекты. Это еще одно нарушение производительности. – Blindy

2

Первый (i.e. IComparable) реализовать не общего типа и не связан с типом используемого для родительского класса, но второй (т.е. IComparable<T>) типобезопасен, где вы можете использовать только тот тип, который вы указали для родительского класса ,

+0

Но сравнение будет работать одинаково, или нет? –

2

Самая большая разница должна быть очевидной: версия where T : IComparable может использоваться с типами, которые реализуют IComparable. Версия where T : IComparable<T> может использоваться с типами, которые реализуют IComparable<T>. Обычно используемые типы, которые реализуют IComparable, но не IComparable<T>, являются различными типами System.Tuple<...>, а также всеми типами перечисления.

+0

Итак, есть только разница в возможных типах, которые я могу использовать для T? –

+0

Функционально, да. Там нет правила, говорящего, что тип, который реализует «IComparable», также должен реализовывать «IComparable », или наоборот. Разумеется, также может быть разница, о чем указывает Blindy, но это должно быть вторичной проблемой: если версия 'IComparable ' будет быстрее, за исключением того, что вы не можете ее назвать, это не очень полезно для вы. – hvd