我正在努力在不改变算法的情况下提高程序的速度。
目前我使用DFT的这种实现:
public double[] dft(double[] data) {
int n = data.Length;
int m = n;// I use m = n / 2d;
float[] real = new float[n];
float[] imag = new float[n];
double[] result = new double[m];
float pi_div = (float)(2.0 * Math.PI / n);
for (int w = 0; w < m; w++) {
float a = w * pi_div;
for (int t = 0; t < n; t++) {
real[w] += (float)(data[t] * Math.Cos(a * t)); //thinking of threading this
imag[w] += (float)(data[t] * Math.Sin(a * t)); //and this
}
result[w] = (float)(Math.Sqrt(real[w] * real[w] + imag[w] * imag[w]) / n);
}
return result;
}
它相当慢,但有一个地方我可以看到可以改进。功能的内部部分是两个独立的任务。实数和虚数相加可以分别进行,但应始终连接以计算结果。
有什么想法吗?我尝试了一些我在网上看到的实现,但它们都崩溃了,我几乎没有线程经验。
当您有一个可以并行化的CPU绑定算法时,您可以使用Parallel
类轻松地将单线程实现转换为多线程。
在你的例子中,你有两个嵌套的循环,但外循环的迭代次数远大于你可以执行的CPU核心的数量,所以只需要并行化外循环就可以让所有核心旋转:
public double[] ParallelDft(double[] data) {
int n = data.Length;
int m = n;// I use m = n / 2d;
float[] real = new float[n];
float[] imag = new float[n];
double[] result = new double[m];
float pi_div = (float)(2.0 * Math.PI / n);
Parallel.For(0, m,
w => {
float a = w * pi_div;
for (int t = 0; t < n; t++) {
real[w] += (float)(data[t] * Math.Cos(a * t)); //thinking of threading this
imag[w] += (float)(data[t] * Math.Sin(a * t)); //and this
}
result[w] = (float)(Math.Sqrt(real[w] * real[w] + imag[w] * imag[w]) / n);
}
);
return result;
}
我已经获取了您的代码,并将外层for循环替换为Parallel.For
。在我有八个超线程核心的计算机上,我的执行速度提高了七倍。
另一种提高执行速度的方法是在CPU上使用SIMD指令集。System.Numerics.Vectors
库与Yeppp!库允许您从托管代码中调用SIMD指令,但使用这些指令来实现算法需要您做一些工作。
您应该为内部For
创建一个新的Task
,每个任务都将结果保存在线程安全字典(ConcurrentDictionary
)中。
我认为以下代码是有用的:
public ConcurrentDictionary<int, double> result = new ConcurrentDictionary<int, double>();
public void dft(double[] data)
{
int n = data.Length;
int m = n;// I use m = n / 2d;
float pi_div = (float)(2.0 * Math.PI / n);
for (int w = 0; w < m; w++)
{
var w1 = w;
Task.Factory.StartNew(() =>
{
float a = w1*pi_div;
float real = 0;
float imag=0;
for (int t = 0; t < n; t++)
{
real += (float)(data[t] * Math.Cos(a * t));
imag += (float)(data[t] * Math.Sin(a * t));
}
result.TryAdd(w1, (float) (Math.Sqrt(real*real + imag*imag)/n));
});
}
}