2009-07-12 13 views
8

Tôi đã được giao nhiệm vụ thực hiện mô phỏng đơn luồng monte carlo hiện cótối ưu hóa. Đây là một ứng dụng giao diện điều khiển C#, không truy cập db nó tải dữ liệu một lần từ một tệp csv và ghi nó ra ở cuối, vì vậy nó là khá nhiều chỉ CPU ràng buộc, cũng chỉ sử dụng khoảng 50MB bộ nhớ.Di chuyển một ứng dụng đơn luồng sang đa luồng, thực thi song song, mô phỏng monte carlo

Tôi đã chạy qua trình thu thập dữ liệu Jetbrains dotTrace. Trong tổng thời gian thực hiện khoảng 30% là tạo ra các số ngẫu nhiên đồng nhất, 24% chuyển số ngẫu nhiên đồng nhất sang các số ngẫu nhiên được phân phối thông thường.

Các thuật toán cơ bản là một toàn bộ rất nhiều lồng nhau cho vòng, với số ngẫu nhiên các cuộc gọi và nhân ma trận ở trung tâm, mỗi lần lặp trả về một đôi mà được thêm vào một danh sách kết quả, danh sách này được định kỳ sắp xếp và thử nghiệm cho một số tiêu chí hội tụ (tại các điểm kiểm tra mỗi 5% tổng số lần lặp) nếu chương trình có thể chấp nhận thoát ra khỏi vòng lặp và ghi kết quả, nếu không nó sẽ tiến tới cuối.

Tôi muốn phát triển phải cân nhắc ở trên:

  • tôi nên sử dụng Chủ đề mới v ThreadPool
  • tôi nên nhìn vào các thư viện Microsoft Parallels mở rộng
  • tôi nên xem xét AForge.Net Parallel.For, http://code.google.com/p/aforge/ bất kỳ thư viện nào khác?

Một số liên kết đến các hướng dẫn trên trên sẽ được chào đón nhất như Tôi chưa bao giờ viết bất kỳ song song hoặc mã đa luồng.

  • các chiến lược tốt nhất để tạo số ngẫu nhiên được phân phối bình thường, và sau đó tiêu thụ các số này. Số ngẫu nhiên đồng nhất không bao giờ được ứng dụng sử dụng trong trạng thái này, chúng luôn được dịch sang thường được phân phối và sau đó được tiêu thụ.
  • thư viện nhanh tốt (song song?) Để tạo số ngẫu nhiên
  • cân nhắc bộ nhớ khi tôi thực hiện song song này, tôi sẽ yêu cầu thêm bao nhiêu.

Ứng dụng hiện tại mất 2 giờ cho 500.000 lần lặp lại, doanh nghiệp cần điều này để chia tỷ lệ thành 3.000.000 lần lặp lại và được gọi là lần mulitple mỗi ngày, vì vậy cần tối ưu hóa nặng.

Particulary muốn nghe từ những người người đã sử dụng Microsoft Parallels mở rộng hoặc AForge.Net Parallel

này cần phải được productionised khá nhanh chóng để .net 4 beta ra mặc dù Tôi biết nó có các thư viện đồng thời được đưa vào, chúng ta có thể xem xét việc di chuyển sang .net 4 sau đó sau khi nó được phát hành. Hiện tại máy chủ có .Net 2, tôi đã gửi để xem xét nâng cấp lên .net 3.5 SP1 mà hộp dev của tôi có.

Cảm ơn

Cập nhật

Tôi vừa cố gắng thực hiện Parallel.For nhưng nó đi kèm với một số kết quả kỳ lạ. đơn luồng:

IRandomGenerator rnd = new MersenneTwister(); 
IDistribution dist = new DiscreteNormalDistribution(discreteNormalDistributionSize); 
List<double> results = new List<double>(); 

for (int i = 0; i < CHECKPOINTS; i++) 
{ 
results.AddRange(Oblist.Simulate(rnd, dist, n)); 
} 

Để:

Parallel.For(0, CHECKPOINTS, i => 
     { 
      results.AddRange(Oblist.Simulate(rnd, dist, n)); 
     }); 

Bên trong mô phỏng có rất nhiều cuộc gọi đến rnd.nextUniform(), Tôi nghĩ rằng tôi nhận được nhiều giá trị giống nhau, có khả năng này xảy ra bởi vì điều này bây giờ là song song?

