Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Method that accepts both float[] and double[] arrays

Tags:

arrays

c#

I have a method that accepts an array (float or double), start and end index and then does some element manipulations for indexes in startIndex to endIndex range.

Basically it 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
       }
   }
}

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 float[] and double[] arrays there, and don't want to have a duplicated code that accepts double[] instead.

Performance is also critical, so I don't want to create a new double[] array, cast float array to it, perform calcs, then update original array by casting back to floats.

Is there any solution to it that avoids duplicated code, but is also as fast as possible?

like image 507
Michal Avatar asked Dec 30 '14 00:12

Michal


2 Answers

You have a few options. None of them match exactly what you want, but depending on what kind of operations you need you might get close.

The first is to use a generic method where the generic type is restricted, but the only operations you can do 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 manipulation in that function would 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 find the minimum, or maximum, or sort the items in the array. It can't do addition/subtraction/etc, so this couldn't, say, find the average. You can make up for this by creating your own 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'd need another argument for each operation that you actually use, and I don't know how that will perform (that last part's gonna be a theme here: I haven't benchmarked 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 called over and over like you claim I don't know what it would do to the performance.

You can combine this option with another answer (now deleted) to give back 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
}

One other idea is to cast all the floats to doubles:

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 re-allocate the array, and so if that causes a performance issue we'll have to look elsewhere.

If (and only if) all else fails, and a profiler shows that this is a critical performance section of your code, you can just overload the method and implement the logic twice. It's not ideal from a code maintenance standpoint, but if the performance concern is well-established and documented, it can be the worth the copy pasta headache. I included a sample comment to indicate how you might want to 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 item to explore here, is that C# includes a generic ArraySegment<T> type, that you may find useful for this.

like image 109
Joel Coehoorn Avatar answered Oct 28 '22 20:10

Joel Coehoorn


Just an idea. I have no idea what the performance implications are, but this helped me to go to sleep :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];
    }
}

Don't forget that if the amount of elements you have is ridiculously small, you can just use pooled 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;
        }
    }
}
like image 36
Erti-Chris Eelmaa Avatar answered Oct 28 '22 19:10

Erti-Chris Eelmaa