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.
我有一个方法接受一个数组(浮点数或双精度数),开始和结束索引,然后对startIndex到endIndex范围内的索引进行一些元素操作。
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.
方法比这长,并涉及将一些元素设置为0或1,或者规范化数组。问题是我需要在那里传递float []和double []数组,并且不希望有一个重复的代码接受double []。
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.
性能也很关键,所以我不想创建一个新的double []数组,向其投射浮点数组,执行计算,然后通过强制转换为浮点数来更新原始数组。
Is there any solution to it that avoids duplicated code, but is also as fast as possible?
是否有任何解决方案可以避免重复的代码,但也尽可能快?
5 个解决方案
#1
4
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.
这里要探讨的最后一个项目是,C#包含一个通用的ArraySegment
#2
3
Just an idea. I have no idea what the performance implications are, but this helped me to go to sleep :P
只是一个想法。我不知道性能影响是什么,但这有助于我入睡: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;
}
}
}
#3
2
What about implementing the method using generics? An abstract base class can be created for your 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 per data type an inheriting class tuned to type-specific operations:
然后为每种数据类型实现一个继承类,该类调整为特定于类型的操作:
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;
}
}
#4
2
John's comment about macros, although completely inaccurate characterization of C++ templates, got me thinking about the preprocessor.
John对宏的评论,虽然对C ++模板的描述完全不准确,让我想到了预处理器。
C#'s preprocessor is nowhere near as powerful as C's (which C++ inherits), but it still is able to handle everything you need except the duplication itself:
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
}
}
Now, you need to include two copies of the file in your project, one of which has an extra
现在,您需要在项目中包含该文件的两个副本,其中一个副本有额外的
#define FOR_FLOAT
line at the top. (Should be fairly easy to automate adding that)
在顶部的线。 (应该相当容易自动添加)
Unfortunately, the /define
compiler option applies to the entire assembly, not per-file, so you can't use a hardlink to include the file twice and have the symbol only defined for one. However, if you can tolerate the two implementations being in different assemblies, you can include the same source file into both, using the project options to define FOR_FLOAT
in one of them.
不幸的是,/ define编译器选项适用于整个程序集,而不是每个文件,因此您不能使用硬链接来包含文件两次并且只为一个文件定义符号。但是,如果您可以容忍这两个实现位于不同的程序集中,则可以在两者中包含相同的源文件,使用项目选项在其中一个中定义FOR_FLOAT。
I still advocate using templates in C++/CLI.
我仍然主张在C ++ / CLI中使用模板。
#5
0
Most code isn't so performance-critical that taking the time to convert from float
to double
and back causes a problem:
大多数代码都不是那么性能至关重要,以至于花时间从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];
}
Here's a thought experiment. Imagine that instead of copying, you duplicated the code of the double[]
version to make a float[]
version. Imagine that you optimized the float[]
version as much as necessary.
这是一个思想实验。想象一下,不是复制,而是复制double []版本的代码以生成float []版本。想象一下,您根据需要优化了float []版本。
Your question is then: does the copying really take that long? Consider that instead of maintaining two versions of the code, you could spend your time improving the performance of the double[]
version.
那么你的问题是:复制真的需要那么久吗?考虑到代替维护两个版本的代码,您可以花时间提高double []版本的性能。
Even if you had been able to use generics for this, it's possible that the double[]
version would want to use different code from the float[]
version in order to optimize performance.
即使您已经能够使用泛型,double []版本也可能希望使用float []版本中的不同代码以优化性能。
#1
4
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.
这里要探讨的最后一个项目是,C#包含一个通用的ArraySegment
#2
3
Just an idea. I have no idea what the performance implications are, but this helped me to go to sleep :P
只是一个想法。我不知道性能影响是什么,但这有助于我入睡: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;
}
}
}
#3
2
What about implementing the method using generics? An abstract base class can be created for your 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 per data type an inheriting class tuned to type-specific operations:
然后为每种数据类型实现一个继承类,该类调整为特定于类型的操作:
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;
}
}
#4
2
John's comment about macros, although completely inaccurate characterization of C++ templates, got me thinking about the preprocessor.
John对宏的评论,虽然对C ++模板的描述完全不准确,让我想到了预处理器。
C#'s preprocessor is nowhere near as powerful as C's (which C++ inherits), but it still is able to handle everything you need except the duplication itself:
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
}
}
Now, you need to include two copies of the file in your project, one of which has an extra
现在,您需要在项目中包含该文件的两个副本,其中一个副本有额外的
#define FOR_FLOAT
line at the top. (Should be fairly easy to automate adding that)
在顶部的线。 (应该相当容易自动添加)
Unfortunately, the /define
compiler option applies to the entire assembly, not per-file, so you can't use a hardlink to include the file twice and have the symbol only defined for one. However, if you can tolerate the two implementations being in different assemblies, you can include the same source file into both, using the project options to define FOR_FLOAT
in one of them.
不幸的是,/ define编译器选项适用于整个程序集,而不是每个文件,因此您不能使用硬链接来包含文件两次并且只为一个文件定义符号。但是,如果您可以容忍这两个实现位于不同的程序集中,则可以在两者中包含相同的源文件,使用项目选项在其中一个中定义FOR_FLOAT。
I still advocate using templates in C++/CLI.
我仍然主张在C ++ / CLI中使用模板。
#5
0
Most code isn't so performance-critical that taking the time to convert from float
to double
and back causes a problem:
大多数代码都不是那么性能至关重要,以至于花时间从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];
}
Here's a thought experiment. Imagine that instead of copying, you duplicated the code of the double[]
version to make a float[]
version. Imagine that you optimized the float[]
version as much as necessary.
这是一个思想实验。想象一下,不是复制,而是复制double []版本的代码以生成float []版本。想象一下,您根据需要优化了float []版本。
Your question is then: does the copying really take that long? Consider that instead of maintaining two versions of the code, you could spend your time improving the performance of the double[]
version.
那么你的问题是:复制真的需要那么久吗?考虑到代替维护两个版本的代码,您可以花时间提高double []版本的性能。
Even if you had been able to use generics for this, it's possible that the double[]
version would want to use different code from the float[]
version in order to optimize performance.
即使您已经能够使用泛型,double []版本也可能希望使用float []版本中的不同代码以优化性能。