Skip to content

Needless emit of callvirt for nullcheck when C# doesn't do it in corresponding code #2844

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
KevinRansom opened this issue Apr 13, 2017 · 5 comments
Labels
Area-Compiler-Optimization The F# optimizer, release code gen etc. Feature Request
Milestone

Comments

@KevinRansom
Copy link
Contributor

This link checks out the code for a relatively simple construct. F# does not necessarily do well.

Spreads/Spreads@b12b3be

@CyberQin
Copy link

CyberQin commented Apr 13, 2017

@KevinRansom No offence.
I just suggest MS giving u more resource.
What I list before is a copy of Spreads's author idea.
Spreads/Spreads#68

@dsyme dsyme changed the title Investigate code quality Needless emit of callvirt for nullcheck when C# doesn't do it in corresponding code Apr 13, 2017
@dsyme
Copy link
Contributor

dsyme commented Apr 13, 2017

@Huqin-China The main issue we will track with this bug is the emit of callvirt in cases where C# doesn't do it.

You can suppress this with --alwayscallvirt- as mentioned on twitter by @buybackoff . Some details are #468. But this is not ideal since it's rare that people even know about this flag (I'd forgotten about it). It's really perfectly safe to use this flag - because in practice the F# values in question are just never null.

I wonder if we should really go back to making call the default - @v2m convinced us we should be using callvirt to get more accurate exceptions in the cases where nulls incorrectly leak into F# coding - but I wonder if it's the wrong default having looked at this. I need to look at the specific test cases again that motivated this.

But what I also really want to know is why we are emitting callvirt at all in a situation where C# doesn't.

@buybackoff
Copy link
Contributor

@Huqin-China that post was mostly caused by a major frustration with .NET Core in project.json era and the situation is visibly improving, esp. with F# 4.1 and VS2017. Taking a pause did not hurt, but F# will stay there and is getting better. Remaining issues are either well known and being taken care of, or there is always a workaround with using a C# assembly for low-level imperative things, like in Spreads and Hopac, and this issue is from using that approach.

@KevinRansom Thanks for reacting to this so fast. I wanted to create an issue mentioning #1637 with some samples and additional benchmarks. In my case I have a collection that often calls small private methods (which suffer from callvirt) as well as large (but simple inlineable) private methods with AggressiveInlining attribute, which get a double hit. I have confirmed that calling instance methods of the same class in F# also uses callvirt instead of call, not only in a parent class method calls case. AggressiveInlining is also quite important and is often a free lunch for improving performance where JIT is too lazy, and that attribute has nothing to do with F# itself.

@dsyme --alwayscallvirt- is a very easy solution, but there is a compiler warning for that flag saying it is for experiments only. Plus with the comments in the issue #468 it doesn't sound like a good idea to apply it to a non-trivial assembly.

@dsyme
Copy link
Contributor

dsyme commented Apr 13, 2017

The case for using "callvirt" is explained here: http://v2matveev.blogspot.com/2010/11/null-stringgetenumerator.html

Here are some relevant snippets quoted then:

For more details please refer to the post by Eric Gunnerson Why does C# always use callvirt. or to Eric Lippert’s comment here: Summing up: Case (1) invoke virtual method: generate callvirt. Case (2) invoke instance method on nullable receiver: generate callvirt to get cheap null check -- yes, this is typesafe. Case (3) invoke instance method on known non-nullable receiver: generate call to avoid null check. Your first example falls into category (2), your second example falls into category (3). (The compiler knows that new never returns null and therefore need not check again.).

@buybackoff
Copy link
Contributor

Now F# compiler doesn't use the knowledge for case 3 (calling this. and base. methods). I have noticed that F# compiler inlines small methods at IL level more aggressively than C# (which probably relies more on JIT for that, not sure if C# ever inlines methods in IL), so the issue doesn't affect properties and small methods in most cases.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Area-Compiler-Optimization The F# optimizer, release code gen etc. Feature Request
Projects
Archived in project
Development

No branches or pull requests

5 participants