2012-10-25 50 views
6

Tôi đang cố gắng hiểu tại sao Parallel.For có thể làm tốt hơn một số chủ đề trong kịch bản sau: xem xét một loạt công việc có thể được xử lý song song. Trong khi xử lý các công việc này, công việc mới có thể được thêm vào, sau đó cũng cần được xử lý. Các giải pháp Parallel.For sẽ trông như sau:Parallel.For vs thường xuyên chủ đề

var jobs = new List<Job> { firstJob }; 
int startIdx = 0, endIdx = jobs.Count; 
while (startIdx < endIdx) { 
    Parallel.For(startIdx, endIdx, i => WorkJob(jobs[i])); 
    startIdx = endIdx; endIdx = jobs.Count; 
} 

Điều này có nghĩa rằng có rất nhiều lần nơi Parallel.For cần phải đồng bộ hóa. Hãy xem xét một thuật toán thuật toán đồ thị-bánh mì đầu tiên; số lượng đồng bộ hóa sẽ khá lớn. Lãng phí thời gian, phải không?

Cố gắng giống nhau trong cách tiếp cận luồng old-fashioned:

var queue = new ConcurrentQueue<Job> { firstJob }; 
var threads = new List<Thread>(); 
var waitHandle = new AutoResetEvent(false); 
int numBusy = 0; 
for (int i = 0; i < maxThreads; i++) 
    threads.Add(new Thread(new ThreadStart(delegate { 
    while (!queue.IsEmpty || numBusy > 0) { 
     if (queue.IsEmpty) 
     // numbusy > 0 implies more data may arrive 
     waitHandle.WaitOne(); 

     Job job; 
     if (queue.TryDequeue(out job)) { 
     Interlocked.Increment(ref numBusy); 
     WorkJob(job); // WorkJob does a waitHandle.Set() when more work was found 
     Interlocked.Decrement(ref numBusy); 
     } 
    } 
    // others are possibly waiting for us to enable more work which won't happen 
    waitHandle.Set(); 
}))); 
threads.ForEach(t => t.Start()); 
threads.ForEach(t => t.Join()); 

Các Parallel.For code đang tất nhiên sạch hơn nhiều, nhưng những gì tôi không thể hiểu được, nó thậm chí còn nhanh hơn nữa! Trình lên lịch nhiệm vụ có tốt không? Các đồng bộ hóa đã được làm nổi bật, không có chờ đợi bận rộn, nhưng cách tiếp cận luồng luôn luôn chậm hơn (đối với tôi). Chuyện gì vậy? Cách tiếp cận luồng có thể được thực hiện nhanh hơn không?

Chỉnh sửa: cảm ơn tất cả các câu trả lời, tôi ước tôi có thể chọn nhiều câu trả lời. Tôi đã chọn để đi với một trong đó cũng cho thấy một cải tiến thực tế có thể.

+1

Tại sao bạn muốn thử và làm cho nó nhanh hơn nếu đã có một giải pháp sạch hơn nhanh hơn? – iMortalitySX

+0

Bởi vì có một sự thiếu hụt rõ ràng có thể được loại bỏ, tôi nghĩ vậy. –

+0

