我知道IComparable和IComparable<T>在一般情况下有很大的区别,参见this,但是在这个搜索方法中,它不会有任何不同,或者会吗?
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>
{
...
}这两种方法都能起作用,结果也是一样的吗?
发布于 2015-12-12 19:08:34
这两种方法都能起作用,结果也是一样的吗?
让我们看看编译器为这两种搜索方法发出了什么:
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操作中是值得注意的),因为非泛型版本接受object类型。
让我们尝试使用https://msdn.microsoft.com/en-us/library/s1ax56ch.aspx来分析两者之间的性能差异。我将使用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 *****请注意,导致装箱操作的非泛型方法是,x86上的慢速超过4倍,而x64上的慢速超过8倍。这可能会对应用程序的性能产生影响。
我通常不会使用非泛型IComparable,它主要是为了向后兼容泛型之前的几天。请注意,另一个同样重要的因素是使用泛型获得的类型安全性。
发布于 2015-12-12 17:26:01
这是一个巨大的区别,第一种方法是无缘无故地装箱值类型。
发布于 2015-12-12 17:26:47
第一个(i.e. IComparable)实现的不是泛型类型,与父类使用的类型无关,但是第二个类型(即IComparable<T>)是类型安全的,您只能使用为父类指定的类型。
https://stackoverflow.com/questions/34242746
复制相似问题