-
Notifications
You must be signed in to change notification settings - Fork 4.7k
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
Add generic Math methods for numeric types #18244
Comments
I like the concept, but I am worried that we will get really bad performance out of this. I think it would be difficult to move forward on this until we were able to understand what it would take to make it high-performance. Is it possible to have a fully-portable binary with this stuff in it, or would we need special handling in the JIT (like As an aside, I use a very crude version of this pattern in the System.Numerics.Vectors tests themselves: https://github.com/dotnet/corefx/blob/master/src/System.Numerics.Vectors/tests/Util.cs#L109. It relies on |
@mellinoe what would be acceptable performance for code such as this? My first usage scenario was as a complement to Vector in order to process the last few elements or to "add the finishing" to algorithms, but then it would be very beneficiary to be able to use this for more general calculations or places where you don't need vector. Ideally I think this is one area where it makes sense to improve the JIT. But I haven't added any feature request in CoreCLR since I wanted feedback on this idea. Implementation Strategies1. "IL"There is no way to write genereic methods using arithmetics or primitives in C# (that I know of at least), but it is still valid IL (at least sort of). .method public hidebysig static !!T AddGeneric<valuetype T> (
!!T '',
!!T ''
) cil managed
{
IL_0000: ldarg.0
IL_0001: ldarg.1
IL_0002: add
IL_0003: ret
} The downside of this is that the JIT will raise a ExecutionEngineException when trying to run this code. I have tried multiple approaches of adding some kind of error checking in order to allow a nicer error to be raised, Pros:
Cons:
2. Handle all types explicilty (as done for vector)Example code for add: [MethodImpl(MethodImplOptions.AggressiveInlining)]
public static T Add<T>(T lhs, T rhs)
where T : struct
{
if (typeof(T) == typeof(float))
{
return (T)(object)(((float)(object)lhs) + ((float)(object)rhs));
}
if (typeof(T) == typeof(int))
{
return (T)(object)(((int)(object)lhs) + ((int)(object)rhs));
}
....
throw new NotSupportedException();
} Pros:
Cons:
3. Using DynamicThe approach as used in https://github.com/dotnet/corefx/blob/master/src/System.Numerics.Vectors/tests/Util.cs#L109 Performance comparisonsBenchmark 1: Computing sum of 100 000 (float) array elementsA simple benchmark summing 100000 random floats that are located in an array. BenchmarkDotNet=v0.10.1, OS=Microsoft Windows NT 6.2.9200.0
Processor=Intel(R) Core(TM) i5-2500K CPU 3.30GHz, ProcessorCount=4
Frequency=14318180 Hz, Resolution=69.8413 ns, Timer=HPET
[Host] : Clr 4.0.30319.42000, 64bit RyuJIT-v4.6.1586.0
Job-FPOZGJ : Clr 4.0.30319.42000, 64bit RyuJIT-v4.6.1586.0
Jit=RyuJit Platform=X64 IterationTime=1.0000 s
LaunchCount=1 TargetCount=3 WarmupCount=3
FoorLoopSumImpl The baseline to comapre against float sum = 0.0;
for (int i = 0; i < _values.Length; ++i)
sum = sum + _values[i];
return sum; GenericHelperClass This is implementation strategy 2. GenericILMethod This is implementation strategy 1 where the IL defined method is located in another assembly. Generic_Sum_Dynamic Implementation strategy 3. Benchmark 2: Computing the sum of all integers from 1 .. 10 000In this test the array accesses and corresponding memory load is taken out away from the equation. BenchmarkDotNet=v0.10.1, OS=Microsoft Windows NT 6.2.9200.0
Processor=Intel(R) Core(TM) i5-2500K CPU 3.30GHz, ProcessorCount=4
Frequency=14318180 Hz, Resolution=69.8413 ns, Timer=HPET
[Host] : Clr 4.0.30319.42000, 64bit RyuJIT-v4.6.1586.0
Job-FPOZGJ : Clr 4.0.30319.42000, 64bit RyuJIT-v4.6.1586.0
Jit=RyuJit Platform=X64 IterationTime=1.0000 s
LaunchCount=1 TargetCount=3 WarmupCount=3
FoorLoopSumImpl The baseline to compare against int sum = 0.0;
for (int i = 0; i < 10000; ++i)
sum = sum + i;
return sum; GenericILMethod Using the Add method from strategy 1. GenericHelperClass This is implementation strategy 2. DynamicHelperC This is implementation strategy 3. Benchmark summary
|
@mellinoe for the question above |
@Daniel-Svensson Wow, that was extremely thorough! Thanks for the attention to detail. Do you have the code available anywhere for reference? It sounds like you may only have the I like this proposal overall, and based on the testing, performance numbers are not catastrophically bad (~3-5x). I think that using the Generic IL implementation is not feasible, even if it is super fast. It's not 100% clear to me whether it's actually valid IL in the first place, or if our JIT just allows it. The "multiple-if" method seems to be the way forward. A few more questions/points come to mind:
|
I've cleaned up some of the examples and uploaded them to https://github.com/Daniel-Svensson/ClrExperiments
I would not call the list complete at the moment.
Including the constraints should not force boxing to occur, the runtime is pretty god at de-virtualization in order to avoid boxing when the type is known as is the case here. But it should certainly be considered if it is desired to require the calling code to require IComparable{T} in the generic constraints or not.
I've tried to find such a library before I started with these experiments. But I did not find any alternative for C# / VB.Net. F# in itself allows these kind of generic functions and algorithms to be written, but I do not know of any good way of writing them in F# and exposing them to other languages except of providing a facade with overloads for all types. |
Two main observations: (1) The type signature issues here are complex. (2) This doesn't seem to me to add much practical value. By the way, Abs takes one argument, not two. First: The point of having methods like this is presumably to be able to build sophisticated algorithms on top of them which will then automatically work for all the built-in numeric types. Let's start with looks to me like the simplest algorithm one might think to build for arbitrary numeric types: averaging. To average, we need to add a bunch of Ts; no problem there. Then we need to divide, not by a T but by an integer. Well, if our values are doubles or decimals, that's probably not a problem, since integer can be cast to those types. But if our values are integers, we don't want to do integer division, we actually want to do double division and produce a double. (Or maybe sometimes a decimal?) To deal with this, we will need to start adding some mixed-type overloads and maybe name variants to specify different conventions. By the way, what should Negate of an unsigned type produce? Maybe the corresponding signed type, but then the output T is different from the input T and what do we do if the input value is too big to negate? Maybe we just throw an exception, but then any algorithm using Negate is automatically useless for ~50% of our built-in classes. Second: Suppose we added variants to cover all the ambiguities and were able to write something really sophisticated and useful on top of this, like say a matrix inversion algorithm. (That would be really impressive, considering that the inverse of an integer matrix is not an integer matrix, but still imagine we did.) How useful would that be to people who care about matrix inversion? As one of those people, I would say "not so much". The first thing we want is certainly to invert a matrix of doubles, but the next thing isn't to invert a matrix of singles or some other built-in-type, but rather to invert a complex matrix or a matrix of some arbitrary-precision type, in any case a matrix of some new type that goes beyond the capabilities of the built-ins, and such a case wouldn't be covered by these methods. |
I apologize for the shameless plug, but if anyone is looking for this sort of functionality currently you may want to consider my library Genumerics. |
@TylerBrinkley I think plugging relevant libraries is always welcome..! |
Closing this. As mentioned in other issues (such as #50647 or #25058) we are looking into the correct surface area to expose here as part of the This is currently being tracked in our dotnet/designs repo and I've added the initial rough draft of the surface area here: dotnet/designs#205 Further discussion or suggestions should happen there and in future PRs that further refine the proposed surface area. |
Overview
With the addition of System.Numerics.Vector it suddenly became possible to write some generic algorithms that worked for most primitive numeric datatypes. However as soon as you need to work with single elements you are out of luck.
I propose a new generic API for scalar datatypes based upon the ideas behind System.Numerics.Vector.
Just as for vectors many of the genereic methods could be generated using .tt files such as in https://github.com/dotnet/corefx/blob/master/src/System.Numerics.Vectors/src/System/Numerics/Vector.tt
API
This is an example of how the API could look like.
All methods below are taken from the Vector class (https://msdn.microsoft.com/en-us/library/system.numerics.vector(v=vs.111).aspx).
Additional methods such as those found in System.Math (https://msdn.microsoft.com/en-us/library/system.math.aspx) would be interesting as well.
It could also be interesting to add non-generic methods such as
float Round(float x, int digits);
as wellAll functionlity could be provided in a new nuget package in order to allow out of band releases without conflicts with the current Math type.
The generic requirements for some methods might be changed a bit to allow additional types, such as using providing extra overload such as those suggested in https://github.com/dotnet/corefx/issues/1583.
Comparison and equality checking was not included since IComparable<> and IEquatable<> are implemented for the types already, but if it is considered a good idea to be as similar as possible to Vector then the following methods should also be considered
Types to support
I propose that all the primitive numeric types including decimal are supported (the same types as Vector<> support but with the addition of decimal). Invoking any method with any other type should result in an NotSupportedException()
Related issues:
I found a few issues regarding similar ideas
The text was updated successfully, but these errors were encountered: