2012-01-03 26 views
5

Tôi đã mã hóa một ứng dụng rất đơn giản sử dụng Fibonacci fonction để so sánh số Parallel.ForEach của TPL so với số parallel_for_each của PPL và kết quả thực sự lạ, trên máy tính có 8 lõi, C# là 11 giây nhanh hơn rồi C++.C# TPL nhanh hơn C++ PPL?

Kết quả tương tự trên cả bản xem trước vs2010 và so với năm 2011. # mã

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 + "\n\r"); 
       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++ mã:

#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(); 
} 

Bạn có thể xin vui lòng chỉ cho tôi, nơi một phần của c của tôi ++ mã tôi đang sai?

Width group_task i có cùng thời gian như C# code:

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(); 
+3

Bạn có thể vui lòng đăng mã nguồn được định dạng đúng không? Các liên kết trống giữa các câu lệnh đặc biệt khó chịu, vì chúng buộc tôi phải di chuyển khá nhiều. –

+0

Điều gì nhảy ra ngoài với tôi là bạn đang sử dụng các bộ sưu tập khác nhau trong các ví dụ của bạn. Phiên bản C# sử dụng hàng đợi trong khi phiên bản C++ sử dụng vectơ. –

+0

Ngoài ra, bạn có chỉ định thời gian gọi hàm 'fibonacci' trong mỗi ví dụ không? –

Trả lời

0

GetTickCount (http://msdn.microsoft.com/en-us/library/windows/desktop/ms724408%28v=vs. 85% 29.aspx) chức năng được sử dụng để đo thời gian trôi qua ở phía gốc không phải là chính xác cả. Mô tả cho biết:

"Độ phân giải của hàm GetTickCount bị giới hạn ở độ phân giải của bộ hẹn giờ hệ thống, thường trong khoảng 10 mili giây đến 16 mili giây".

Từ kinh nghiệm của tôi khi sử dụng GetSystemTime mang lại kết quả tốt hơn trên Windows Vista trở lên (trên win XP có cùng độ phân giải như số đếm nếu tôi nhớ chính xác). Hoặc tốt hơn bạn có thể sử dụng cho các phép đo hạt mịn, một số API-othet cung cấp độ phân giải phụ mils. Có lẽ trong trường hợp của bạn xây dựng bộ dữ liệu lớn sẽ hữu ích hơn, để có một số dữ liệu có ý nghĩa.

+1

Tôi nghĩ rằng vấn đề không phải là chức năng GetTickCount cũng không đồng thời container, nếu tôi thay đổi mã song song C++ song_for_each thành task_group Tôi có cùng thời gian như mã C#. – user1127781

+1

Theo OP có 11 giây khác biệt giữa hai, do đó, nó có lẽ không liên quan đến tính chính xác của số lượng đánh dấu. –

6

Dưới đây là những lời giải thích bởi Rahul v Patil Microsoft đội

Xin chào,

Cảm ơn vì đã mang này lên. Thật vậy, bạn đã xác định được số tiền trên được kết hợp với song song mặc định cho * - đặc biệt khi số lần lặp lại là nhỏ và kích thước công việc là biến. song song mặc định để bắt đầu bằng cách chia nhỏ công việc thành 8 khối (trên 8 lõi). Khi công việc kết thúc, công việc động là cân bằng tải. Mặc định hoạt động tốt trong hầu hết các trường hợp (số lượng lớn lần lặp) và khi công việc cơ bản trên mỗi lần lặp lại không được hiểu rõ (ví dụ: bạn gọi vào thư viện) - nhưng nó đến với chi phí không được chấp nhận trong một số trường hợp.

Giải pháp chính xác là những gì bạn đã xác định trong lần thay thế thực hiện. Để có hiệu lực đó, chúng tôi sẽ có một song song cho phân vùng được gọi là "đơn giản" trong phiên bản tiếp theo của Visual Studio, sẽ là tương tự như triển khai thay thế mà bạn mô tả và sẽ có hiệu suất tốt hơn nhiều.

PS: C# và C++ song song cho mỗi triển khai sử dụng hơi thuật toán khác nhau trong cách họ đi qua các lần lặp - do đó bạn sẽ thấy đặc tính hiệu suất hơi khác nhau tùy thuộc vào khối lượng công việc.

Trân

4

Có một số vấn đề với mã của bạn, chúng ta hãy giải quyết chúng từng cái một:

Sử dụng đệ quy để tính toán Fibonacci gây quá trình sử dụng một số tiền inordinate bộ nhớ, vì nó là sử dụng ngăn xếp cuộc gọi để tính kết quả. Có đệ quy Fibonacci có nghĩa là bạn không so sánh các khung công tác song song C#/C++, bạn đang so sánh các cơ chế ngăn xếp cuộc gọi. Bạn có thể tính toán Fibonacci nhanh hơn nhiều:

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; 
} 

Với chức năng đó, tôi phải chạy ít nhất 1 triệu lần để có được giá trị đo được.

Sử dụng GetTickCount64 thay vì GetTickCount:

template <class Function> 
__int64 time_call(Function&& f) 
{ 
    __int64 begin = ::GetTickCount64(); 
    f(); 
    return GetTickCount64() - begin; 
} 

Chạy parallel_for với cơ thể nhỏ như vậy có thể thực sự gây bất lợi cho hoạt động. Nó là tốt hơn để sử dụng một functor chi tiết hơn.

Với những đặc điểm này trong tâm trí, đây là mã trong 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; 
} 

Và đây là đoạn code trong 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 + "\n\r"); 

      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; 
     } 
    } 
} 

Thời gian trung bình để chạy 12 triệu tính toán Fibonacci cho số giữa 37 và 49:

C++: 513ms

C#: 2527ms

Các vấn đề liên quan