Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Is a struct wrapping a primitive value type a zero cost abstraction in C#?

Sometimes I want to add more typesafety around raw doubles. One idea that comes up a lot would be adding unit information with the types. For example,

struct AngleRadians {
  public readonly double Value;
  /* Constructor, casting operator to AngleDegrees, etc omitted for brevity... */
}

In the case like above, where there is only a single field, will the JIT be able to optimize away this abstraction in all cases? What situations, if any, will result in extra generated machine instructions compared to similar code using an unwrapped double?

Any mention of premature optimization will be downvoted. I'm interested in knowing the ground truth.

Edit: To narrow the scope of the question, here are a couple of scenarios of particular interest...

// 1. Is the value-copy constructor zero cost?
// Is...
var angleRadians = new AngleRadians(myDouble);
// The same as...
var myDouble2 = myDouble;

// 2. Is field access zero cost?
// Is...
var myDouble2 = angleRadians.Value;
// The same as...
var myDouble2 = myDouble;

// 3. Is function passing zero cost?
// Is calling...
static void DoNaught(AngleRadians angle){}
// The same as...
static void DoNaught(double angle){}
// (disregarding inlining reducing this to a noop

These are some of the things I can think of off the top of my head. Of course, an excellent language designer like @EricLippert will likely think of more scenarios. So, even if these typical use cases are zero-cost, I still think it would be good to know if there is any case where the JIT doesn't treat a struct holding one value, and the unwrapped value as equivalent, without listing each possible code snippet as it's own question

like image 573
Zachary Burns Avatar asked Jul 12 '17 16:07

Zachary Burns


People also ask

What is the best way to convert a wrapper class to primitive?

All wrapper classes can be function-called, which converts an arbitrary value to the primitive type that the class represents. This is a descriptive way of converting to primitive types and I recommend it.

How do you wrap a primitive value in Python?

In addition to wrapping a primitive value by new -invoking a wrapper class, we can also do so generically by function-calling Object (the class of most objects): With Object (), we can even create instances of BigInt and Symbol (even though those classes can’t be new -invoked):

How do you instantiate a wrapper class in Java?

The wrapper classes Boolean, Number, and String can be instantiated via new: That is, new String () wraps a primitive string and produces a wrapper object. The wrapper classes BigInt (ES2020) and Symbol (ES6) are relatively new and can’t be instantiated:

How to unwrap a wrapper object from its arguments?

As an aside, if the argument of Object () is an object, it is simply returned without any changes: The generic way of unwrapping a wrapper object is method .valueOf (): > new String ('abc').valueOf () 'abc' > new Number (123).valueOf () 123


1 Answers

There can be some slight and observable differences because of ABI requirements. For instance for Windows x64, a struct-wrapped float or double will be passed to a callee via an integer register, while floats and doubles are passed via XMM registers (similarly for returns). At most 4 ints and 4 floats can be passed via registers.

The actual impact of this is very context dependent.

If you extend your example to pass a mixture of at least 5 integer and struct-or-double args, you will run out of integer arg registers faster in the struct wrapped double case, and calls and accesses to the trailing (non-register passed) args in the callee will be slightly slower. But the effect can be subtle as the first callee access will usually cache the result back in a register.

Likewise if you pass a mixture of at least 5 doubles and struct wrapped doubles you can fit more things in registers at a call than if you passed all args as doubles or all args as struct wrapped doubles. So there might be some small advantage to having some struct wrapped doubles and some non struct wrapped doubles.

So in isolation, the pure call overhead and raw access to args is lower if more args fit in registers, and that means struct wrapping some doubles helps if there are a number of other doubles, and not struct wrapping helps if there are a number of other integers.

But there are complications if either the caller and callee both computes with the values and also receives or passes them -- typically in those cases struct wrapping will end up being a bit slower as the values must be moved from an int register to the stack or (possibly) a float register.

Whether or not this cancels out the small potential gains at the calls depends on the relative balance of computation vs calls and how many args are passed and what types the args are, register pressure, etc.

ABIs that have HFA struct passing rules tend to be better insulated from this kind of thing, as they can pass struct wrapped floats in float registers.

like image 60
Andy Ayers Avatar answered Oct 13 '22 01:10

Andy Ayers