接受float []和double []数组的方法

时间:2014-12-30 00:16:00

标签: c# arrays

我有一个接受数组(float或double),start和end索引的方法,然后对startIndex到endIndex范围内的索引进行一些元素操作。

基本上它看起来像这样:

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

方法比此长,并且涉及将一些元素设置为0或1,或者规范化数组。 问题是我需要在那里传递float[]double[]数组,并且不希望拥有一个接受double[]的重复代码。

性能也很关键,因此我不想创建新的double[]数组,向其投射浮点数组,执行计算,然后通过强制转换为浮点数来更新原始数组。

是否有任何解决方案可以避免重复的代码,但也尽可能快?

5 个答案:

答案 0 :(得分:4)

您有几个选择。它们都不符合您的要求,但根据您需要的操作类型,您可能会接近。

第一种方法是使用泛型类型受限制的泛型方法,但是你可以做的唯一操作是有限的:

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

该函数中的条件和数组操作仅限于使用以下形式的表达式:

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

这足以执行查找最小值或最大值或对数组中的项进行排序等操作。它不能进行加/减/等,所以这也就是说,找不到平均值。您可以通过为所需的任何其他方法创建自己的重载代理来弥补这一点:

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

你需要为你实际使用的每个操作提出另一个论点,我不知道它将如何表现(最后一部分将成为这里的一个主题:我没有&#39;对这一点进行基准测试,但性能似乎对这个问题至关重要。)

我想到的另一个选择是dynamic类型:

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

这应该可行,但是对于一些被称为反复的事情,你声称我不知道它会对演出产生什么影响。

您可以将此选项与另一个答案(现已删除)结合使用,以提供某种类型的安全性:

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
}

另一个想法是将所有花车投射到双打:

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
}

同样,这将重新分配数组,因此,如果这会导致性能问题,我们将不得不寻找其他地方。

如果(且仅当)所有其他方法都失败,并且分析器显示这是代码的关键性能部分,您可以重载该方法并实施逻辑两次。从代码维护的角度来看,它并不理想,但如果性能问题得到充分证实和记录,则复制意大利面的问题可能是值得的。我添加了一个示例注释,以指出您可能希望如何记录此内容:

/******************
   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
}

这里要探讨的最后一项是,C#包含一个通用的ArraySegment<T>类型,您可能会发现它很有用。

答案 1 :(得分:3)

只是一个想法。我不知道性能影响是什么,但这有助于我入睡: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];
    }
}

不要忘记,如果您拥有的元素数量非常小,您可以使用池化内存,这将为您带来零内存开销。

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;
        }
    }
}

答案 2 :(得分:2)

使用泛型实现该方法怎么样?可以为您的核心业务逻辑创建一个抽象基类:

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);
}

然后为每种数据类型实现一个继承类,该类调整为特定于类型的操作:

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;
    }
}

答案 3 :(得分:2)

John关于宏的评论,虽然对C ++模板的描述完全不准确,让我想到了预处理器。

C#的预处理器远不如C&C(C ++继承的那样强大),但除了复制本身之外,它仍然能够处理所需的一切:

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

现在,您需要在项目中包含该文件的两个副本,其中一个副本具有额外的

#define FOR_FLOAT

排在最前面。 (应该很容易自动添加)

不幸的是,/define编译器选项适用于整个程序集,而不是每个文件,因此您不能使用硬链接将文件包含两次,并且只为一个文件定义符号。但是,如果您可以容忍这两个实现位于不同的程序集中,则可以在两者中包含相同的源文件,使用项目选项在其中一个中定义FOR_FLOAT

我仍然主张在C ++ / CLI中使用模板。

答案 4 :(得分:0)

大多数代码都不是性能至关重要的,因此需要花时间从float转换为double并导致问题:

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];
}

这是一个思想实验。想象一下,您没有复制,而是复制double[]版本的代码以生成float[]版本。想象一下,您根据需要优化了float[]版本。

那么你的问题是:复制真的需要那么久吗?请注意,您可以花时间提高double[]版本的性能,而不是维护代码的两个版本。

即使您已经能够使用泛型,double[]版本也可能希望使用float[]版本中的不同代码来优化性能。< / p>