C# 并行的性能令人失望

C# 并行的性能令人失望,c#,performance,task-parallel-library,C#,Performance,Task Parallel Library,我正试图通过使用Parallel.For来加快我的计算速度。我有一个Intel Core i7 Q840 CPU,有8个内核,但与顺序for循环相比,我只获得了4的性能比。对于,这是否与使用并行程序所能达到的效果一样好,还是可以对方法调用进行微调以提高性能 这是我的测试代码,顺序: var loops = 200; var perloop = 10000000; var sum = 0.0; for (var k = 0; k < loops; ++k) { var sumk =

我正试图通过使用
Parallel.For
来加快我的计算速度。我有一个Intel Core i7 Q840 CPU,有8个内核,但与顺序
for
循环相比,我只获得了4的性能比。对于,这是否与使用并行程序所能达到的效果一样好,还是可以对方法调用进行微调以提高性能

这是我的测试代码,顺序:

var loops = 200;
var perloop = 10000000;

var sum = 0.0;
for (var k = 0; k < loops; ++k)
{
    var sumk = 0.0;
    for (var i = 0; i < perloop; ++i) sumk += (1.0 / i) * i;
    sum += sumk;
}
但是没有用。我不清楚为什么不是所有的CPU功率都分配给并行计算

我注意到有人就此提出了类似的问题,结果更令人失望。然而,这个问题还涉及第三方库中的低级并行化。我主要关心的是核心库中基本操作的并行化

更新

有人在一些评论中向我指出,我使用的CPU只有4个物理内核,如果启用了超线程,则系统可以看到8个物理内核。为此,我禁用了超线程并重新进行了基准测试

禁用“超线程”后,我的计算速度现在加快了,并行循环和(我认为是)顺序循环都加快了。
for
循环期间的CPU利用率高达约45%(!!!),而
并行循环期间的CPU利用率为100%

循环的
计算时间为15.6秒(比启用超线程时快一倍多),并行
循环的
计算时间为6.2秒。
(比启用超线程时快25%)。与
并行的性能比。For
现在只有2.5,运行在4个实际内核上


因此,尽管禁用了超线程,但性能比仍然大大低于预期。另一方面,有趣的是,在
for
循环期间,CPU利用率如此之高?这个循环中是否也会进行某种内部并行化?

Parallel.For和Parallel.ForEach将使用它认为合适的并行度,平衡设置和拆卸线程的成本以及它期望每个线程执行的工作


请注意,即使要为每个核心启动一个线程,上下文切换、问题、资源锁和其他问题也可能会阻止您实现线性可伸缩性(通常,不一定是特定代码示例)。

使用全局变量可能会导致严重的同步问题,即使您不使用锁。当您为变量赋值时,每个核心必须访问系统内存中的同一位置,或者等待另一个核心完成后才能访问它。 通过使用打火机方法在操作系统级别原子地向总和添加一个值,可以避免没有锁的损坏,但仍然会由于争用而导致延迟

正确的方法是更新线程局部变量以创建部分和,并在末尾将所有部分和添加到单个全局和。有一个重载,它就是这样做的。MSDN甚至有一个例子在

int[]nums=Enumerable.Range(01000).ToArray();
长总计=0;
//使用类型参数将小计设置为long,而不是int
对于(0,nums.Length,()=>0,(j,loop,subtotal)=>
{
小计+=nums[j];
返回小计;
},
(x) =>联锁。添加(参考总数,x)
);

每个线程更新自己的小计值,并使用Interlocked.Add更新全局总计。完成后添加。

我认为计算增益很低,因为您的代码“太容易”在每个迭代中处理其他任务-因为并行。对于每个迭代,只需创建新任务,所以这将需要时间在线程中为它们提供服务。我会这样做:

int[] nums = Enumerable.Range(0, 1000000).ToArray();
long total = 0;

