我有一个场景,我需要迭代6400万个组合,并且每个组合为64,000个数据项执行相同类型的处理逻辑。
我注意到,根据我如何配置循环逻辑 - 即使在并行循环中,性能也会降低或增加。
以下是3种情况:
常见数据:
int numberofSets = 3;
int set1ElementCount = 5840;
int set2ElementCount = 5840;
int set3ElementCount = 2;
int combinationsCount = 68211200; // = 5840 * 5840 * 2
int dataCount = 64000;
代码:
int[,] combinations = new int[combinationsCount, numberofSets];
// combinations = generator.Generate(); // generate combinations
/* generated format is:
[0,0,0]
[0,0,1]
[1,0,0]
...
[5839, 5839, 1]
*/
//itterate combinations
Parallel.For(0, combinationsCount, (idx, state) =>
{
int idx1 = combinations[idx, 0]; // a bit of hardcoding here since we have 3 sets of data
int idx2 = combinations[idx, 1];
int idx3 = combinations[idx, 3];
// proccess data set for each combination
for (int i = 0; i < dataCount; i++) {
// do something
}
});
结果:
〜15分钟处理1%的数据
代码:
// itterate set 1 in parallel
Parallel.For(0, set1ElementCount, (idx1, state) =>
{
// itterate set 2
for (int idx2 = 0; idx2 < set2ElementCount; idx2 ++)
{
// itterate set 3
for (int idx3 = 0; idx3 < set3ElementCount; idx3 ++)
{
// proccess data set for each combination
for (int i = 0; i < dataCount; i++)
{
// do something
}
}
}
});
结果:
〜10分钟处理1%的数据
代码:
// itterate set 1
for (int idx1 = 0; idx1 < set1ElementCount; idx1 ++)
{
// itterate set 2
for (int idx2 = 0; idx2 < set2ElementCount; idx2 ++)
{
// itterate set 3
for (int idx3 = 0; idx3 < set3ElementCount; idx3 ++)
{
// proccess data set for each combination
for(int i = 0; i < dataCount; i++)
{
// do something
}
}
}
}
结果:
超过25分钟处理1%的数据
单线程方法最慢是可以理解的。我主要担心的是多线程性能和优化。
我可以解释1和2之间的执行差异,因为它需要一些时间来生成一个新线程。因此,即使快速处理数据本身 - 创建和删除线程仍然可能效率不高。
第二个结论是 - 有时每个线程放置一个更高的CPU负载是有意义的(如案例2),因此不会有任何不必要的时间花在新线程创建上。
现在,我们正在提出一个问题。
如何获得此限制或每个线程需要处理的数据量,以获得最佳性能?有没有一种方法可以让我确定 - 一个线程最好做多少工作?
例如,在上面的示例中,我只有3组,其中两组是相同的。它可能是10套,每套都有不同的数据。在这种情况下,将有100种组合,如何配置循环:
并行设置1 - 其余是单线程
并行设置2 - 其余是单线程
并行设置1 + 2 - 其余是单线程
...
使用某种其他方法获得最佳循环性能是否有意义?线程队列?任务?
编辑:正如@arekzyla所建议的那样 - 我试图将数据拆分成块!为此,我需要将组合数组类型从jagged
更改为multidimensional
,然后执行分块逻辑:
var combinationsCount = combinations.Length;
int coreCount = 4;
int chuncSize = combinationsCount / coreCount;
List<int[][]> chunked = new List<int[][]>();
for (int i = 0; i < coreCount; i++)
{
int skip = i * chuncSize;
int take = chuncSize;
int diff = (combinationsCount - skip) - take;
if (diff < chuncSize)
take = take + diff;
var sub = combinations.Skip(skip).Take(take).ToArray();
chunked.Add(sub);
}
// iterate chunks - each on a separate core
Parallel.For(0, coreCount, new ParallelOptions() { MaxDegreeOfParallelism = coreCount }, (chunkIndex, state) =>
{
var chunk = chunked[chunkIndex];
int chunkLength = chunk.Length;
// iterate combinations per-chunk
for (int idx = 0; idx < chunkLength; idx++)
{
// itterate data here
}
}
结果:
~9分钟
嗯,一点改进仍然是一个改进。虽然,我希望有更多的东西。