我相信微软声称在处理引用类型时,泛型比使用普通多态更快。但是,以下简单测试(64位VS2012)将指示其他情况。我通常使用多态性使秒表时间快10%。我误解了结果吗?
public interface Base { Int64 Size { get; } }
public class Derived : Base { public Int64 Size { get { return 10; } } }
public class GenericProcessor<TT> where TT : Base
{
private Int64 sum;
public GenericProcessor(){ sum = 0; }
public void process(TT o){ sum += o.Size; }
public Int64 Sum { get { return sum; } }
}
public class PolymorphicProcessor
{
private Int64 sum;
public PolymorphicProcessor(){ sum = 0; }
public void process(Base o){ sum += o.Size; }
public Int64 Sum { get { return sum; } }
}
static void Main(string[] args)
{
var generic_processor = new GenericProcessor<Derived>();
var polymorphic_processor = new PolymorphicProcessor();
Stopwatch sw = new Stopwatch();
int N = 100000000;
var derived = new Derived();
sw.Start();
for (int i = 0; i < N; ++i) generic_processor.process(derived);
sw.Stop();
Console.WriteLine("Sum ="+generic_processor.Sum + " Generic performance = " + sw.ElapsedMilliseconds + " millisec");
sw.Restart();
sw.Start();
for (int i = 0; i < N; ++i) polymorphic_processor.process(derived);
sw.Stop();
Console.WriteLine("Sum ="+polymorphic_processor.Sum+ " Poly performance = " + sw.ElapsedMilliseconds + " millisec");
更令人惊讶(并且令人困惑)的是,如果我按如下方式向处理器的多态版本添加类型转换,则它的运行速度始终比通用版本快20%。
public void process(Base trade)
{
sum += ((Derived)trade).Size; // cast not needed - just an experiment
}
这里发生了什么?我理解泛型在处理原始类型时可以帮助避免昂贵的装箱和拆箱,但我在这里严格处理引用类型。
答案 0 :(得分:2)
使用Ctrl-F5(无调试器)在.NET 4.5 x64下执行测试。 N也增加了10倍。这样,无论测试的顺序如何,结果都可以可靠地重现。
对于ref类型的泛型,你仍然可以获得相同的vtable /接口查找,因为对于所有ref类型只有一个编译方法。 Derived
没有专业化。基于此,执行callvirt
的执行情况应该相同。
此外,泛型方法有一个隐藏方法参数typeof(T)
(因为这允许您在通用代码中实际编写typeof(T)
!)。这是额外的开销,解释了为什么通用版本更慢。
为什么演员表比接口电话更快?演员表只是一个指针比较和一个完美可预测的分支。在演员之后,对象的具体类型是已知的,允许更快的呼叫。
if (trade.GetType() != typeof(Derived)) throw;
Derived.Size(trade); //calling directly the concrete method, potentially inlining it
所有这些都是受过教育的猜测。通过查看反汇编来验证。
如果添加演员表,您将获得以下程序集:
我的装配技巧不足以完全解码。但是:
Size
已被列入内容。lea
实现了添加this.sum
同样的技巧适用于通用版本(((Derived)(Base)o).Size
)。
答案 1 :(得分:1)
我相信Servy是正确的,这是你的测试的问题。我颠倒了测试的顺序(只是预感):
internal class Program
{
public interface Base
{
Int64 Size { get; }
}
public class Derived : Base
{
public Int64 Size
{
get
{
return 10;
}
}
}
public class GenericProcessor<TT>
where TT : Base
{
private Int64 sum;
public GenericProcessor()
{
sum = 0;
}
public void process(TT o)
{
sum += o.Size;
}
public Int64 Sum
{
get
{
return sum;
}
}
}
public class PolymorphicProcessor
{
private Int64 sum;
public PolymorphicProcessor()
{
sum = 0;
}
public void process(Base o)
{
sum += o.Size;
}
public Int64 Sum
{
get
{
return sum;
}
}
}
private static void Main(string[] args)
{
var generic_processor = new GenericProcessor<Derived>();
var polymorphic_processor = new PolymorphicProcessor();
Stopwatch sw = new Stopwatch();
int N = 100000000;
var derived = new Derived();
sw.Start();
for (int i = 0; i < N; ++i) polymorphic_processor.process(derived);
sw.Stop();
Console.WriteLine(
"Sum =" + polymorphic_processor.Sum + " Poly performance = " + sw.ElapsedMilliseconds + " millisec");
sw.Restart();
sw.Start();
for (int i = 0; i < N; ++i) generic_processor.process(derived);
sw.Stop();
Console.WriteLine(
"Sum =" + generic_processor.Sum + " Generic performance = " + sw.ElapsedMilliseconds + " millisec");
Console.Read();
}
}
在这种情况下,我的测试中的多态性较慢。这表明第一次测试明显慢于第二次测试。它可能是第一次加载类,抢占,谁知道......
我只想指出,我并不认为仿制药更快或更快。我只是想证明这些类型的测试不会以某种方式形成一种情况。