.net 密封类真的提供性能优势吗?
我遇到了很多优化技巧,它们说你应该将你的类标记为密封的,以获得额外的性能优势 我运行了一些测试来检查性能差异,但没有发现任何差异。我做错什么了吗?我是否错过了密封类将提供更好结果的情况 有人运行测试并看到了差异吗.net 密封类真的提供性能优势吗?,.net,optimization,frameworks,performance,.net,Optimization,Frameworks,Performance,我遇到了很多优化技巧,它们说你应该将你的类标记为密封的,以获得额外的性能优势 我运行了一些测试来检查性能差异,但没有发现任何差异。我做错什么了吗?我是否错过了密封类将提供更好结果的情况 有人运行测试并看到了差异吗 帮助我了解:)抖动有时会对密封类中的方法使用非虚拟调用,因为它们无法进一步扩展 关于调用类型、虚拟/非虚拟,有很多复杂的规则,我不知道所有这些规则,所以我无法为您概括这些规则,但是如果您搜索密封类和虚拟方法,您可能会找到一些关于这个主题的文章 请注意,从这一级别的优化中获得的任何性能优
帮助我了解:)抖动有时会对密封类中的方法使用非虚拟调用,因为它们无法进一步扩展 关于调用类型、虚拟/非虚拟,有很多复杂的规则,我不知道所有这些规则,所以我无法为您概括这些规则,但是如果您搜索密封类和虚拟方法,您可能会找到一些关于这个主题的文章 请注意,从这一级别的优化中获得的任何性能优势都应视为最后手段,在代码级别优化之前,始终在算法级别进行优化 这里有一个链接提到这一点:密封类应该提供性能改进。由于无法派生密封类,因此任何虚拟成员都可以转换为非虚拟成员 当然,我们谈论的是非常小的收益。我不会为了提高性能而将一个类标记为密封的,除非分析表明它是一个问题。 我讨厌封闭的课堂。即使性能优势令人震惊(我对此表示怀疑),它们也会通过阻止通过继承进行重用而破坏面向对象模型。例如,Thread类是密封的。虽然我可以看到人们可能希望线程尽可能地高效,但我也可以想象这样的场景:能够对线程进行子类化将带来巨大的好处。 类作者,如果您出于“性能”原因必须密封类,请至少提供一个接口,这样我们就不必包装和替换您忘记的功能 示例:必须包装Thread类,因为Thread是密封的,并且没有IThread接口;SafeThread会自动在线程上捕获未处理的异常,Thread类中完全缺少这一点。[否,未处理的异常事件不会在次线程中拾取未处理的异常]
答案是否定的,密封类的性能并不比非密封类好 问题归结为呼叫与呼叫之间的冲突
Call
比callvirt
快,而且callvirt
主要用于不知道对象是否已被子类化的情况。因此人们认为,如果你密封一个类,所有的操作码都会从calvirts
变为calls
,并且会更快
不幸的是,callvirt
还做了其他一些事情,比如检查空引用。这意味着,即使类被密封,引用也可能仍然为null,因此需要callvirt
。您可以绕过这个问题(无需密封类),但它变得有点毫无意义
结构使用调用
,因为它们不能被子类化,并且从不为null
有关更多信息,请参见此问题:
将类标记为密封的应该不会对性能产生影响 在某些情况下,
csc
可能必须发出callvirt
操作码,而不是call
操作码。然而,这些病例似乎很少见
在我看来,JIT应该能够为callvirt
发出与call
相同的非虚拟函数调用,如果它知道类没有任何子类的话。如果只存在该方法的一个实现,那么从vtable加载其地址没有意义,只需直接调用该实现即可。因此,JIT甚至可以内联函数
这对JIT来说有点冒险,因为如果以后加载子类,JIT将不得不扔掉该机器代码并再次编译代码,发出真正的虚拟调用。我猜这在实践中并不经常发生
(是的,虚拟机设计人员确实积极追求这些微小的性能胜利。)运行此代码,您将看到密封类的速度提高了2倍:
class Program
{
static void Main(string[] args)
{
Console.ReadLine();
var watch = new Stopwatch();
watch.Start();
for (int i = 0; i < 10000000; i++)
{
new SealedClass().GetName();
}
watch.Stop();
Console.WriteLine("Sealed class : {0}", watch.Elapsed.ToString());
watch.Start();
for (int i = 0; i < 10000000; i++)
{
new NonSealedClass().GetName();
}
watch.Stop();
Console.WriteLine("NonSealed class : {0}", watch.Elapsed.ToString());
Console.ReadKey();
}
}
sealed class SealedClass
{
public string GetName()
{
return "SealedClass";
}
}
class NonSealedClass
{
public string GetName()
{
return "NonSealedClass";
}
}
类程序
{
静态void Main(字符串[]参数)
{
Console.ReadLine();
var watch=新秒表();
watch.Start();
对于(int i=0;i<10000000;i++)
{
新建SealedClass().GetName();
}
看,停;
WriteLine(“密封类:{0}”,watch.appeased.ToString());
watch.Start();
对于(int i=0;i<10000000;i++)
{
新的非密封类().GetName();
}
看,停;
WriteLine(“非密封类:{0}”,watch.appeased.ToString());
Console.ReadKey();
}
}
密封等级密封等级
{
公共字符串GetName()
{
返回“SealedClass”;
}
}
类非密封类
{
公共字符串GetName()
{
返回“非密封类”;
}
}
输出:
密封等级:00:00:00.1897568
非密封类:00:00:38 26268 < /P> < P>我认为“密封”类是正常情况,我总是有理由省略“密封”关键字。
对我来说,最重要的原因是:
a) 更好的编译时检查(转换到未实现的接口将在编译时检测,而不仅仅是在运行时)
最主要的原因是:
b) 这样就不可能滥用我的课程
我希望微软能将标准“密封”,而不是“未密封”。正如我所知,性能优势无法保证。但是,在某些特定条件下,使用密封方法有机会降低性能惩罚。(sealed类使所有方法都被密封。) 但这取决于编译器实现和exec
// Value of `v` is unknown,
// and can be resolved only at runtime.
// CPU cannot know which code to prefetch.
// Therefore, just prefetch any one of a() or b().
// This is *speculative execution*.
int v = random();
if (v==1) a();
else b();
public class NormalClass {
public void WriteIt(string x) {
Console.WriteLine("NormalClass");
Console.WriteLine(x);
}
}
public sealed class SealedClass {
public void WriteIt(string x) {
Console.WriteLine("SealedClass");
Console.WriteLine(x);
}
}
public static void CallNormal() {
var n = new NormalClass();
n.WriteIt("a string");
}
public static void CallSealed() {
var n = new SealedClass();
n.WriteIt("a string");
}
L_0000: newobj instance void <NormalClass or SealedClass>::.ctor()
L_0005: stloc.0
L_0006: ldloc.0
L_0007: ldstr "a string"
L_000c: callvirt instance void <NormalClass or SealedClass>::WriteIt(string)
L_0011: ret
// var n = new NormalClass();
00000000 push ebp
00000001 mov ebp,esp
00000003 sub esp,8
00000006 cmp dword ptr ds:[00585314h],0
0000000d je 00000014
0000000f call 70032C33
00000014 xor edx,edx
00000016 mov dword ptr [ebp-4],edx
00000019 mov ecx,588230h
0000001e call FFEEEBC0
00000023 mov dword ptr [ebp-8],eax
00000026 mov ecx,dword ptr [ebp-8]
00000029 call dword ptr ds:[00588260h]
0000002f mov eax,dword ptr [ebp-8]
00000032 mov dword ptr [ebp-4],eax
// n.WriteIt("a string");
00000035 mov edx,dword ptr ds:[033220DCh]
0000003b mov ecx,dword ptr [ebp-4]
0000003e cmp dword ptr [ecx],ecx
00000040 call dword ptr ds:[0058827Ch]
// }
00000046 nop
00000047 mov esp,ebp
00000049 pop ebp
0000004a ret
Normal JIT generated code
Begin 003c00b0, size 39
003c00b0 55 push ebp
003c00b1 8bec mov ebp,esp
003c00b3 b994391800 mov ecx,183994h (MT: ScratchConsoleApplicationFX4.NormalClass)
003c00b8 e8631fdbff call 00172020 (JitHelp: CORINFO_HELP_NEWSFAST)
003c00bd e80e70106f call mscorlib_ni+0x2570d0 (6f4c70d0) (System.Console.get_Out(), mdToken: 060008fd)
003c00c2 8bc8 mov ecx,eax
003c00c4 8b1530203003 mov edx,dword ptr ds:[3302030h] ("NormalClass")
003c00ca 8b01 mov eax,dword ptr [ecx]
003c00cc 8b403c mov eax,dword ptr [eax+3Ch]
003c00cf ff5010 call dword ptr [eax+10h]
003c00d2 e8f96f106f call mscorlib_ni+0x2570d0 (6f4c70d0) (System.Console.get_Out(), mdToken: 060008fd)
003c00d7 8bc8 mov ecx,eax
003c00d9 8b1534203003 mov edx,dword ptr ds:[3302034h] ("a string")
003c00df 8b01 mov eax,dword ptr [ecx]
003c00e1 8b403c mov eax,dword ptr [eax+3Ch]
003c00e4 ff5010 call dword ptr [eax+10h]
003c00e7 5d pop ebp
003c00e8 c3 ret
Normal JIT generated code
Begin 003c0100, size 39
003c0100 55 push ebp
003c0101 8bec mov ebp,esp
003c0103 b90c3a1800 mov ecx,183A0Ch (MT: ScratchConsoleApplicationFX4.SealedClass)
003c0108 e8131fdbff call 00172020 (JitHelp: CORINFO_HELP_NEWSFAST)
003c010d e8be6f106f call mscorlib_ni+0x2570d0 (6f4c70d0) (System.Console.get_Out(), mdToken: 060008fd)
003c0112 8bc8 mov ecx,eax
003c0114 8b1538203003 mov edx,dword ptr ds:[3302038h] ("SealedClass")
003c011a 8b01 mov eax,dword ptr [ecx]
003c011c 8b403c mov eax,dword ptr [eax+3Ch]
003c011f ff5010 call dword ptr [eax+10h]
003c0122 e8a96f106f call mscorlib_ni+0x2570d0 (6f4c70d0) (System.Console.get_Out(), mdToken: 060008fd)
003c0127 8bc8 mov ecx,eax
003c0129 8b1534203003 mov edx,dword ptr ds:[3302034h] ("a string")
003c012f 8b01 mov eax,dword ptr [ecx]
003c0131 8b403c mov eax,dword ptr [eax+3Ch]
003c0134 ff5010 call dword ptr [eax+10h]
003c0137 5d pop ebp
003c0138 c3 ret
public sealed class Sealed
{
public string Message { get; set; }
public void DoStuff() { }
}
public class Derived : Base
{
public sealed override void DoStuff() { }
}
public class Base
{
public string Message { get; set; }
public virtual void DoStuff() { }
}
static void Main()
{
Sealed sealedClass = new Sealed();
sealedClass.DoStuff();
Derived derivedClass = new Derived();
derivedClass.DoStuff();
Base BaseClass = new Base();
BaseClass.DoStuff();
}
.method private hidebysig static void Main() cil managed
{
.entrypoint
// Code size 41 (0x29)
.maxstack 8
IL_0000: newobj instance void ConsoleApp1.Program/Sealed::.ctor()
IL_0005: callvirt instance void ConsoleApp1.Program/Sealed::DoStuff()
IL_000a: newobj instance void ConsoleApp1.Program/Derived::.ctor()
IL_000f: callvirt instance void ConsoleApp1.Program/Base::DoStuff()
IL_0014: newobj instance void ConsoleApp1.Program/Base::.ctor()
IL_0019: callvirt instance void ConsoleApp1.Program/Base::DoStuff()
IL_0028: ret
} // end of method Program::Main
--- C:\Users\Ivan Porta\source\repos\ConsoleApp1\Program.cs --------------------
{
0066084A in al,dx
0066084B push edi
0066084C push esi
0066084D push ebx
0066084E sub esp,4Ch
00660851 lea edi,[ebp-58h]
00660854 mov ecx,13h
00660859 xor eax,eax
0066085B rep stos dword ptr es:[edi]
0066085D cmp dword ptr ds:[5842F0h],0
00660864 je 0066086B
00660866 call 744CFAD0
0066086B xor edx,edx
0066086D mov dword ptr [ebp-3Ch],edx
00660870 xor edx,edx
00660872 mov dword ptr [ebp-48h],edx
00660875 xor edx,edx
00660877 mov dword ptr [ebp-44h],edx
0066087A xor edx,edx
0066087C mov dword ptr [ebp-40h],edx
0066087F nop
Sealed sealedClass = new Sealed();
00660880 mov ecx,584E1Ch
00660885 call 005730F4
0066088A mov dword ptr [ebp-4Ch],eax
0066088D mov ecx,dword ptr [ebp-4Ch]
00660890 call 00660468
00660895 mov eax,dword ptr [ebp-4Ch]
00660898 mov dword ptr [ebp-3Ch],eax
sealedClass.DoStuff();
0066089B mov ecx,dword ptr [ebp-3Ch]
0066089E cmp dword ptr [ecx],ecx
006608A0 call 00660460
006608A5 nop
Derived derivedClass = new Derived();
006608A6 mov ecx,584F3Ch
006608AB call 005730F4
006608B0 mov dword ptr [ebp-50h],eax
006608B3 mov ecx,dword ptr [ebp-50h]
006608B6 call 006604A8
006608BB mov eax,dword ptr [ebp-50h]
006608BE mov dword ptr [ebp-40h],eax
derivedClass.DoStuff();
006608C1 mov ecx,dword ptr [ebp-40h]
006608C4 mov eax,dword ptr [ecx]
006608C6 mov eax,dword ptr [eax+28h]
006608C9 call dword ptr [eax+10h]
006608CC nop
Base BaseClass = new Base();
006608CD mov ecx,584EC0h
006608D2 call 005730F4
006608D7 mov dword ptr [ebp-54h],eax
006608DA mov ecx,dword ptr [ebp-54h]
006608DD call 00660490
006608E2 mov eax,dword ptr [ebp-54h]
006608E5 mov dword ptr [ebp-44h],eax
BaseClass.DoStuff();
006608E8 mov ecx,dword ptr [ebp-44h]
006608EB mov eax,dword ptr [ecx]
006608ED mov eax,dword ptr [eax+28h]
006608F0 call dword ptr [eax+10h]
006608F3 nop
}
0066091A nop
0066091B lea esp,[ebp-0Ch]
0066091E pop ebx
0066091F pop esi
00660920 pop edi
00660921 pop ebp
00660922 ret