我有一个场景,我需要迭代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;
  • Parallel execution

码:

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%的数据

  • Parallel execution 2

码:

// 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%的数据

  • Single-threaded execution

码:

// 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之间的执行差异,因为它需要一些时间来生成一个新线程 . 因此,即使快速处理数据本身 - 创建和删除线程仍然可能效率不高 .

第二个结论是-- sometimes 每1个线程放置一个更高的CPU负载是有意义的(如案例2),因此在新线程创建上不会花费任何不必要的时间 .

现在,我们正在提出一个问题 .

为了获得最佳性能,我如何找到每个线程需要处理的此限制或数据量?有没有一种方法可以让我确定 - 一个线程最好做多少工作?

例如,在上面的示例中,我只有3组,其中两组是相同的 . 它可能是10套,每套都有不同的数据 . 在这种情况下,将有100种组合,您可以如何配置循环:

并联设置1 - 其余为单螺纹组2并联 - 其余为单螺纹组1 2并联 - 其余为单螺纹...

使用某种其他方法获得最佳循环性能是否有意义?线程队列?任务?

Edit: 正如@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分钟

嗯,一点改进仍然是一个改进 . 虽然,我希望有更多的东西 .