A method that accepts both float [] and double [] arrays

I have a method that takes an array (float or double), a start and end index, and then does some manipulation of the elements for indexes in startIndex to endIndex.

It basically looks like this:

public void Update(float[] arr, int startIndex, int endIndex) { if (condition1) { //Do some array manipulation } else if (condition2) { //Do some array manipulation } else if (condition3) { if (subcondition1) { //Do some array manipulation } } } 

The method is longer than this and involves setting some elements to 0 or 1 or normalizing the array. The problem is that I need to pass both the float[] and double[] arrays there, and don’t want to have duplicate code that accepts instead of double[] .

Performance is also important, so I do not want to create a new double[] array, create a float array for it, perform calculations, and then update the original array, returning to float.

Is there any solution to avoid code duplication, but also as quickly as possible?

+6
source share
5 answers

You have several options. None of them matches exactly what you want, but depending on what operations you need, you may come closer.

First, you should use the general method, in which the generic type is limited, but the only operations you can perform are limited:

 public void Update<T>(T[] arr, int startIndex, int endIndex) : IComarable { if (condition1) { //Do some array manipulation } else if (condition2) { //Do some array manipulation } else if (condition3) { if (subcondition1) { //Do some array manipulation } } } 

And the conditions and array manipulations in this function will be limited to expressions that use the following forms:

 if (arr[Index].CompareTo(arr[OtherIndex])>0) arr[Index] = arr[OtherIndex]; 

This is enough to do things like finding a minimum or maximum or sorting elements in an array. He cannot do addition / subtraction / etc., Therefore, he cannot, say, find the average value. You can compensate for this by creating your overloaded delegates for any additional methods you need:

 public void Update<T>(T[] arr, int startIndex, int endIndex, Func<T,T> Add) : IComarable { //... arr[Index] = Add(arr[OtherIndex] + arr[ThirdIndex]); } 

You will need a different argument for each operation that you are actually using, and I don’t know how it will be performed (this last part will be the topic here: I have not tested any of this, but performance seems to be critical for this question).

Another option that came to mind is the dynamic type:

 public void Update(dynamic[] arr, int startIndex, int endIndex) { //...logic here } 

This should work, but for something repeating over and over again, as you say, I don't know what it will do with performance.

You can combine this option with another answer (now deleted) to return some type safety:

 public void Update(float[] arr, int startIndex, int endIndex) { InternalUpdate(arr, startIndex, endIndex); } public void Update(double[] arr, int startIndex, int endIndex) { InternalUpdate(arr, startIndex, endIndex); } public void InternalUpdate(dynamic[] arr, int startIndex, int endIndex) { //...logic here } 

Another idea is to double all the floats:

 public void Update(float[] arr, int startIndex, int endIndex) { Update( Array.ConvertAll(arr, x => (double)x), startIndex, endIndex); } public void Update(double[] arr, int startIndex, int endIndex) { //...logic here } 

Again, this will redistribute the array, and therefore, if this causes a performance problem, we will have to look elsewhere.

If (and only if) everything else does not work, and the profiler shows that this is a critical section of the performance of your code, you can simply overload this method and implement the logic twice. It is not ideal in terms of code maintenance, but if a performance problem is well established and documented, it may be worth a copy of the headache of pasta. I included a sample comment to indicate how you can document this:

 /****************** WARNING: Profiler tests conducted on 12/29/2014 showed that this is a critical performance section of the code, and that separate double/float implementations of this method produced a XX% speed increase. If you need to change anything in here, be sure to change BOTH SETS, and be sure to profile both before and after, to be sure you don't introduce a new performance bottleneck. */ public void Update(float[] arr, int startIndex, int endIndex) { //...logic here } public void Update(double[] arr, int startIndex, int endIndex) { //...logic here } 

One final point to explore here is that C # contains a generic type of ArraySegment<T> , which may be useful for this.

+4
source

Just an idea. I don't know what the performance implications are, but it helped me fall asleep: P

 public void HardcoreWork(double[] arr){HardcoreWork(arr, null);} public void HardcoreWork(float[] arr){HardcoreWork(null, arr);} public struct DoubleFloatWrapper { private readonly double[] _arr1; private readonly float[] _arr2; private readonly bool _useFirstArr; public double this[int index] { get { return _useFirstArr ? _arr1[index] : _arr2[index]; } } public int Length { get { return _useFirstArr ? _arr1.Length : _arr2.Length; } } public DoubleFloatWrapper(double[] arr1, float[] arr2) { _arr1 = arr1; _arr2 = arr2; _useFirstArr = _arr1 != null; } } private void HardcoreWork(double[] arr1, float[] arr2){ var doubleFloatArr = new DoubleFloatWrapper(arr1, arr2); var len = doubleFloatArr.Length; double sum = 0; for(var i = 0; i < len; i++){ sum += doubleFloatArr[i]; } } 

Do not forget that if the number of elements you have is ridiculously small, you can simply use the combined memory, which will give you zero memory overhead.

 ThreadLocal<double[]> _memoryPool = new ThreadLocal<double[]>(() => new double[100]); private void HardcoreWork(double[] arr1, float[] arr2){ double[] array = arr1; int arrayLength = arr1 != null ? arr1.Length : arr2.Length; if(array == null) { array = _memoryPool.Value; for(var i = 0; i < arr2.Length; i++) array[i] = arr2[i]; } for(var i = 0; i < 1000000; i++){ for(var k =0; k < arrayLength; k++){ var a = array[k] + 1; } } } 
+3
source

How to implement a method using generics? An abstract base class can be created for the core business logic:

 abstract class MyClass<T> { public void Update(T[] arr, int startIndex, int endIndex) { if (condition1) { //Do some array manipulation, such as add operation: T addOperationResult = Add(arr[0], arr[1]); } else if (condition2) { //Do some array manipulation } else if (condition3) { if (subcondition1) { //Do some array manipulation } } } protected abstract T Add(T x, T y); } 

Then implement for each data type an inheriting class configured for operations of the type:

 class FloatClass : MyClass<float> { protected override float Add(float x, float y) { return x + y; } } class DoubleClass : MyClass<double> { protected override double Add(double x, double y) { return x + y; } } 
+2
source

John's comment on macros, although a completely inaccurate characterization of C ++ templates, made me think of a preprocessor.

The C # preprocessor is nowhere near as powerful as C (which inherits from C ++), but it is still capable of handling everything you need except duplication itself:

 partial class MyClass { #if FOR_FLOAT using Double = System.Single; #endif public void Update(Double[] arr, int startIndex, int endIndex) { // do whatever you want, using Double where you want the type to change, and // either System.Double or double where you don't } } 

Now you need to include two copies of the file in your project, one of which has an additional

 #define FOR_FLOAT 

up. (It should be easy enough to automate adding this)

Unfortunately, the /define compiler option applies to the entire assembly, not the file, so you cannot use a hard link to include the file twice and only have a specific character for one. However, if you can transfer two implementations that are in different assemblies, you can include the same source file in both options, using project parameters to define FOR_FLOAT in one of them.

I am still a proponent of using templates in C ++ / CLI.

+2
source

Most codes are not so performance critical that it takes time to convert from float to double and vice versa, which causes a problem:

 public void Update(float[] arr, int startIndex, int endIndex) { double[] darr = new double[arr.Length]; for(int i=startIndex; i<endIndex; i++) darr[i] = (double) arr[i]; Update(darr, startIndex, endIndex); for(int j=startIndex; j<endIndex; j++) arr[j] = darr[j]; } 

Here is a thought experiment. Imagine that instead of copying you duplicated the double[] version code to make the float[] version. Imagine that you optimized the float[] version as much as needed.

Your question then: does copying really take so long? Note that instead of saving two versions of the code, you could spend time improving the performance of the double[] version.

Even if you could use generics for this, it is possible that the double[] version would like to use different code from the float[] version to optimize performance.

0
source

Source: https://habr.com/ru/post/980281/


All Articles