Parallel.ForEach(
    Partitioner.Create(0, nums.Length),
    () => 0,
    (part, loopState, partSum) =>
    {
        for (int i = part.Item1; i < part.Item2; i++)
        {
            partSum += nums[i];
        }
        return partSum;
    },
    (partSum) =>
    {
        Interlocked.Add(ref total, partSum);
    }
);
int[]nums=Enumerable.Range(01000).ToArray();
长总计=0;
并行ForEach(
创建(0,nums.Length),
() => 0,
(部分、循环状态、部分和)=>
{
对于(int i=part.Item1;i
{
联锁。添加(参考总计、部分合计);
}
);

Partitioner将为每个任务创建作业的最佳部分,使用线程执行服务任务的时间将更少。如果可以,请对该解决方案进行基准测试,并告诉我们它是否加快了速度。

foreach vs parallel for each示例

    for (int i = 0; i < 10; i++)
    {
        int[] array = new int[] { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29 };
        Stopwatch watch = new Stopwatch();
        watch.Start();
        //Parallel foreach
        Parallel.ForEach(array, line =>
        {
            for (int x = 0; x < 1000000; x++)
            {

            }

        });

        watch.Stop();
        Console.WriteLine("Parallel.ForEach {0}", watch.Elapsed.Milliseconds);
        watch = new Stopwatch();
        //foreach
        watch.Start();
        foreach (int item in array)
        {
            for (int z = 0; z < 10000000; z++)
            {

            }
        }
        watch.Stop();
        Console.WriteLine("ForEach {0}", watch.Elapsed.Milliseconds);

        Console.WriteLine("####");
    }
    Console.ReadKey();
for(int i=0;i<10;i++)
{
int[]数组=新的int[]{0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29};
秒表=新秒表();
watch.Start();
//平行foreach
Parallel.ForEach(数组,行=>
{
对于(int x=0;x<1000000;x++)
{
}
});
看,停;
WriteLine(“Parallel.ForEach{0}”,watch.appeased.millizes);
手表=新秒表();
//弗雷奇
watch.Start();
foreach(数组中的int项)
{
对于(int z=0;z<10000000;z++)
{
}
}
看,停;
WriteLine(“ForEach{0}”,watch.appeased.millizes);
Console.WriteLine(“######”);
}
Console.ReadKey();

我的CPU


英特尔®核心™ i7-620M处理器(4M高速缓存,2.66GHz)

并行循环不需要对求和变量进行锁定吗?@SteveB我相信你是对的。但是,使用显式锁不会提高性能,对吗?:-)你确定你有8个核吗?例如,根据i7-840QM,由于具有多线程功能,操作系统可以将4个内核视为8个。超线程将愚弄操作系统,使其知道您有8个内核。你可以在bios中关闭它。超线程技术在许多程序的日常工作中运行良好。正如@Magnus所说,它将“fo
        int[] nums = Enumerable.Range(0, 1000000).ToArray();
        long total = 0;

        // Use type parameter to make subtotal a long, not an int
        Parallel.For<long>(0, nums.Length, () => 0, (j, loop, subtotal) =>
        {
            subtotal += nums[j];
            return subtotal;
        },
            (x) => Interlocked.Add(ref total, x)
        );
int[] nums = Enumerable.Range(0, 1000000).ToArray();
long total = 0;

Parallel.ForEach(
    Partitioner.Create(0, nums.Length),
    () => 0,
    (part, loopState, partSum) =>
    {
        for (int i = part.Item1; i < part.Item2; i++)
        {
            partSum += nums[i];
        }
        return partSum;
    },
    (partSum) =>
    {
        Interlocked.Add(ref total, partSum);
    }
);
    for (int i = 0; i < 10; i++)
    {
        int[] array = new int[] { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29 };
        Stopwatch watch = new Stopwatch();
        watch.Start();
        //Parallel foreach
        Parallel.ForEach(array, line =>
        {
            for (int x = 0; x < 1000000; x++)
            {

            }

        });

        watch.Stop();
        Console.WriteLine("Parallel.ForEach {0}", watch.Elapsed.Milliseconds);
        watch = new Stopwatch();
        //foreach
        watch.Start();
        foreach (int item in array)
        {
            for (int z = 0; z < 10000000; z++)
            {

            }
        }
        watch.Stop();
        Console.WriteLine("ForEach {0}", watch.Elapsed.Milliseconds);

        Console.WriteLine("####");
    }
    Console.ReadKey();