Structs, C#7 and Performance Improvement 

C# 7 provides a very powerful feature from the performance standpoint. This feature is returning by ref. Essentially this allows for value types to be returned without having to copy them.

The guidelines are normally that you shouldn’t use a struct with too many fields. Various sources quote various size guidelines. Whenever the struct was over the prescribed size it was recommended that you pass it by reference.

With the new syntax for returning types by reference, it’s now more convenient (no more methods returning via out parameter) to use structs. 

In performance critical scenarios where you need to avoid polluting managed heap with too many Gen #0 objects using structs has now become more natural. In the past dealing with structs was somewhat cumbersome if you dealt with a large number of fields and needed to avoid copying of values. 

I have worked on a large application at McLaren – Telemetry Acquisition System that is supplied to all teams. The performance of the application is very critical as it has to process gigabytes of telemetry data. We have used structs extensively to squeeze out every bit of performance from .NET runtime.

I think it’s my second favourite feature after value tuples.

Fraction Implementation in C#

I’m not really sure why Microsoft have never bothere with implementing a Fraction primitive in .NET. I’m sure there are plenty of uses as fraction allow to preserve the maximum possible precision. I have therefore decided to create my own implementation (albeit somewhat primitive at this stage) .

My implementation automatically simplifies the fraction, so if you we to create new Function(6, 3) that would be simplified to 2. The Fraction struct implements all the arithmetic operators on itself and on Int64, float, double and decimal.

Internally the Fraction is represented as two Int64: Numerator and Denominator and is always simplified upon initialisation. I initially intended to have it as an option, however following profiling the cost of simplification is not that great and the benefits outweigh the performance drawbacks. 

Fraction has explicit conversion to Int64 (although that is bound to lose precision), float, double and decimal. It supports comparison with Int64, float, double and decimal and even supports ++ and — operations.

So far I have provided more or less complete implementation with plenty of Unit Test. Now the hard word of optimising the performance begins!

Design of Fractions

Fraction is implemented as a struct (pretty obvious choice). It takes a numerator as the first argument and denominator,  it then tries to simplify the fraction using the Euclidean algorithm, so if you were to specify 333/111 it would become 3.

The implementation supports all arithmetic operations with long, float, double and decimal and can also be converted to those type by either calling the corresponding methods or using explicit cast.

You can also create a function from either a long, float, double or decimal. Conversion from a long is quite trivial however conversion from a float, double or a decimal goes through a while loop and multiplies the floating point number until it has no decimal places. This method is relatively slow and therefore is not recommended.

Apart from that the Fraction behaves like a fist class citizen: you can compare a Fraction to any other number, divide, multiply, add, subtract, compare, increment decrement etc.

For example:

var oneThird = Fraction(1, 3);
var reciprocal = oneThird.Reciprocal();

Console.WriteLine(oneThird * reciprocal) : "1"
Console.WriteLine(++oneThird) : "4/3" - just like with an integer ++ adds 1 
Console.WriteLine(oneThird * oneThird) : "1/9"

 

Please feel free to contribute to the codebase if you feel like it

https://github.com/ebalynn/Balynn.Maths.Fraction

Implementing Value Types – Best Practices

When implementing you own struct you should take into consideration the following:

  • Use structs when you intend to create a great many of them
  • structs should be small. Large structs (i.e. structs with many fields) would lead to poor performance, since structs are copied each time.
  • Use structs when you require high density memory collections.  Structs have have a much simpler memory layout and offer excellent memory density and lack of overhead
  • Override structs Equals(…) method and implement IEquitable<T> interface to  avoid boxing.
  • Overload == and != operators
  • Override GetHashCode method.
  • structs should almost always be immutable. Actually I’ll go further and say always. Mutable structs could lead to confusion if declared as readonly. Any modifications to a mutable readonly struct will be ignored, making it a very difficult bug to spot.

Structs have also got their own limitations. Since they don’t have an object header, you cannot for example use a lock(…) on a struct since this  will result on compile time error. However Monitor.Enter(…) accepts any object, structs included, thus leading to a bug. The value  of the struct will be boxed each time and that would be equivalent to having no lock at all!

Blog at WordPress.com.

Up ↑