C# TPL faster than C++ PPL?
C# TPL faster than C++ PPL?
我编写了一个非常简单的应用程序,使用斐波那契函数来比较TPL的Parallel.ForEach
与PPL的parallel_for_each
,结果非常奇怪,在8核的pc上,c#比c++快11秒。
vs2010和vs2011预览版结果相同。
c#代码:using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Collections.Concurrent;
using System.Threading.Tasks;
using System.Diagnostics;
namespace ConsoleApplication1
{
class Program
{
static void Main(string[] args)
{
var ll = new ConcurrentQueue<Tuple<int, int>>();
var a = new int[12] { 40, 41, 42, 43, 44, 45, 46, 47, 35, 25, 36, 37 };
long elapsed = time_call(() =>
{
Parallel.ForEach(a, (n) => { ll.Enqueue(new Tuple<int, int>(n, fibonacci(n))); });
});
Console.WriteLine("TPL C# elapsed time: " + elapsed + "nr");
foreach (var ss in ll)
{
Console.WriteLine(String.Format("fib<{0}>: {1}", ss.Item1, +ss.Item2));
}
Console.ReadLine();
}
static long time_call(Action f)
{
var p = Stopwatch.StartNew();
p.Start();
f();
p.Stop();
return p.ElapsedMilliseconds;
}
Computes the nth Fibonacci number.
static int fibonacci(int n)
{
if (n < 2) return n;
return fibonacci(n - 1) + fibonacci(n - 2);
}
}
}
c++代码:
#include <windows.h>
#include <ppl.h>
#include <concurrent_vector.h>
#include <array>
#include <tuple>
#include <algorithm>
#include <iostream>
using namespace Concurrency;
using namespace std;
template <class Function>
__int64 time_call(Function&& f) {
__int64 begin = GetTickCount();
f();
return GetTickCount() - begin;
}
// Computes the nth Fibonacci number.
int fibonacci(int n) {
if (n < 2) return n;
return fibonacci(n-1) + fibonacci(n-2);
}
int wmain() {
__int64 elapsed;
array<int, 12> a ={ 40, 41, 42, 43, 44, 45, 46, 47, 35, 25, 36, 37 };
concurrent_vector<tuple<int,int>> results2;
elapsed = time_call([&]{
parallel_for_each(a.begin(), a.end(), [&](int n) {
results2.push_back(make_tuple(n, fibonacci(n)));
});
});
wcout << L"PPL time: " << elapsed << L" ms" << endl << endl;
for_each (results2.begin(), results2.end(), [](tuple<int,int>& pair) {
wcout << L"fib(" << get<0>(pair) << L"): " << get<1>(pair) << endl;
});
cin.ignore();
}
你能告诉我,我的c++代码哪里错了吗?
宽度group_task我有相同的时间像c#代码:
task_group tasks;
elapsed = time_call([&]
{
for_each(begin(a), end(a), [&](int n)
{
tasks.run([&,n]{results2.push_back(make_tuple(n, fibonacci(n)));});
});
tasks.wait();
以下是Rahul v Patil微软团队的解释
你好,
谢谢你提出这个问题。实际上,您已经确定了开销与*的默认并行相关联-特别是当迭代的次数很少,工作大小是可变的。的默认的并行开始时将工作分解为8个块(8核)。当工作完成时,工作是动态的负载平衡。默认值在大多数情况下工作得很好(大量)迭代),以及每次迭代的底层工作不是很好理解(假设你调用了一个库)——但它确实来了在某些情况下有不可接受的开销。
解决方案正是您在备选方案中确定的implemtnation。为了达到这个效果,我们将有一个并行的分区器在Visual Studio的下一个版本中被称为"简单",这将是类似于您所描述并将拥有的替代实现更好的性能。
PS: c#和c++并行的每个实现稍微使用不同的算法是如何进行迭代的,所以你会看到略有不同的性能特征取决于工作量。
对
你的代码有一些问题,让我们逐一解决:
使用递归计算斐波那契会导致进程使用过多的内存,因为它使用调用堆栈来计算结果。使用递归斐波那契意味着你不是在比较c#/c++并行框架,而是在比较调用栈机制。你可以更快地计算斐波那契:
int fibonacci(int n)
{
int curr = 1, prev = 0, total = 0;
for (int i = 0; i < n; i++)
{
int pc = curr;
curr += prev;
total += curr;
prev = pc;
}
return total;
}
对于这个函数,我必须运行至少100万次才能得到可测量的值。
使用GetTickCount64代替GetTickCount:
template <class Function>
__int64 time_call(Function&& f)
{
__int64 begin = ::GetTickCount64();
f();
return GetTickCount64() - begin;
}
使用如此小的体运行parallel_for实际上可能对性能有害。最好使用更细粒度的函子。
考虑到这些特征,下面是c++代码:// ParallelFibo.cpp : Defines the entry point for the console application.
//
#include "stdafx.h"
#include <windows.h>
#include <ppl.h>
#include <concurrent_vector.h>
#include <array>
#include <tuple>
#include <algorithm>
#include <iostream>
#include <random>
using namespace Concurrency;
using namespace std;
template <class Function>
__int64 time_call(Function&& f)
{
__int64 begin = ::GetTickCount64();
f();
return GetTickCount64() - begin;
}
// Computes the nth Fibonacci number.
inline int fibonacci(int n)
{
int curr = 1, prev = 0, total = 0;
for (int i = 0; i < n; i++)
{
int pc = curr;
curr += prev;
total += curr;
prev = pc;
}
return total;
}
#define NUMBER_OF_REPETITIONS 1000000
#define MIN_FIBO 37
#define MAX_FIBO 49
int main()
{
__int64 elapsed;
vector<int> v;
for (int i = MIN_FIBO; i < MAX_FIBO; i++)
{
v.emplace_back(i);
}
concurrent_vector<tuple<int, int>> results;
elapsed = time_call([&] {
parallel_for(MIN_FIBO, MAX_FIBO, [&](int n) {
for (int i = 0; i < NUMBER_OF_REPETITIONS; i++)
{
results.push_back(make_tuple(n, fibonacci(n)));
}
});
});
wcout << L"PPL time: " << elapsed << L" ms" << endl << endl;
cin.ignore();
return 0;
}
下面是c#中的代码:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Collections.Concurrent;
using System.Diagnostics;
namespace ParallelFiboCS
{
class Program
{
const int NUMBER_OF_REPETITIONS = 1000000;
const int MIN_FIBO = 37;
const int MAX_FIBO = 49;
static void Main(string[] args)
{
var ll = new ConcurrentQueue<Tuple<int, int>>();
var a = new int[MAX_FIBO - MIN_FIBO];
for (int i = MIN_FIBO; i < MAX_FIBO; i++)
{
a[i - MIN_FIBO] = i;
}
long elapsed = time_call(() =>
{
Parallel.ForEach(a, (n) =>
{
for (int i = 0; i < NUMBER_OF_REPETITIONS; i++)
{
ll.Enqueue(new Tuple<int, int>(n, fibonacci(n)));
}
});
});
Console.WriteLine("TPL C# elapsed time: " + elapsed + "nr");
Console.ReadLine();
}
static long time_call(Action f)
{
var p = Stopwatch.StartNew();
p.Start();
f();
p.Stop();
return p.ElapsedMilliseconds;
}
static int fibonacci(int n)
{
int curr = 1, prev = 0, total = 0;
for (int i = 0; i < n; i++)
{
int pc = curr;
curr += prev;
total += curr;
prev = pc;
}
return total;
}
}
}
对37到49之间的数字运行1200万次斐波那契计算的平均时间:
c++: 513 ms
C #: 2527 ms
GetTickCount (http://msdn.microsoft.com/en-us/library/windows/desktop/ms724408%28v=vs.85%29.aspx)函数用于测量本机端传递的时间根本不精确。描述是这样说的:
" GetTickCount函数的分辨率受限于系统计时器的分辨率,通常在10毫秒到16毫秒之间。"
根据我的经验,使用GetSystemTime在windows Vista和更高版本上产生更好的结果(如果我没记错的话,在win XP上有与tick count相同的分辨率)。或者更好的是,您可以使用其他一些提供亚粒度分辨率的api来进行细粒度测量。也许在您的情况下,构建大型数据集将更有帮助,以获得一些有意义的数据。
- ppl-如何配置本机线程数
- 在PPL任务中进行构造的目的是什么?
- PPL任务何时在UI线程上执行
- 如何使用Microsoft PPL轻量级任务调度程序实现后退
- 由 PPL parallel_for引发的 C++ 捕获错误
- 在Windows 8 metro应用程序上同步两个ppl任务
- 使用并行模式库(ppl.h)
- c++ PPL 并行工作 - 归约类 'combinable' 中的函数 max()
- 如何在循环中使用 ppl 任务和 .then
- 具有PPL和并行内存分配的线程ID
- 在数组中查找最大元素OpenMP和PPL版本的运行速度比串行代码慢得多
- 并发::parallel_fo(PPL)创建的线程太多
- ppl,如何正确使用它
- 如何在PPL中并行化任务数量可变的任务
- C# TPL faster than C++ PPL?
- ppl中的任务执行属性
- 在ppl中向任务传递参数
- 返回PPL任务的c++函数签名
- 为什么在这种情况下PPL明显慢于顺序循环和OpenMP
- c++ Intel TBB和Microsoft PPL,如何在并行循环中使用next_permutation