Cũng có thể sự cố với cuộc gọi Danh sách AddRange không phải là chủ đề an toàn? Tôi thấy điều này

System.Threading.Collections.BlockingCollection có thể đáng được sử dụng, nhưng nó chỉ có phương thức Thêm không có AddRange nên tôi phải xem xét kết quả đó và thêm một cách an toàn cho luồng. Bất kỳ cái nhìn sâu sắc từ một người đã sử dụng Parallel.For nhiều đánh giá cao. Tôi chuyển sang System.Random cho các cuộc gọi của tôi tạm thời như tôi đã nhận được một ngoại lệ khi gọi nextUniform với thực hiện Mersenne Twister tôi, có lẽ nó đã không đề an toàn một mảng nhất định đã nhận được một chỉ số ngoài giới hạn. ...

+0

Bạn đang chạy máy nào? Có thể nhận được một phần tốc độ tăng yêu cầu từ phần cứng được nâng cấp. –

+0

Đây là trên một opteron AMD 275, 4 cpus tôi nghĩ, không chắc chắn có bao nhiêu lõi. Máy chủ Windows 2003 SP2 32 bit – m3ntat

Trả lời

13

Trước tiên, bạn cần phải hiểu lý do tại sao bạn cho rằng việc sử dụng nhiều luồng là một tối ưu hóa - khi thực tế thì không. Sử dụng nhiều luồng sẽ làm cho khối lượng công việc của bạn hoàn thành nhanh hơn chỉ nếu bạn có nhiều bộ xử lý, và sau đó nhanh nhất là nhiều lần khi bạn có CPU (điều này được gọi là tăng tốc). Công việc không được "tối ưu hóa" theo nghĩa truyền thống của từ (nghĩa là số lượng công việc không giảm - thực tế, với đa luồng, tổng số lượng công việc thường tăng lên do luồng trên không).

Vì vậy, khi thiết kế ứng dụng của bạn, bạn phải tìm những phần công việc có thể được thực hiện theo cách song song hoặc chồng chéo. Có thể tạo ra các số ngẫu nhiên song song (bằng cách có nhiều RNG chạy trên các CPU khác nhau), nhưng điều đó cũng sẽ thay đổi kết quả, khi bạn nhận được các số ngẫu nhiên khác nhau. Một tùy chọn khác là tạo ra các số ngẫu nhiên trên một CPU và mọi thứ khác trên các CPU khác nhau. Điều này có thể cung cấp cho bạn tốc độ tối đa là 3, vì RNG sẽ vẫn chạy tuần tự và vẫn chiếm 30% tải.

Vì vậy, nếu bạn đi song song này, bạn kết thúc với 3 luồng: luồng 1 chạy RNG, luồng 2 tạo phân phối bình thường và chuỗi 3 thực hiện phần còn lại của mô phỏng.

Đối với kiến ​​trúc này, producer-consumer architecture là thích hợp nhất. Mỗi luồng sẽ đọc đầu vào của nó từ một hàng đợi và tạo đầu ra của nó thành một hàng đợi khác. Mỗi hàng đợi nên được chặn, vì vậy nếu chuỗi RNG nằm sau, chuỗi chuẩn hóa sẽ tự động chặn cho đến khi có các số ngẫu nhiên mới. Để có hiệu quả, tôi sẽ chuyển các số ngẫu nhiên trong mảng, ví dụ, 100 (hoặc lớn hơn) qua các luồng, để tránh đồng bộ hóa trên mọi số ngẫu nhiên.

Đối với phương pháp này, bạn không cần bất kỳ luồng nâng cao nào. Chỉ cần sử dụng lớp chủ đề thông thường, không có hồ bơi, không có thư viện. Điều duy nhất mà bạn cần là (không may) không phải trong thư viện chuẩn là một lớp Queue chặn (lớp Queue trong System.Collections là không tốt).Codeproject cung cấp triển khai thực hiện một cách hợp lý; có thể có những người khác.

+0

