-
Notifications
You must be signed in to change notification settings - Fork 1k
This issue was moved to a discussion.
You can continue the conversation there. Go to discussion →
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
Proposal: by-ref struct operator parameters #6628
Comments
That said, I don't think that said metadata or enforcement is really necessary to provide |
I didn't mean to imply that CLR changes are required. Compiler enforced "readonly" for ref operator parameters seems worthwhile and sufficient. However, if the CLR and verifier were changed to understand and enforce "readonly ref", it might be worthwhile to use it for all non-async struct parameters rather than require the noisy declarations. I've reworded to clarify. I suspect, for backward compatibility, we'd want to allow the same operator (and types) to be defined as both ref and non-ref. This would allow pre-existing binaries to continue to link against the non-ref version of the operator. The compiler overload resolution would pref the ref version of operators. An alternative would be for the compiler to only allow the ref versions, and generate the non-ref versions for compatibility. |
1st: Should it also use 2nd: Does |
@qrli Why should a ref-return be useful for value types at all? Unless it's a cast operator, of course, where I wouldn't mind being able to pass a reference from an inner struct, e.g.
|
@orthoxerox Same reason as ref parameters, to remove redundent copies to get better performance. public static vect3d operator +(readonly ref vect3d a, readonly ref vect3d b)
{
vect3d r; // local variable
r.x = a.x + b.x;
r.y = a.y + b.y;
r.z = a.z + b.z;
return r; // r is copied to caller
} |
While the ref-returns proposal (#118) doesn't make this clear, AFAIK it's not safe for CLR ref returns to refer to values on the stack, as those values will be potentially over-written after returning. In order to safely avoid the return copy for general struct operators, the caller would need to pre-allocate the return space and pass in a reference. This might look something like:
|
Well I wish we could have It shorter and could be assume being ref
Also I love @jeske idea that making operator function with Simple problem is, what it would look like if var a = vec3d.one;
var b = vec3d.one;
var c = a + b; // easy , we can tranform this code to vec3d.+(a,b,outc)
var d = (a + b) + (a - b); // This is hard parts We can't use d to temp (a + b) and (a - b) at the same time. So it need to alloc at least another vec3d automatically Aside from that I think we have another way to deal with return ref, using public static out vec3d operator +(const vect3d a, const vect3d b)
{
// Normally static function can't use base or this keyword
// But with that out keyword present, then this static function can use base keyword
base.X = a.X + b.X;
base.Y = a.Y + b.Y;
base.Z = a.Z + b.Z;
}
var a = vec3d.one;
var b = vec3d.one;
var c = a + b; // It pick this c tobe base |
I've done some checking, and there's nothing in the compiler that prevents you from having a ref operator other than the spec itself:
I've replaced the check with an
It even supports stuff like one parameter being ref, and the other not automatically (useful for things like |
It looks like this issue was addressed in C# 7.2's Safe Efficient Code Enhancements. See also writing safe efficient C#. It looks like the technique requires declaring the struct readonly (immutable), and then using "in" modifiers on arguments. The result looks like this:
You can also declare return values as |
This would be very useful for Or is the compiler or runtime clever enough to see fields of passed-in matrices are only read from, preventing a defensive copy to happen? So far I always have to use extremely ugly static methods to prevent needless copies, like |
How about an overall public sealed class MyClass<T>
{
private T m_value;
public MyClass(in T value) => m_value = value;
public ref T Value => ref m_value;
// Example of new proposed "ref" return operator for class:
public static ref T operator ref(MyClass<T> value) => ref value.m_value;
} Now code like this: var x = new MyClass<int>(12);
ref var y = ref x.Value;
y = 13; Could become: var x = new MyClass<int>(12);
ref var y = ref x;
y = 13; This basically proxies ref return operations on a class, that would normally not allow this, to its ref operator. Per previous comment by @RayKoopa, this would allow results for large structures by doing something like the following: public unsafe struct Matrix4x4
{
public fixed double Values[16];
public ref double this[int x, int y] => ref Values[x * y];
}
public class MatrixMultiplyOp
{
private Matrix4x4 m_result;
public MatrixMultiplyOp(in Matrix4x4 lhs, in Matrix4x4 rhs)
{
for (int i = 0; i < 4; i++)
for (int j = 0; j < 4; j++)
m_result[i, j] = lhs[i, j] * rhs[i, j];
}
public ref Matrix4x4 Result => ref m_result;
// Example of new proposed "ref" return operator for class:
public static ref Matrix4x4 operator ref (MatrixMultiplyOp value) => ref value.m_result;
}
public static class Matrix
{
public static MatrixMultiplyOp Multiply(in Matrix4x4 lhs, in Matrix4x4 rhs) => new MatrixMultiplyOp(lhs, rhs);
// FYI - something like this already works today for this example, but the above code is more simple
//public static ref Matrix4x4 Multiply(in Matrix4x4 lhs, in Matrix4x4 rhs) => ref new MatrixMultiplyOp(lhs, rhs).Result;
}
static void Main()
{
// Used like this:
var lhs = new Matrix4x4();
var rhs = new Matrix4x4();
ref var result = ref Matrix.Multiply(lhs, rhs);
} |
This issue was moved to a discussion.
You can continue the conversation there. Go to discussion →
Related to: dotnet/roslyn#165, dotnet/roslyn#115
One substantial performance gap between C# and C++ is C#'s high overhead of using struct operators, because of their pass by value copying semantics. In a simple overhead test for a relatively small vect3d type (vector of 3 doubles), the cost of pass-by-value is 3x vs implementing the same function with ref arguments. As the struct types become larger, the overhead of pass-by-value grows even higher.
While C# offers many advantages over C++, software which spends a significant amount of time performing mathematical operations on struct datatypes is taxed by their performance overhead. Using non-operator functions is not a practical solution, as mathematical code becomes too hard to read.
One possibility is to combine by-ref operators with dotnet/roslyn#115 (readonly parameters), to enable higher performance struct operator implementations while enforcing (or encouraging) immutable value-type semantics for the caller.
If readonly is merely a construct of the C# compiler, then the parameters are not immutable WRT to CLR. This is analogous to the const references used in C++ operators, which are not strictly enforced. In practice, C++ operators are generally well behaved in respecting immutable intent of these const arguments, and I believe C# "readonly ref" operator overloads would also be well behaved - while achieving significant performance improvement from avoiding copies.
It may be advantageous to allow value-copy and ref overloads of the same operator, to maintain backward compatibility with already compiled code. For new resolutions, the compiler would prefer the ref version of the operator overload. One option would be for the compiler to only allow a single matching overload (either readonly-ref or non-ref), and in the case of "readonly ref" it could generate the non-ref version for compatibility.
If the CLR and verifier are taught about "readonly ref", and they are allowed more generally in function parameters, the compiler could allow the user to omit the "ref" decorator for any "readonly ref" parameter in a function invocation, since "readonly" assures that parameter will maintain value type semantics. In this case, it might be advantageous to look for alternative syntax options, to avoid a noisy proliferation of "readonly ref" on many value-type parameter declarations. One option is to make non-async struct parameters pass as "readonly ref" by default. However, for very small structs (like IntPtr) pass by reference is marginally slower than pass by value. (20% slower in one trivial test) Another option is to use a shorter keyword, such as "in", matching up "in/out/ref".
The text was updated successfully, but these errors were encountered: