编译器如何优化密封类实现的虚方法

发布于 2024-07-17 14:58:17 字数 1047 浏览 6 评论 0原文

我想知道下面的代码是如何优化的。 特别是关于虚拟和直接呼叫。 我已经评论过我认为一切都是优化的,但这些只是猜测。

public abstract class Super
{
    public abstract void Foo();

    public void FooUser()
    {
        Foo();
    }
}

public class Child1 : Super
{
    public override void Foo()
    {
        //doSomething
    }
}

public class SealedChild : Super
{
    public override void Foo()
    {
        //doSomething
    }
}

class Program
{
    void main()
    {
        Child1 child1 = new Child1();
        child1.Foo(); //Virtual call?
        child1.FooUser(); //Direct call and then a virtual call. 

        SealedChild sealedChild = new SealedChild();
        sealedChild.Foo(); //Direct call?
        sealedChild.FooUser(); 
        /* Two options: either a direct call & then a virtual call
         * Or: direct call with a parameter that has a function pointer to Foo, and then a direct call to foo.
         */

        Super super = child1;
        super.Foo(); //Virtual call.
        super.FooUser(); //Virtual call then direct call.
    }
}

I'm wondering how the following code is optimized. Specifically concerning virtual and direct calls. I have commented on how I think everything is optimized but those are just guesses.

public abstract class Super
{
    public abstract void Foo();

    public void FooUser()
    {
        Foo();
    }
}

public class Child1 : Super
{
    public override void Foo()
    {
        //doSomething
    }
}

public class SealedChild : Super
{
    public override void Foo()
    {
        //doSomething
    }
}

class Program
{
    void main()
    {
        Child1 child1 = new Child1();
        child1.Foo(); //Virtual call?
        child1.FooUser(); //Direct call and then a virtual call. 

        SealedChild sealedChild = new SealedChild();
        sealedChild.Foo(); //Direct call?
        sealedChild.FooUser(); 
        /* Two options: either a direct call & then a virtual call
         * Or: direct call with a parameter that has a function pointer to Foo, and then a direct call to foo.
         */

        Super super = child1;
        super.Foo(); //Virtual call.
        super.FooUser(); //Virtual call then direct call.
    }
}

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(3

一枫情书 2024-07-24 14:58:17

如果密封类上有虚拟方法,并且对象引用的类型是密封类,则可以避免虚拟调用。 以下面的例子为例。 没有实际原因需要虚拟调用 GetName,因为我们知道 Parent 不能有子类,因此不需要进一步的虚拟调度。

sealed class Parent : Child  {
  public override string GetName() { return "foo"; }
}

public void Test() {
  var p = new Parent();
  var name = p.GetName();
}

编译器可以选择注意到这一点并输出 call IL 指令而不是 callvirt。 然而,C# 和 VB.Net 编译器都选择不执行此优化。 两者都会发出 callvirt。

JIT也可以自由地进行这样的优化。 它也选择不这样做。

但这并不意味着您不应该密封您的课程。 类应该是密封的,除非您确实打算让某人继承它们。 否则,您将面临无法准确计算成本的情况。

此外,没有什么可以阻止编译器和 JIT 在以后实现这一点。

In the case where you have a virtual method on a sealed class, and the type of the object reference is the sealed class the virtual call could be avoided. Take the following example. There is no actual reason that GetName needs to be called virtually because we know there can be no sub class of Parent and hence no further virtual dispatch.

sealed class Parent : Child  {
  public override string GetName() { return "foo"; }
}

public void Test() {
  var p = new Parent();
  var name = p.GetName();
}

A compiler could choose to notice this and output a call IL instruction instead of a callvirt. However both the C# and VB.Net Compiler choose not to perform this optimization. Both will emit callvirt.

The JIT is also free to make such an optimization. It also chooses not to.

That does not mean however that you should not seal your classes. Classes should be sealed unless you actually intend for someone to inherit from them. Otherwise you're opening yourself up to scenarios you have failed to accurately cost.

Additionally, there's nothing stopping the compilers and JIT from implementing this at a later date.

み零 2024-07-24 14:58:17

编译器根本不进行任何类型的优化。 它总是生成一个IL“callvirt”指令(调用虚拟)。 理论上,运行时可以删除调用的虚拟部分,但我见过和尝试过的每个基准测试都表明情况并非如此。 考虑到即使是完全静态的 C++ 编译器也无法在微不足道的情况下执行此操作,JIT 似乎不太可能实现这一目标。 即使他们能够让它发挥作用,他们也可以花时间解决大量更常见的性能缺陷。

哦,还有这个博客Eric Gunnerson 的帖子解释了为什么 C# 总是生成 callvirt。

The compiler doesn't do any type of optimization at all. It always generates an IL 'callvirt' instruction (call virtual). The runtime could in theory remove the virtual part of the call, but every benchmark I've seen and tried indicates this is not the case. Considering even fully static C++ compilers can't do this for trivial situations, it seems unlikely that the JIT is able to pull it off. And even if they could make it work, there's a vast range of much more common performance pitfalls they can spend their time on instead.

Oh, and this blog post by Eric Gunnerson explains why C# always generates callvirt.

海未深 2024-07-24 14:58:17

如果它被密封(也许编辑它?),当已知对象是 SealedChild 时,编译器或 JIT 可以发出非虚拟调用,从而保存间接寻址。 Java做到了这一点,C#好像没有做到; 我不知道 JIT 是做什么的。

If it were sealed (maybe edit it?), the compiler or the JIT could issue a non-virtual call when the object is known to be a SealedChild, saving an indirection. Java does this, C# seems not to do it; I don't know what the JIT does though.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文