22

For example, I know it is defined for gcc and used in the Linux kernel as:

#define likely(x)       __builtin_expect((x),1)
#define unlikely(x)     __builtin_expect((x),0)

If nothing like this is possible in C#, is the best alternative to manually reorder if-statements, putting the most likely case first? Are there any other ways to optimize based on this type of external knowledge?

On a related note, the CLR knows how to identify guard clauses and assumes that the alternate branch will be taken, making this optimization inappropriate to use on guard clases, correct?

(Note that I realize this may be a micro-optimization; I'm only interested for academic purposes.)

ceyko
  • 4,822
  • 1
  • 18
  • 23
  • 5
    How would you do that, seeing as C# compiles to IL, a bytecode that can end up being then compiled to any native CPU? – Oded Jan 15 '12 at 22:34
  • The answer is "no,it is not possible." The rationale for this impossibility is indicated by Oded. – Heath Hunnicutt Jan 15 '12 at 22:35
  • I suppose I was hoping it was possible for it to be done in IL, since the IL is compiled down to native code, where an instruction for hinting could be used. Or, any other similar technique, even if not at the processor level would be of interest (though I'm not sure this is possible). – ceyko Jan 15 '12 at 23:02
  • 2
    @Oded, Heath: That doesn't make it impossible. MSIL carries all kinds of metadata, describing types of local variables, exception try/catch blocks, and so forth. Having metadata for branch prediction would have been possible, if the .NET designer had included an encoding for it. – Ben Voigt Jan 15 '12 at 23:09
  • @BenVoigt - True, however, there is no such encoding, so practically speaking, this is not currently possible. – Oded Jan 15 '12 at 23:15
  • Are these hints even taken into account by modern CPUs? I mean we're so far beyond static branch prediction these days on modern x86 CPUs that I'm not even sure how these hints would really help the processor. – Voo Jan 16 '12 at 01:01

1 Answers1

29

Short answer: No.

Longer Answer: You don't really need to in most cases. You can give hints by changing the logic in your statements. This is easier to do with a performance tool, like the one built into the higher (and more expensive) versions of Visual Studio, since you can capture the mispredicted branches counter. I realize this is for academic purposes, but it's good to know that the JITer is very good at optimizing your code for you. As an example (taken pretty much verbatim from CLR via C#)

This code:

public static void Main() {
    Int32[] a = new Int32[5];
    for(Int32 index = 0; index < a.Length; index++) {
        // Do something with a[index]
    }
}

may seem to be inefficient, since a.Length is a property and as we know in C#, a property is actually a set of one or two methods (get_XXX and set_XXX). However, the JIT knows that it's a property and either stores the length in a local variable for you, or inlines the method, to prevent the overhead.

...some developers have underestimated the abilities of the JIT compiler and have tried to write “clever code” in an attempt to help the JIT compiler. However, any clever attempts that you come up with will almost certainly impact performance negatively and make your code harder to read, reducing its maintainability.

Among other things, it actually goes further and does the bounds checking once outside of the loop instead of inside the loop, which would degrade performance.

I realize it has little to do directly with your question, but I guess the point that I'm trying to make is that micro-optimizations like this don't really help you much in C#, because the JIT generally does it better, as it was designed exactly for this. (Fun fact, the x86 JIT compiler performs more aggressive optimizations than the x64 counterpart)

This article explains some of the optimizations that were added in .NET 3.5 SP1, among them being improvements to straightening branches to improve prediction and cache locality.

All of that being said, if you want to read a great book that goes into what the compiler generates and performance of the CLR, I recommend the book that I quoted from above, CLR via C#.

EDIT: I should mention that if this were currently possible in .NET, you could find the information in either the EMCA-335 standard or working draft. There is no standard that supports this, and viewing the metadata in something like IlDasm or CFF Explorer show no signs of any special metadata that can hint at branch predictions.

Christopher Currens
  • 29,917
  • 5
  • 57
  • 77
  • Although that's disappointing, the info about JITing is reassuring. Thanks for the tips, I will definitely be checking out that book since I'm not too familiar with the CLR, but have been very interested in what's actually happening "behind the scenes" lately. – ceyko Jan 16 '12 at 01:45
  • 1
    While I enjoyed much of that book, I dislike the quote you give. It's factually accurate, but I dislike the tone about "clever code", especially considering people are just as disdainful when someone wonders why a given optimisation isn't done. We can imagine a magically perfect jitter that always produced the most efficient code possible, and more easily imagine a horribly naïve one that never optimised anything, and we know the truth is in-between. Not knowing just where that point in-between is, people are left trying these things at hot-spots and seeing if they work. – Jon Hanna Jan 22 '12 at 22:23
  • @JonHanna - I think I agree with you to a large extent. I don't think that developer's should ignore what the JITer is doing; it's a bit unfortunate that we don't actually know exactly what is getting optimized. I know it's not perfect. I also know it's not terrible. You said it succinctly, though, we simply don't know the extent to which it optimized. The only way to know (for a single version of the framework), would be to look at what it's doing it as it's being done, using something like Windbg. That's easier said than done, though. – Christopher Currens Jan 23 '12 at 18:00
  • On the other hand, if we go too far with what we do know we can end up over-optimising for the case in front of us (particular framework, machine, network speed, etc) to the detriment of the overall quality. It's an imperfect balance to neither depend too much or too little on what are implementation details (whether arrived at by deduction or profiling), no matter how skilled or knowledgeable the person doing it is. – Jon Hanna Jan 23 '12 at 18:23
  • "(`get_Length` and `set_Length` in this case)". Nope, only `get_Length` in this case. If you have some time, please fix. – Sergey.quixoticaxis.Ivanov Apr 10 '18 at 18:37