Vấn đề khác cần xem xét là chuyển đổi ngữ cảnh. Nếu bạn không chọn kiến ​​trúc ở trên (có thể là một lỗi lầm từ những gì bạn đã nói) thì bạn sẽ cố gắng chạy nhiều phép tính song song mà vượt xa số lượng bộ vi xử lý của bạn. Điều này sẽ là thảm họa như rất nhiều thời gian xử lý đã được tính toán câu trả lời trước đây là dành cho việc chuyển đổi giữa các chủ đề. Nếu bạn có một số tệp io sau mỗi phép tính thì có lẽ điều đó có thể được thực hiện async (nhưng sau đó bạn sẽ sử dụng một hàng đợi và chuyển các mục để lưu trữ vào một thành phần chuyên dụng). –

+0

Tính toán carlo monte là hoàn toàn CPU bị ràng buộc, vì vậy bạn đang nói tôi luôn luôn nên bản đồ 1 thread đến 1 cpu trên hộp không bao giờ có một lợi thế để đi> 1 chủ đề cho mỗi CPU? trừ khi một sợi đang chờ đợi một thứ gì đó khác, nó sẽ cho phép các hiệu ứng với các công tắc ngữ cảnh, nhưng trong trường hợp của tôi, tôi nghĩ rằng không có lợi thế trong thực tế nó sẽ là hiệu suất tồi tệ hơn. – m3ntat

+0

Chính xác. Nếu có thực sự không có IO trong các chủ đề này, sau đó sử dụng nhiều chủ đề cho mỗi CPU sẽ làm chậm nó xuống, không tăng tốc độ nó lên. –

0

Luồng sẽ phức tạp. Bạn sẽ phải phá vỡ chương trình của bạn thành các đơn vị logic mà mỗi người có thể chạy trên các chủ đề của riêng họ, và bạn sẽ phải đối phó với bất kỳ vấn đề đồng thời nào xuất hiện.

Thư viện tiện ích mở rộng song song sẽ cho phép bạn song song chương trình của mình bằng cách thay đổi một số vòng lặp của bạn thành các vòng lặp Parallel.For. Nếu bạn muốn xem cách làm việc này, Anders Hejlsberg và Joe Duffy cung cấp một giới thiệu tốt trong video 30 phút của họ ở đây:

http://channel9.msdn.com/shows/Going+Deep/Programming-in-the-Age-of-Concurrency-Anders-Hejlsberg-and-Joe-Duffy-Concurrent-Programming-with/

Threading vs ThreadPool

Các ThreadPool, như tên của nó ngụ ý, là một nhóm các chủ đề. Sử dụng ThreadPool để có được chủ đề của bạn có một số lợi thế. Thread pooling cho phép bạn sử dụng các luồng hiệu quả hơn bằng cách cung cấp ứng dụng của bạn với một chuỗi các luồng công nhân được hệ thống quản lý.

+0

Hmm, tôi không nghĩ rằng việc sử dụng ThreadPool sẽ phức tạp hơn so với luồng thủ công - điều mà tôi nghĩ là điều bạn muốn nói nhưng bị bỏ quên? So sánh ThreadPool và xử lý thủ công Threads the ThreadPool hiệu quả hơn (vì nó tái tạo các luồng đã hoàn thành, tạo luồng là tốn kém) và dễ làm việc hơn - đặc biệt nếu sử dụng các delegate. Điều đó nói rằng tôi không thể nói để so sánh nó với các thư viện song song - chỉ không muốn ThreadPool để có được một tên xấu :-) – STW

1

List<double> chắc chắn không phải là an toàn chỉ. Xem phần "an toàn chủ đề" trong System.Collections.Generic.List documentation. Lý do là hiệu suất: thêm an toàn chủ đề không phải là miễn phí.

Việc triển khai số ngẫu nhiên của bạn cũng không an toàn theo chủ đề; nhận được cùng một số nhiều lần là chính xác những gì bạn mong đợi trong trường hợp này. Hãy sử dụng các mô hình đơn giản sau đây của rnd.NextUniform() để hiểu những gì đang xảy ra:

  1. tính toán số giả ngẫu nhiên từ tình trạng hiện thời của đối tượng
  2. cập nhật trạng thái của đối tượng nên cuộc gọi tiếp theo mang lại một số khác nhau
  3. trở lại các giả ngẫu nhiên số