Đóng câu hỏi [PLinq có nhanh hơn System.Threading.Tasks.Parallel.ForEach] không (http://stackoverflow.com/questions/5196293/is-plinq-inherently-faster-than-system-threading-tasks-parallel- foreach) – iMortalitySX

Trả lời

12

Hai mẫu mã không thực sự giống nhau.

Parallel.ForEach() sẽ sử dụng số lượng giới hạn chủ đề và sử dụng lại chúng. Mẫu thứ 2 đã bắt đầu bằng cách tạo ra một số chủ đề. Điều đó cần có thời gian.

Và giá trị của maxThreads là gì? Rất quan trọng, trong Parallel.ForEach() nó rất năng động.

Trình lập lịch biểu tác vụ có tốt không?

Điều này khá tốt. Và TPL sử dụng trộm cắp công việc và các công nghệ thích nghi khác. Bạn sẽ có một thời gian khó khăn để làm tốt hơn.

+0

Ví dụ về luồng sử dụng lại các chủ đề mà nó tạo ra. Nó bắt đầu một số lượng hạn chế của họ, không phải một cho mỗi công việc nếu đó là những gì bạn có ý nghĩa. –

+0

Đánh tôi với nó, sử dụng hồ bơi Vs không. http://stackoverflow.com/questions/230003/thread-vs-threadpool –

+0

@Justin: Aha, tham khảo tốt. Cảm ơn. –

1

Tạo chuỗi chủ đề mới và Parallel.For đang sử dụng Threadpool. Bạn sẽ thấy hiệu suất tốt hơn nếu bạn đang sử dụng C# threadpool nhưng có thực sự là không có điểm trong việc đó.

Tôi sẽ ngại tránh tung ra giải pháp của riêng bạn; nếu có trường hợp góc mà bạn cần tùy chỉnh sử dụng TPL và tùy chỉnh ..

3

Parallel.For không thực sự chia nhỏ các mục thành các đơn vị công việc. Nó phá vỡ tất cả các công việc (sớm) dựa trên số lượng các chủ đề nó có kế hoạch sử dụng và số lần lặp lại được thực hiện. Sau đó, mỗi chủ đề đồng bộ xử lý hàng loạt đó (có thể sử dụng công việc ăn cắp hoặc lưu một số mục bổ sung để cân bằng tải gần cuối). Bằng cách sử dụng cách tiếp cận này, các luồng công nhân hầu như không bao giờ chờ đợi lẫn nhau, trong khi các luồng của bạn liên tục chờ đợi nhau do đồng bộ hóa nặng mà bạn đang sử dụng trước/sau mỗi lần lặp đơn.

Trên hết, vì nó đang sử dụng chuỗi chủ đề của luồng nên nhiều chủ đề mà nó cần có khả năng đã được tạo, đây là một lợi thế khác có lợi cho nó.

Để đồng bộ hóa, toàn bộ điểm của Parallel.For là tất cả các lần lặp có thể được thực hiện song song, vì vậy hầu như không có đồng bộ hóa cần thực hiện (ít nhất là trong mã của chúng).

Sau đó, tất nhiên có vấn đề về số lượng chủ đề. Các threadpool có rất nhiều thuật toán rất tốt và heuristics để giúp nó xác định có bao nhiêu chủ đề cần tại thời điểm đó trong thời gian, dựa trên phần cứng hiện tại, tải từ các ứng dụng khác, vv. nhiều, hoặc không đủ chủ đề.

Ngoài ra, vì số lượng mục bạn chưa biết trước khi bắt đầu, tôi khuyên bạn nên sử dụng Parallel.ForEach thay vì nhiều vòng Parallel.For. Nó được thiết kế đơn giản cho tình huống mà bạn đang ở, do đó, nó là heuristics sẽ áp dụng tốt hơn. (Nó cũng làm cho mã thậm chí còn sạch hơn.)

BlockingCollection<Job> queue = new BlockingCollection<Job>(); 

//add jobs to queue, possibly in another thread 
//call queue.CompleteAdding() when there are no more jobs to run 

Parallel.ForEach(queue.GetConsumingEnumerable(), 
    job => job.DoWork()); 
+0

Thực ra dường như cách tiếp cận đó là không thể, vì bạn không biết khi nào nên gọi 'queue.CompleteAdding()'. Đây là chỉ khi hàng đợi là cả hai sản phẩm nào và không ai đang làm việc trên bất kỳ mục nào khác. –

+0

@FrankRazenberg Nope. Bạn chỉ cần gọi 'CompleteAdding' khi không còn mục nào để thêm. Bạn không cần phải chờ đợi cho nó được để trống hoặc không có thêm các mục để được trong quá trình được làm việc trên. 'BlockingCollection' sẽ xử lý điều đó. 'CompleteAdding' sẽ chỉ đơn giản có nghĩa là điều tra viên sẽ không thêm bất kỳ mục nào vào bộ sưu tập nội bộ của nó, do đó, khi đó, cuối cùng, nhổ ra mục cuối cùng nó sẽ bị vỡ, thay vì chặn và chờ thêm các mục. – Servy

+0

Nhưng làm thế nào bạn sẽ biết khi nào/nơi để gọi CompleteAdding()? Nó chỉ có thể được gọi một lần, đúng không? –

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