Khác biệt giữa Parallel.ForEach() và ParallelEnumerable.ForAll() trong .NET framework

Parallel.ForEach() là một static method cho phép thực hiện song song các thao tác trên một Enumerable object. ParallelEnumerable.ForAll() là một extension method cho phép thực hiện song song các thao tác trên một Enumerable object do một câu truy vấn tạo ra (hơi khó giải thích). Tuy nhiên, không chỉ khác biệt về cách sử dụng, chúng cũng có khác biệt về performance nói chung.

Giả sử bạn gặp tình huống xử lí trên một biến data kiểu Enumerable như sau:

var q = from d in data … select d;
foreach (var item in q) { doSomething(item); }

Nếu bạn muốn câu truy vấn q chạy ở chế độ song song (chẳng hạn, khi bạn có nhiều core và muốn tận dụng chúng) bạn dùng ParallelEnumerable.AsParallel():

var q = from d in data.AsParallel() … select d;
foreach (var item in q) { doSomething(item); }

Và nếu hàm doSomething() không quan tâm đến thứ tự xử lí của item (tức là gọi doSomething với item nào trước thì kết quả cũng ra như nhau) thì bạn cũng có thể chạy foreach ở chế độ song song:

var q = from d in data.AsParallel() … select d;
Parallel.ForEach(q, item => { doSomething(item) });

Nhưng bạn cũng có thể dùng ForAll với câu truy vấn q để đạt hiệu quả tương tự:

var q = (from d in data.AsParallel() … select d);
q.ForAll(item => { doSomething(item) });

Nhìn hai đoạn code cuối ta có cảm giác performance của chúng giống nhau, nhưng thực ra không phải như vậy. Tạm gọi đoạn code dùng Parallel.ForEach() là đoạn code (1) và đoạn code dùng ParallelEnumerable.ForAll() là đoạn code (2).

Các công việc mà đoạn code (1) thực hiện (không hoàn toàn chính xác trên thực tế, nhưng đủ để cho thấy sự khác biệt) là:

  • Chạy câu truy vấn:
    • Phân hoạch data ra thành nhiều phần (để chạy song song).
    • Chạy câu truy vấn trên từng data, tạm gọi kết quả là q1, q2, q3, v.v.
    • Trộn q1, q2, q3, v.v lại thành Enumerable object q.
  • Chạy Parallel.ForEach():
    • Phân hoạch q ra thành nhiều phần (để chạy song song).
    • Gọi doSomething();

Sơ đồ minh họa cho đoạn code (1).

image

Đoạn code (1) lãng phí ở chỗ đầu tiên ta trộn các q1, q2, q3, v.v lại thành q, sau đó lại phân hoạch q ra. Sẽ tốt hơn nếu sau khi phân hoạch data, câu truy vấn được chạy, và các item lấy ra sẽ được cho vào doSomething() luôn. Nói cách khác doSomething() trở thành một phần trong hoạt động của câu truy vấn. Đó chính là cách mà đoạn code (2) thi hành. Dưới đây là sơ đồ minh họa cho đoạn code (2):

image

Đó là lí do lại sao đoạn code (2) cho performance tốt hơn, và được khuyên dùng trong những tình huống tương tự.

Nếu muốn tìm hiểu thêm về Parallel trong C#, bạn có thể đọc Paterns of Parallel Programming (sách miễn phí từ Microsoft).

Advertisements

One thought on “Khác biệt giữa Parallel.ForEach() và ParallelEnumerable.ForAll() trong .NET framework

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s