Bây giờ, nếu hai luồng thực hiện phương pháp này song song, một cái gì đó như thế này có thể xảy ra:

  • Chủ đề Một tính toán một số ngẫu nhiên như ở bước 1.
  • Thread B sẽ tính toán một số ngẫu nhiên như ở bước 1. Chủ đề Một vẫn chưa cập nhật trạng thái của đối tượng, vì vậy kết quả là giống nhau.
  • Chủ đề Một cập nhật trạng thái của đối tượng như ở bước 2.
  • Thread B cập nhật trạng thái của đối tượng như trong bước 2, chà đạp lên tình trạng thay đổi của một hoặc có thể đưa ra cùng một kết quả .

Như bạn có thể thấy, bất kỳ lý do nào bạn có thể làm để chứng minh rằng tác phẩm rnd.NextUniform() không còn giá trị vì hai chuỗi đang can thiệp lẫn nhau. Tồi tệ hơn, các lỗi như thế này phụ thuộc vào thời gian và có thể hiếm khi xuất hiện dưới dạng "trục trặc" theo khối lượng công việc nhất định hoặc trên một số hệ thống nhất định. Gỡ lỗi cơn ác mộng!

Một giải pháp có thể là để loại bỏ chia sẻ trạng thái: cho mỗi tác vụ tạo số ngẫu nhiên riêng của nó khởi tạo với một hạt giống khác (giả sử rằng các trường hợp không chia sẻ trạng thái thông qua các trường tĩnh theo một cách nào đó).

khác (kém) giải pháp là tạo ra một lĩnh vực tổ chức một đối tượng khóa trong lớp MersenneTwister của bạn như thế này:

private object lockObject = new object(); 

Sau đó, sử dụng khóa này trong MersenneTwister.NextUniform() thực hiện của bạn:

public double NextUniform() 
{ 
    lock(lockObject) 
    { 
     // original code here 
    } 
} 

Điều này sẽ ngăn không cho hai luồng thực thi phương thức NextUniform() song song. Sự cố với danh sách trong số Parallel.For của bạn có thể được xử lý theo cách tương tự: tách biệt cuộc gọi Simulate và cuộc gọi AddRange và sau đó thêm khóa xung quanh cuộc gọi AddRange.

Đề xuất của tôi: tránh chia sẻ bất kỳ trạng thái có thể thay đổi nào (như trạng thái RNG) giữa các tác vụ song song nếu có thể. Nếu không có trạng thái có thể thay đổi được chia sẻ, không có vấn đề luồng nào xảy ra. Điều này cũng tránh tắc nghẽn cổ chai: bạn không muốn các tác vụ "song song" của bạn phải chờ trên một trình tạo số ngẫu nhiên đơn lẻ không hoạt động song song. Đặc biệt là nếu 30% thời gian là dành số ngẫu nhiên.

Giới hạn chia sẻ và khóa ở những nơi bạn không thể tránh, như khi tổng hợp kết quả thực thi song song (như trong các cuộc gọi AddRange).

+0

nhờ phản ứng tuyệt vời! Điều đó có ý nghĩa hoàn hảo. Bây giờ câu hỏi là tôi nên sử dụng thêm phạm vi hoặc tìm một bộ sưu tập threadsafe cho phép tôi tích lũy danh sách các số ngẫu nhiên (gấp đôi), thêm thứ tự là không quan trọng nhưng tôi cần định kỳ sắp xếp kết quả và lấy kết quả ở một phần trăm nhất định và kiểm tra các tiêu chí hội tụ để kiểm tra sự kết thúc sớm của mô phỏng, tôi cần thực hiện điều này cho mỗi đường dẫn Parallel.For đang chạy và sau đó hủy bỏ tất cả các lệnh thực thi song song ngay lập tức nếu không cần xử lý thêm, bất kỳ ý tưởng nào để làm điều đó? – m3ntat

+0

Tôi không ngay lập tức có câu trả lời cho điều đó. Kiểm tra trạng thái định kỳ và hủy các tác vụ song song đang chờ xử lý/đang chạy là một chủ đề lớn về chính nó; Tôi khuyên bạn nên đăng câu hỏi mới. –

+0

Mặc dù, hãy xem http://blogs.msdn.com/pfxteam/archive/2009/05/22/9635790.aspx –