Skip to content

Why is alwayscallvirt the default? #468

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
latkin opened this issue May 22, 2015 · 8 comments
Closed

Why is alwayscallvirt the default? #468

latkin opened this issue May 22, 2015 · 8 comments

Comments

@latkin
Copy link
Contributor

latkin commented May 22, 2015

By default, the F# compiler always uses the callvirt instruction, even when the faster call instruction is safe/appropriate. Default is set in build.fs.

You can override this with the undocumented --alwayscallvirt- flag.

Why is this the default? Should we consider changing it?

@latkin latkin added the Bug label May 22, 2015
@latkin latkin changed the title Base class ctors invoked with 'callvirt', not 'call' Why is alwayscallvirt the default? May 22, 2015
@latkin latkin added discussion and removed Bug labels May 22, 2015
@latkin
Copy link
Contributor Author

latkin commented May 22, 2015

Edit: Found root cause of why base class constructors use callvirt instead of call. Changed this issue to discussion of why this is the default setting.

@vladima
Copy link
Contributor

vladima commented May 22, 2015

I guess this is my fault :) Back in 2010 I've submitted this issue and AFAIR we've adopted callvirt as default in F# 3.0

@veikkoeeva
Copy link

Could the reason be the same as in C#, that is NullReferenceException sooner and then a better story with no need to recompile referenced assemblies. I don't think it adds more than a check for null reference, so there unlikely is a noticeable performance penalty. Though looking from the C++ perspective, the native compilers do a lot of work to remove virtual tables and related machinery, so I'm not sure about that.

An article going into detail of this looks like being .NET Type Internals - From a Microsoft CLR Perspective. Interesting blog on observing null this is Jarepar's Observing a null this value.

<edit: Plus the blog by @vladima. :)

@latkin
Copy link
Contributor Author

latkin commented May 22, 2015

@veikkoeeva The C# compiler does use call when it is safe/appropriate - one example being the invocation of base class constructors. The F# compiler uses callvirt even in these situations.

@latkin
Copy link
Contributor Author

latkin commented May 22, 2015

Oh I see, --alwayscallvirt- is really more like --alwayscall+ 😄 It goes too far and causes call to be used even when it "shouldn't" (i.e. when null-check is desired).

So a proper fix would require reviewing everywhere in the code generator that currently specifies a call, and determine if it should really be a callvirt...

@vladima
Copy link
Contributor

vladima commented May 22, 2015

@latkin yes, that is correct

@zpodlovics
Copy link

The performance difference could be huge between the direct and indirect calls. I have (re)created a small benchmark [1] in F# to measure the call overhead in different cases. The benchmark is far from perfect, but it will provide some valuable information.

https://gist.github.com/zpodlovics/73deb8b7a06d7d5ec374

Some benchmark result:

Windows Vista, .NET 4.0.30319.34209, Intel Core2 7200 (2GHz)

Method call overhead:
No function                             1,838.2 MOps/s,   0.272 s
F# function                             1,754.4 MOps/s,   0.285 s
F# inline function                      1,805.1 MOps/s,   0.277 s
System.Func<int,int> function             148.5 MOps/s,   3.368 s
Delegate function                         141.0 MOps/s,   3.546 s
ClassA Static                           1,976.3 MOps/s,   0.253 s
ClassA plain                            1,976.3 MOps/s,   0.253 s
ClassA Interface call base plain          103.7 MOps/s,   4.821 s
ClassA Interface call base abstract        62.3 MOps/s,   8.029 s
ClassA Interface local impl                93.0 MOps/s,   5.376 s
ClassB Static                           1,018.3 MOps/s,   0.491 s
ClassB plain                            1,182.0 MOps/s,   0.423 s
ClassB overridden                         126.3 MOps/s,   3.960 s
ClassB ClassA cast plain                1,792.1 MOps/s,   0.279 s
ClassB ClassA cast overridden             193.5 MOps/s,   2.584 s
ClassB Interface plain                    135.9 MOps/s,   3.679 s
ClassB Interface overridden               108.2 MOps/s,   4.623 s
ClassB Interface local                    109.9 MOps/s,   4.548 s
StructC Static                          1,087.0 MOps/s,   0.460 s
StructC plain                             980.4 MOps/s,   0.510 s
StructC Interface call base plain          48.8 MOps/s,  10.241 s
StructC Interface local                    54.3 MOps/s,   9.207 s
X: 0

Ubuntu 14.04, Mono 4.0, AMD Kaveri 7850 (3.7GHz)

Method call overhead:
No function                             1,901.1 MOps/s,   0.263 s
F# function                             1,865.7 MOps/s,   0.268 s
F# inline function                      2,336.4 MOps/s,   0.214 s
System.Func<int,int> function             326.6 MOps/s,   1.531 s
Delegate function                         293.4 MOps/s,   1.704 s
ClassA Static                           2,145.9 MOps/s,   0.233 s
ClassA plain                            2,347.4 MOps/s,   0.213 s
ClassA Interface call base plain          290.5 MOps/s,   1.721 s
ClassA Interface call base abstract       276.5 MOps/s,   1.808 s
ClassA Interface local impl               550.7 MOps/s,   0.908 s
ClassB Static                           1,466.3 MOps/s,   0.341 s
ClassB plain                            2,347.4 MOps/s,   0.213 s
ClassB overridden                         477.1 MOps/s,   1.048 s
ClassB ClassA cast plain                2,145.9 MOps/s,   0.233 s
ClassB ClassA cast overridden             524.7 MOps/s,   0.953 s
ClassB Interface plain                    334.9 MOps/s,   1.493 s
ClassB Interface overridden               274.3 MOps/s,   1.823 s
ClassB Interface local                    612.7 MOps/s,   0.816 s
StructC Static                          2,358.5 MOps/s,   0.212 s
StructC plain                           2,155.2 MOps/s,   0.232 s
StructC Interface call base plain         205.6 MOps/s,   2.432 s
StructC Interface local                   210.7 MOps/s,   2.373 s
X: 0

[1] https://programmers.stackexchange.com/questions/234473/how-are-virtual-methods-slower-in-c

@latkin
Copy link
Contributor Author

latkin commented Aug 4, 2015

My question was answered, so I'll close this out.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

4 participants