-
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
System.Numerics.BigRational and System.Numerics.MathR #71791
Comments
Tagging subscribers to this area: @dotnet/area-system-numerics Issue DetailsBackground and motivationI want to suggest a novel BigRational number type for namesapce System.Numerics.https://github.com/c-ohle/RationalNumerics This solution does not based on BigInteger, it based on a novel concept and an own calculation core.
API Proposalnamespace System.Numerics
{
public unsafe readonly partial struct BigRational : IComparable<BigRational>, IEquatable<BigRational>, IFormattable, ISpanFormattable
{
}
} API Usage var x = (BigRational)1 / 3;
var y = MathR.Sin(x, digits: 100);
//... Alternative DesignsNo response RisksNo response
|
Thanks for the interest here, I'd like to note that this proposal isn't complete enough to be actionable as is. The proposal needs to be updated to include the full API surface being proposed. If you have a specific implementation in mind, then it would be helpful to go more in depth with examples explaining how its expected to work and any limitations resulting from that. Ideally it would be considered alongside another proposal such as #21588, which equally needs a "full proposal" writeup covering the shape and functionality. One consideration is that a general purpose |
@tannergooding, thanks for your response. I saw your set of new interfaces for numeric types and I guess it is a good idea. But I'm sceptical with a Rational<TInteger> type.
Therefore I'm sceptical that such template makes sense in practice. |
@tannergooding, I've updated the suggestion and hope it's now in the form you need. |
I just wonder, should one really calculate transcendental functions like exp, sin or cos with rational numbers? It seems that (arbitrary precision) floating-point numbers are more appropriate when calculating transcendental functions. |
@tfenise sure, up to an desired perecisission, and this should be possible with an arbitrary nuber type. |
Of couse it is possible, but I don't think it is particularly efficient having to deal with the denominators when doing non-exact, approximating calculation. For example, if sin(1) is calculated to a / 2^n up to an absolute error of 2^(-n-1), and sin(2), sin(3) calculated to b / 2^n, c / 2^n likewise, calculating the product sin(1)*sin(2)*sin(3) would mean to calculate (a*b*c) / 2^(3n) if using rational number multiplication, which would be too laborious to calculate the exact big integer multiplication a*b*c, because (a*b*c) / 2^(3n) cannot be expected to really have an error as small as 2^(-3n-1), as sin(1), sin(2) and sin(3) may already have an error of 2^(-n-1). |
@tfenise, Yes, that's right, so if you want to work with such precisions, you have to know what you're doing, to estimate the desired precision before. The only difference to floating-point is that the precision is natural limited there by default. Finally a floating-point type is nothing else than a rational number type, with the limitation that the denumerator is limited to 2^n. (or 10^n for decimal). It is possible to build an arbitrary BigFloat type based on the BigRational CPU. Like a more performant and efficent BigInteger. Thats the point. The necessry arithmetics is there and can be shared. Thats what I mean with an arbitrary base type for NET. |
@tfenise, I read your concerns again this morning. "I don't think it is particularly efficient having to deal with the denominators when doing non-exact" You are absolutely right. It would not been efficent, especially not in such iterations, taylor series, etc. The solution is a very fast binary rounding on bit level, implemented as cpu instruction: lim(uint c) It is very simple: a msb (Most Significant Bit) test for the numerator and the denominator what cost nothing. Pseudo code: var x = min(msb(num), msb(den));
If (x > c)
{
num >>= x - c;
den >> x - c;
} This is finally the same that happens at floating-point arithmetic automatically. |
Thanks for the comprehensive write-up. For
Prior to .NET 7,
This is likely going to be a non-starter, especially with it exposed as a public implementation detail. Not only can the consumer of the APIs not rely on operators and therefore language features like expressions and precedence (e.g. Having this use The issue here is effectively the same that We should aim to expose something that remains easy and intuitive to use while also achieving good performance and there are many potential avenues for this. We should also consider what impact having a "packed" format will have. It may make it "machine word sized", but it also impacts the usability of the code and will increase the cost in other areas, such as if the consumer wants to get the numerator and denominators explicitly (a fairly common and expected operation for any |
@tannergooding, thanks for taking the time, I understand the needs and the priorities better now.
I have omitted the conversions from smaller types, since they are automatically mapped to the next larger type by the compiler.
Good decision for the API design, I like it, more straight an logic.
Ok, proposal for a API solution:
I am aware of the problem. The better choice would be to use: private static AsyncLocal<BigRational.CPU>? _async_cpu; That would result in the desired behavior and user safety.
uint id = ((uint)&id) >> 20; // as long as the NET stack is limited to 1MB: >> 20 Only if this id differs from a last one stored in _task_cpu does it require a request to AsyncLocal to swap and overall we maintain performance.
As proposed: Another much more general approach: unlike C++, in C# we don't have a copy constructor/operator or anything with similar functionality, QuestionsIt stands to reason that I should do this as a dedicated NET 7 development in a new repository. And in general. Befor I start with something. Is there really any interest in such an solution at all? Regards |
@tannergooding, a .NET7 BigRational version is available for testing. MathR is gone, the missing conversions and the new interfaces are implemented and tested, - only not finally optimized. |
The new INumber-based interfaces are great. I tested this from both sides.But I want to suggest some improvements - before it is to late. The
The same applies to the functions
For the statics
The Also for the other interfaces. They could all be improved upon and benefit from this approach by using enum parameters for capability checks and then only one static method is required for each method body type, unary, binary etc. Consistently thought: the entire current But yes, it is also nice to see the always same normed function names for similar types and to force this by the interfaces. |
I am not so sure they design 17 of |
@Thaina but always ever each number type with INumberBase interface gets a function table for every static interface function for the own implementation and has completely implement this. One table for each INumber sub interface. |
@tannergooding update:
I found a solution using the or-operator in combination with an implicit conversion for the var x = a * b + c * d + e * f; it is possible to write: var x = 0 | a * b + c * d + e * f; // 5x faster, allocs only for the final result It must be the or operator, because of its priority in arithmetic expressions, whereby its
How easy it is here Of course, the best would be the compiler-devs would reconsidering the 20 years old decision regarding an assign-operator in C#. Only some lines and it would possible to automate such things perfectly. |
As per the framework design guidelines:
That is, you should only use an operator where that mathematically makes sense so using |
The real or operator is not in touch. A better idea? Is there a chance to get an op_assign or something similar solution from the compiler team? |
We snapped "code complete" last Tuesday and any additional changes need a lot more scrutinization to be accepted. None of these suggestions look like they warrant such a change.
This will ultimately hurt codegen and optimizations that the JIT can do. It will also make the code overall harder to understand and cause it to do more work except for potentially rare edge cases where you want multiple checks simultaneously.
Extending this is no easier and is in a few ways much harder. While it indeed doesn't need new API surface, it does require existing code paths to update to handle it (risks failure for unrecognized flags) and still requires all existing types to version regardless.
It is a consideration of the storage format, most generally this is
This is problematic both short and long term for many of the reason's I've listed above. It makes the code harder to read/reason about and hinders optimizations available to the JIT as it has to deal with inlining larger method bodies with more code.
Jump tables are problematic in the first place, ideally we little to no jumps in the common paths and the JIT is able to optimize these calls instead.
The former are IEEE 754 required functions and important for allowing basic comparisons between
This would restrict versioning, restrict which types could implement the interfaces, and hurt the overall ability to improve these interfaces and their support over time. |
I don't see this happening. The solution is to follow the normal and existing design guidelines and patterns. In this case, the most likely solution is to have and expose the |
ok, thanks, I need time to think. |
@tannergooding I guess there is agreement that the current |
I assume you have something like a test environment for INumber. |
Relying on tricks like that is a non-starter. It will never pass API review. |
it's a trial |
@tannergooding With request to check. String formatting - does it fulfills the requirements? Standard numeric format strings (link used as spec)
Custom numeric format strings (link used as spec)
Default ToString()
Of course, all this can be changed quickly. Full custom format support could be included. |
@tannergooding update
|
@tannergooding please - with request to check. How it looks to the user: BigRational a, b, c, d, x;
x = a * b + c * d; // standard notation, 3x alloc/normalization
x = (BigRational.Builder)a * b + c * d; // one final alloc/normalization, up to 3x faster
x = (BigRational.Builder)a * b + (c - 1) * BigRational.Sin(d); // parentheses and function calls inclusive. To test and show I added a small BigInt a, b, c, d, x;
x = a * b + c * d;
x = (BigInt.Builder)a * b + c * d;
x = (BigInt.Builder)a * b + (c - 1) * BigInt.Pow(d, 100); So far it should conform to NET standards, except that it also works for parenthesized expressions and function calls. How it works:
The implementation: (complete code) public readonly ref struct Builder
{
readonly BigRational p; // the one and only field
public static implicit operator Builder(BigRational v); // set of implicit's
public static implicit operator Builder(long v);
public static implicit operator Builder(double v);
//...
public static Builder operator +(Builder a, Builder b); // set of operator's
public static Builder operator -(Builder a, Builder b);
public static Builder operator *(Builder a, Builder b);
//...
public static implicit operator BigRational(Builder v); // finally, "ToResult()", as implicit cast
} Almost identical, the code for a BigInteger solution. Remarks
|
An expression BigInteger t1 = a * b;
BigInteger t2 = c * d;
x = t1 + t2; Inserting a cast such as the following is problematic for a few reasons: x = (BigInt.Builder)a * b + c * d; To start, this only effectively changes the expression to be: BigInteger.Builder t1 = new BigInteger.Builder(a);
t1 *= b;
BigInteger t3 = c * d;
x = t2 + t3; It cannot optimize or handle Additionally, casting from a I strongly believe that the stack based approach is not the right approach here. If publicly exposed, it forces users into a more complex pattern and while there is indeed some benefit to such an approach, the complexity cost for something that is meant to be a "simple to use number type" is too high. If used as an internal implementation detail, it becomes far too restrictive on what developers can do. Both of these lead the common usage scenarios into being pits of failure. Because of this, such functionality would be better suited to a third-party package so that the more advanced/complex path is opt-in and only for when the user really needs it. |
@tannergooding thanks for response. No big discussion is necessary at this point. If it's not good enough, I'll have to find a better solution. Just as note:
It is simply impossible to construct such cases because there are neither constructors nor functions. Operators only, but using this we're back to standard notation and everything is fine and the behavior is clear defined.
Regardless of parentheses and function calls, the compiler always generates a flat sequence of operator calls when parsing. The explicit cast from number to number.builder and implicitly back is always the first and the last event in the sequence. The problem is rather the opposite - the operator has no reference to parentheses - difficult to switch the optimizaton of.
Therefore not possible without a cast: t1 = (Builder)a * b; The case: builder t1 = (builder)a * b; is something that looks dangerous, but improper use can be detected at runtime.
Since it is a ref structure, it costs nothing, in fact only the operator calls are needed as an event. |
I called out that this isn't the type of scenario where a cast is desirable due to the hidden cost. The standard pattern is for a conversion via constructors/functions.
That is an implementation detail, not a guarantee. You have an expression that results in at least two intermediates needing to exist (one for
Detecting improper use at runtime, rather than compile time, is often viewed as a "pit of failure". It means that it is much easier to miss an issue or introduce a bug.
The ref struct doesn't have overhead but it must track the "storage space" for the computed values. Due to this being "big data" and the builder needing to grow to fit the size of the result, this must be either some GC or Native memory allocation (with it likely being a GC allocation due to the potential pit of failures around handling Native allocation lifetimes). Every builder is, therefore, at least 1 pointer (4/8 bytes) in length. Realistically, it is likely 12-16 bytes instead, as we typically need to track the backing data, the length, and some flags. You can of course do tricks like tracking just a Every builder likewise needs to support "growing" the backing data for the array. This means that you cannot (without flags tracking that the data cannot be mutated) do a "zero cost" conversion of a There are many considerations that have to be taken into account around usability, where allocations come into play, where those allocations actually have cost vs where they won't show up, etc. |
Yes for real Builder solution and yes the name Builder for this thing is misleading. I had no better idea how to call it. "BuilderActivator", "BuilderDelegator", "TempValue", ...
The approach is completely independent from this since all depends on the initial- and the final cast and this must happen first and last. It's actually only a safe workaround to simulate the missing op_Assign operator. Also the operators and conversions, there is no science behind, it is only to provide a NET conform and safe interface. Something better than the "OR" trick.
Not really, as ref struct the life time is short, the buffer or builder is there anyway and only needed for back cast's.
Behind it remains all the same. |
The point is that it is functionally equivalent to the example I gave and so there aren't "enough" casts/conversions. A second builder may be needed for
You must have a storage space to track the result. Given
I was trying to elaborate that the ref struct only removes an unimportant and unexpensive short lived allocation. While removing such allocations can sometimes pay off, it will not meaningfully impact most operations and comes with a disadvantage around utilizing the builder in more complex scenarios (including around async code). The cost around things like |
@tannergooding (the movs between calls removed) x = (BigInt.Builder)a * b + c * d; |
@tannergooding Please, this is only a option to provide more performance. The solution itself does not depend on it. |
@tannergooding To make sure I can give verified statements I checked everything again, latest NET 7 code The In principle there is no difference. Where is the problem with That and the efficiency of the systems already make a difference in average performance of over 20%. This for simple integer arithmetic - for rational arithmetic the effect is much bigger. With Formula to have all bottleneck functions (incl. a sqr) in: |
Maybe not related to this issue, but |
I don't think it's possible to have constant number of variable for irrational number. For rational it possible for just 2 number so we can make a struct out of it. But irrational realm can be chained and so I think it not worth to support them in language |
@acaly yes of course. mainly intended to get a test case with massive calculations and a width range of number complexity and to test thread safety. But there is another aspect. BigRational acts like a rational number. The CPU however is a general solution with very special bit level operations and can emulate integers and floating point arithmetic. There are for example fast binary round ops to emulate floating-point behavior: forget the last bits. Tools needed for example in Taylor series where interime results quickly would get much more precision then then necessary for the result precisission. |
@Thaina The day someone invents an irrational type of number, I will delete the repository. Yes of course, it's more to have a arbitrary number type for at first, precision in algorithms sensitive for epsilon and robustness problems, geometrics cut of lines, planes etc, intersections always perfect exact - fp can only estimate and wrong estimation and the algorythm fails. Such things. |
I want a BigDecimal like this one But with more functions than pow(). It needs also a BigDecimal of Pi, e, Sin(), Cos, Tan(), etc. |
@theuserbl Hi, Meanwhile, BigRational supports all math functions. Results rounded to appropriate decimal places and it matches BigDecimal numbers and their behavior with better performance and memory efficiency. An IEEE-754 value struct template Type: |
@c-ohle Nice. But why haven't you added a pull request here? Possibly the .NET maintainers have some terms, like that "Copyright (c) 2022 Christian Ohle" will be changed to "Copyright (c) .NET Foundation and Contributors". |
As per our API review process, all new API surface area must first go through and be approved by the API Review team: https://github.com/dotnet/runtime/blob/main/docs/project/api-review-process.md As per the discussion above, the area owners do not feel the proposed surface area/implementation are the right fit for inclusion in the BCL itself. There is some interest in providing some type of |
@tannergooding Hi, something completely new is coming soon. If you want we could work together. |
@tannergooding a new BigRational class - in short: BigRat.cs (single file solution, just copy and try)
Would that meet the requirements?*
New approach:
This as a reference version. |
@c-ohle Nice. But I still think, that it is the wrong place, to publish the code. The right place is, to add the code per "Pull requests" and not posting it only under "Issues". |
Putting up a PR is not the right approach. All new APIs must first have their open proposal which is accepted by the area owners and then taken to and approved by API review. If all that happens, then a PR is allowed to be put up. The entire process required is seen here: https://github.com/dotnet/runtime/blob/main/docs/project/api-review-process.md I'm still not a fan of the overall approach being used and don't believe it fits or considers the needs/scenarios at large. It has taken into account some of the more fundamental issues that were raised initially. However, the code as is not super readable, which makes it difficult to review and determine if it is acceptable or not (lots of one liners and other things that violate the general coding style/rules for the repo): https://github.com/dotnet/runtime/blob/main/docs/coding-guidelines/coding-style.md If it were in box, it would most likely not support downlevel frameworks. It would be .NET 8+ exclusive and the names of APIs would need to follow the general Framework Design Guidelines around naming: https://learn.microsoft.com/en-us/dotnet/standard/design-guidelines/naming-guidelines Not supporting concepts such as |
Background and motivation
I want to suggest a novel arbitrary base number type for namespace System.Numerics.
I love C#. I miss a base number type for arbitrary precisission that is really usable in practice.
Such a number type should have the properties of a System.String:
a simple binary unique always normalized immutable readonly struct in machine-word size.
This would ensure that it can be passed around internally as quickly as possible and
makes it efficient to use it for client types like vectors and matrices.
The general problem with NET and arbitrary number types.
It is so nice and easy to implement custom number types in C# by define numeric operators and implicit or explicit conversions.
For value struct types it works perfectly as long as they have a fixed size.
However, there are major problems for any kind of arbitrary precision type.
No problem if we have a single operation like: a = b + c;
The the + operator function can alloc the necessary memory resources, create and return a final object as needed for a.
But in practice we have expressions like this: a = (b * c + d * e) / f;
We usually have many intermediate results, much more than final results, but the intermediate results don't require memory allocs and setting up fully qualified, normalized final result objects.
The C# compiler simply generate code that calls the operator functions and the operator function gets no indication that only an intermediate result is needed, which could be managed in buffers.
BigInteger and BigRational.
Both types are closely related.
They need the same arithmetic big-integer calculation core whereby, for efficient rational arithmetics it needs some more bit level functions..
It looks like a logical consequence to setup a BigRational type from two BigInteger.
But that only doubles the problems:
BigInteger has already a 2 machine-word size, a BigRational based on BigInteger gets 4.
A Vector3R with X, Y, Z gets 12 and so on.
For every interimes result we get 12 times allocations and unnecessary normalizations.
This is far from any efficency.
The new approach, a BigRational type for NET that solves the problems
To bypass the mentioned problems I want to propose a general new system with significantly better performance and better memory usage.
At first the data structure:
This is all, BigRational has only one array field and therefore the desired machine-word size and has furthermore the immutable properties like a string object.
A nested public class CPU what represents a stack-machine and encapsulates the calculation core.
Nested as only class that has access to the private BigRational creator and access to the data pointer - for safety.
Everything as small and secure as possible, no additional system resources required. Not even predefined default values, nothing.
The data format used in the BigRational uint[] data pointer is the same as used in the stack-machine stack entries and is also simple as possible:
The first uint a sign bit and the number of following uint digits for the numerator, followed by a uint with the number of unit digits for the denominator.
This sequential format has the benefit that the internal integer arithmetics need only one pointer and not ranges, conversions, case handling etc. and this allows the best possible troughput.
A BigRational with null data pointer is defined as the number Zero (0) and therfore the value struct default is a valid number.
The Interfaces
BigRational itself acts like a empty hull to encapsulate the data pointer and provides a save first level user interface for a number type with all the number typical operators:
Documentation for the particular functions at GitHub
For best possible compatibility the high level functions like several
Round
,Truncate
,Floor
,Ceiling
,a set of
Log
andExp
functions up to trigonometric functionsSin
,Cos
,Tan
,Atan
,...specials like
Factorial
etc.These all in a static extra class MathR as mirror to System.
Math
and System.Numercs.MathF
and similiar as possible.This is to reflect the old known standards and to make it easy to change algorythms from for example double to BigRational or back, to check robbustness problems.
A look at the BigRational and MathR functions always shows the same scheme.
Gets the task_cpu and execute instructions on it:
All calculation happens in the CPU object.
The high-level API functions are all implemented as short or longer sequences of CPU instructions.
It all breaks down in such sequences and this is in general short and fast code and allows a rich API with less memory usage.
Some example client types using this system: ComplexR, Vector2R, Vector3R, Matrix3x4R, PlaneR, VectorR.
A special is VectorR, This struct has also only one pointer containing a index and a sequence of the rational data structures and can therefore store lot of numbers very efficently. It is an example for client types taht it is easy using BigRational.CPU but independent from BigRational, using a private data format.
The CPU - a stack-machine
Like every stack-machine the CPU has instructions like
push
,push(double)
,push(int)
,... and pop's likepop(),
BigRational popr()
,double popd()
,... self explaining instructions likeadd()
,sub()
,mul()
,div()
,mod()
,neg()
,abs()
,inv()
,dup()
.But there is a difference:
Since we have numbers with arbitrary size
mov()
instructions are not efficient because it would imply many memory copies.Instead of this it fast as possible to swap the stack entry pointer only since the stack is a uint[][] array.
This explains the versions of
swp()
instructions.A pop command frees nothing. It only decrements the index of the stack top and the buffer there keeps alive for next operations.
Allocs are only necessary if the current stack top buffer is not big enough for the next push op but this is a successive rar case.
Not only for the high-level API, the stack-machine itself uses the stack excessively for all internal interimes results and therefore dont need stackalloc or shared buffers.
It forms a consistent system and enables fast and compact code without special handling.
The CPU class reduced to the public instruction set:
It is noticeable that there are many different versions of the same instruction. Since we have number values with arbitrary size it is all to avoid unnecessary memory copies. For example, to add two BigRational numbers it is possible to push both values to the stack, and call a simple add(). But since the data structure on the stack is the same as in BigRational pointers the CPU can access and read the data pointer directly and push the result only on the stack what saves two mem copies and is therfore faster.
Other operation versions working with absolute stack indices what is more efficent in many situations. For example if one function pushes a vector structure and another function takes this as input.
The Efficiency
On an example. The CPU implementation of the sine function, which can also calculate cosines.
This function uses several identities and a final Taylor-series approximation.
The code looks ugly, but it reduces the computation to a sequence of just 36 short CPU instructions.
It means that providing a rich set of such common math functions already at this level doesn't involve much overhead.
To come back to the begin, the problem with BigInteger and a Rational type based on BigInteger.
Imagin the same sine calculation with such type. Where every instruction involves not only two new full allocated BigIntegers for a result, Rational itself needs as example, for a simple addition 3mul+1div+1*add and this are all new allocated BigIntegers. But not enough, Rational has to normalize or round the fractions - additional many new BigIntegers.
It should be obvious that this is inefficient, slow and leads to an impractical solution.
To show the differences I made the Mandelbrot example in BigRationalTest app. It does exactly this. A conventional BigRational class based on a BigInteger for numerator and denominator is part of the Test app and called OldRational.
At the begin BigInteger profits as it can store small numbers in the sign field but then, deeper in the Mandelbrot set, it slows down exponentially before it stops working in a short break between endless GC loops.
The bottle-nack for rational arbitrary arithmetic is the integer multiplication and for the normalization the GCD function and the integer division.
The first benchmarks showing that the BigRational calculation core is aproximatly 15% faster then the equivalent functions in System.Numerics.BigInteger. At least on my machine.
With other words, using BigRagtional for pure integer arithmetics can improve the performance already.
For integer algorithms using the CPU the effect is much bigger of course..
BigInteger in NET 7 will use Spans more stackalloc and shared buffers.
This benchmarks i made with a NET 7 preview versions are showing that this reduces a little bit the memory pressure
but further degreads the performance, especialliy for big numbers.
It is probably the use of System.Buffers.ArrayPool.Shared what is not the fastest by a threshold of 256 for stackalloc.
These are problems that are not existing for a stack-machine solution.
Latest benchmark results can be found Latest-BigRational-Benchmarks;
(Same benchmarks with NET 7 preview, latest mater-branch a lost of around 10% performance)
It is currently under construction, but I will be adding more tests daily.
Further development
There is great potential for further improvements.
In the calculation core itself, in the algorithms.
Furthermore, frequently used values like
Pow10
,PI
etc. should efficiently cached, numbers that are currently always new calculated for functions likeExp
andLog
; and this would give another big performance boost.I'm looking for a solution using
static AsyncLocal<CPU>? _async_cpu
; instead of only[ThreadStatic] CPU _task:cpu
;This would be more user friendly and save for
await
functions etc. but access to _async_cpu is 3x times slower, I need to find a trick to detect if a _async_cpu request is necessary, maybe a quick stack baseadress check or something.The set of MathR functions should be extended and handle the DefaultPrecision property as it seems to work with a hypothetical floating point type that allows for such precision.
Especially geometric algorithms are very sensitive to epsilon and robustness problems.
A vector extension, compatible with the current System.Numercis.Vector types would be useful.
No one with BigInteger experiences would believe that it makes sense, that it's even possible, especially not in NET.
But the Polyhedron 3D example shows: It works for thousands of BigRational Vertex, Points and Planes, for Polygon Tessellation, Boolean Intersections of Polyhedra (CSG) and this in real time at animation time:
Remarks
The current implementation is optimized for X64, tuned for RyuJIT code generation.
For better performance on X86 it would be necessary to put special X86 code in critical pathes.
Same goes for ARM, but I don't currently have a development environment for it.
I have created a C++ COM implementation of the CPU where the CPU instructions are COM interface calls.
Unfortunately, for C# such a solution is about 30% slower due to the many platform calls.
The same for a C++/CLI solution, which is also about 30% slower.
But the COM solution inproc in a C++ app is almost 2x faster because of the powerful C++ optimization.
I checked the possibility of using AVX, SSE, Bmi intrinsics for optimizations. Unfortunately none of the test implementations were faster than the simple scalar arithmetic currently in use. Same experiences as published by many other programmers.
Only Bmi2.X64.MultiplyNoFlags (MULX r64a, r64b, reg/m64) could really improve the performance but unfortunatlely, the corresponding ADCX and ADOX instructions are currently not implemented in Bmi2.X64.
From my point of view the best choice for an System arbitrary-precision number type is BigRational, not BigInteger.
For BigRational all other types are only special cases. This applies for BigInteger, BigFloat, BigDecimal, BigFixed, in general floating-point types in any fixed or arbitrary size.
Finally these are all rational number types, only with restricted denumerators, BigInteger 1, BigFLoat or in general floating-point 2^n , BigDecimal 10^n and so on.
It is easy and efficent to implement such types very compact based on the BigRational CPU. The CPU has all the instructions that are necessary to calculate for the specific number types, otherwise it's easy to add.
No problem to use own data formats as shown in example: VectorR. Only one core is necessary and the resources can shared for several client types. Not necessary to implement every time again a new calculation core.
The focus should be on highly optimizing this CPU core for all possible platform- and processor configurations, and all client types would benefit.
More technical details, example codes etc. can be found at:
The text was updated successfully, but these errors were encountered: