昂贵的C#操作,希望提高性能
这将是一个很长的问题,请提前道歉。我并不期待一个完整的代码解决方案,我正在寻找一些来自不同视角和比我更有经验的人的意见 我的公司正在为一种产品开发软件,该产品使用红外相机的胶片进行一些相当昂贵的计算,其中每个像素都包含一个温度值。这些方法中成本最高的称为热信号重建(如果您感兴趣,可以在这里阅读)。它基本上对每个像素随时间(帧数)进行多项式拟合。我的C#实现如下所示:昂贵的C#操作,希望提高性能,c#,performance,parallel-processing,C#,Performance,Parallel Processing,这将是一个很长的问题,请提前道歉。我并不期待一个完整的代码解决方案,我正在寻找一些来自不同视角和比我更有经验的人的意见 我的公司正在为一种产品开发软件,该产品使用红外相机的胶片进行一些相当昂贵的计算,其中每个像素都包含一个温度值。这些方法中成本最高的称为热信号重建(如果您感兴趣,可以在这里阅读)。它基本上对每个像素随时间(帧数)进行多项式拟合。我的C#实现如下所示: public static double[] PolynomialRegression(in double[] xValu
public static double[] PolynomialRegression(in double[] xValues, in double[] yValues, byte order)
{
Debug.Assert(xValues != null && yValues != null);
Debug.Assert(xValues.Length == yValues.Length);
Debug.Assert(xValues.Length != 0 || yValues.Length != 0);
int dataSamples = xValues.Length;
double[] result = new double[order + 1];
// array containing N,sigma(xi),sigma(xi^2),sigma(xi^3)...sigma(xi^2*poly_order), where N=number of samples
double[] sigmaX = new double[2 * order + 1];
for (var index = 0U; index < sigmaX.Length; ++index)
{
sigmaX[index] = 0.0;
for (var dataPoint = 0U; dataPoint < dataSamples; ++dataPoint)
sigmaX[index] += System.Math.Pow(xValues[(int)dataPoint], index);
}
// array containing sigma(yi),sigma(xi*yi),sigma(xi^2*yi)...sigma(xi^poly_order*yi)
double[] sigmaY = new double[order + 1];
for (var pOrder = 0U; pOrder < sigmaY.Length; ++pOrder)
{
sigmaY[pOrder] = 0.0;
for (var dataPoint = 0U; dataPoint < dataSamples; ++dataPoint)
sigmaY[pOrder] += System.Math.Pow(xValues[(int)dataPoint], pOrder) * yValues[(int)dataPoint];
}
// equation system's augmented normal matrix
int matrixRows = order + 1;
int matrixCols = order + 2;
double[,] matrix = new double[matrixRows, matrixCols];
for (var row = 0U; row < matrixRows; ++row)
for (var col = 0U; col < matrixCols - 1; ++col)
matrix[row, col] = sigmaX[row + col];
for (var row = 0U; row < matrixRows; ++row)
matrix[row, order + 1] = sigmaY[row];
// pivotisation of matrix
for (var pivotRow = 0U; pivotRow < matrixRows; ++pivotRow)
for (var lowerRow = pivotRow + 1U; lowerRow < matrixRows; ++lowerRow)
if (matrix[pivotRow, pivotRow] < matrix[lowerRow, pivotRow])
for (var col = 0U; col < matrixCols; ++col)
{
double temp = matrix[pivotRow, col];
matrix[pivotRow, col] = matrix[lowerRow, col];
matrix[lowerRow, col] = temp;
}
// Gaussian elimination
for (var pivotRow = 0U; pivotRow < matrixRows; ++pivotRow)
for (var lowerRow = pivotRow + 1U; lowerRow < matrixRows; ++lowerRow)
{
double ratio = matrix[lowerRow, pivotRow] / matrix[pivotRow, pivotRow];
for (var col = 0U; col < matrixCols; ++col)
matrix[lowerRow, col] -= ratio * matrix[pivotRow, col];
}
// back-substitution
for (var row = (short)order; row >= 0; --row)
{
result[row] = matrix[row, order + 1];
for (var col = 0U; col < matrixCols - 1; ++col)
if (col != row)
result[row] -= matrix[row, col] * result[col];
result[row] /= matrix[row, row];
}
return result;
}
publicstaticdouble[,,]热信号重构(列表热信号,字节多序)
{
分辨率胶片分辨率=热胶片[0]。分辨率;
uint宽度=胶片分辨率。宽度;
uint高度=胶片分辨率。高度;
int frames=热胶片计数;
双精度[,]结果=新双精度[polyOrder+1,高度,宽度];
//使用帧索引作为多边形拟合的x值
列表框架索引=新列表(框架);
用于(可变帧=0U;帧<帧;+帧)
frameindex.Add(frame);
//制作热膜的局部副本,并用差异图像填充
List localThermalFilm=新列表(帧);
用于(可变帧=0U;帧<帧;+帧)
添加(新帧(胶片分辨率));
并行。对于(0U,帧,帧=>
{
用于(变量行=0U;行<高度;++行)
用于(变量列=0U;列<宽度;++列)
localThermalFilm[(int)frame]。数据[行,列]=thermalFilm[(int)frame]。数据[行,列]-thermalFilm[0]。数据[行,列];
});
//通过查找具有最大平均像素值的帧来确定闪点
double maxAverage=double.MinValue;
uint最大索引=0U;
并行。对于(0U,帧,帧=>
{
双平均=Math.MatrixMean(localThermalFilm[(int)frame].Data);
如果(平均值>最大平均值)
{
最大平均值=平均值;
maxIndex=(uint)帧;
}
});
//从胶片上移除闪点之前的框架,包括其本身
localThermalFilm.RemoveRange(0,(int)maxIndex+1);
frameIndexes.RemoveRange(0,(int)maxIndex+1);
帧-=(int)最大索引+1;
//计算所有像素和帧索引的以10为底的对数
并行。对于(0U,帧,帧=>
{
用于(变量行=0U;行<高度;++行)
用于(变量列=0U;列<宽度;++列)
localThermalFilm[(int)frame]。数据[行,列]=System.Math.Log10(localThermalFilm[(int)frame]。数据[行,列];
frameIndexes[(int)frame]=System.Math.Log10(frameIndexes[(int)frame]);
});
//对每个像素执行多项式拟合
平行。对于(0U,高度,行=>
{
用于(变量列=0U;列<宽度;++列)
{
//提取当前像素的多边形拟合输入y值
double[]像素值=新的双[帧];
用于(可变帧=0U;帧<帧;+帧)
像素值[frame]=localThermalFilm[(int)frame]。数据[row,col];
//(…)执行一些值验证
//适合当前像素的多边形-这是最长的一步
double[]系数=数学多项式回归(FrameIndexValidated.ToArray(),pixelValuesValidated.ToArray(),polyOrder);
//插入到系数图像结果数组中
对于(var系数=0U;系数
如您所见,在帧上执行多个操作的几个并行循环是按顺序执行的,多项式拟合(Math.PolynomialRegression)是最后一个也是最昂贵的。这是一个包含多项式拟合算法的函数,我自己拼凑的,因为它在标准系统中不存在。Math library和我在Math.NET library中尝试的唯一一个函数实际上比我编写的运行得慢。我的代码基于Rosetta代码中给出的示例:
我的观点是,我以前在非托管C++中写了整个算法,我们公司决定远离这个问题,因为我们使用的GUI框架有一些许可问题,现在使用C.Y./.NET。一个直接的比较旧的非托管C++代码和上面在托管C语言中发布的代码,显示了C代码比C++代码要长48%(!!),即使算法是相同的。我知道C++是一种高级的、管理的语言,因此比C++有更大的翻译距离,所以我完全希望它运行得慢一些,但我没想到它会这么糟糕。48%是一件大事,这让我相信我可能做错了什么。另一方面,我还没有太多的经验,所以如果我非常诚实的话,我也不知道在这种情况下会发生什么
到目前为止,我所尝试的:
- 在顺序运行各个循环和并行运行之间切换,所有循环都像上面那样并行运行,速度最快
- 调整单个并行化循环实例访问的变量(例如,每次不访问相同的分辨率对象,但在开始循环之前声明单独的宽度和高度变量),这已经大大提高了性能,但之后仍保留了48%
- 尝试Parallel.ForEach(Partitioner.Create(0,frames)…)方法,即使用Partitioner类对数据块进行更粗略的分区,这没有帮助,会使代码运行变慢
- 尽可能优化调用的其他函数以及调用方的代码
编辑:在TSR中为每个请求和我的多项式回归添加前三个循环体
public static double UIntPow(double @base, uint power)
{
if (power == 0)
return 1.0;
else if (power == 1)
return @base;
else
return @base * UIntPow(@base, power - 1);
}
public static double UIntPow(double @base, uint power)
{
double result = 1.0;
while (power != 0)
{
if ((power & 1) == 1)
result *= @base;
@base *= @base;
power >>= 1;
}
return result;
}