C# - Kiểm Tra Tốc Độ Của Foreach vs LinQ vs LinQasParallel


Hôm nay chúng ta sẽ thử đo xem thời gian query Data của "Foreach vs LinQ vs LinQasParallel" như thế nào. Tôi tạo một tool nhỏ để làm việc này.




I> Testing
Bài test 1: Tôi tạo một tool với chức năng là tìm trong một List<int> các số chia hết cho 9, tôi dùng thuật toán vét cạn với độ phức tạp là O(n). Các bạn có thể kiểm tra cách tôi làm sau đây




[code language="csharp"]

IList<int> lstData = new List<int>();

private IList<int> GenerateList(Double max)
{
IList<int> result = new List<int>();
var rand = new Random();

for (int i = 0; i < max; i++)
{
var randInt = rand.Next(50000);
result.Add(randInt);
}
return result;
}

// Executing for test

private void CheckingWithLinqAsParallel()
{
var stopwatch = new Stopwatch();
var countResult = new List<int>();
// Begin timing
stopwatch.Start();

countResult = (from itemValue in lstData.AsParallel()
where (CheckingValue(itemValue))
select itemValue).ToList();

// Stop timing
stopwatch.Stop();

// Write result
txtResult.Text = countResult.Count.ToString();
txtTime.Text = stopwatch.ElapsedTicks.ToString();
}

private void CheckingWithLinq()
{
var stopwatch = new Stopwatch();
var countResult = new List<int>();
// Begin timing
stopwatch.Start();

countResult = (from itemValue in lstData
where (CheckingValue(itemValue))
select itemValue).ToList();

// Stop timing
stopwatch.Stop();

// Write result
txtResult.Text = countResult.Count.ToString();
txtTime.Text = stopwatch.ElapsedTicks.ToString();
}

private void CheckingWithForeach()
{
var stopwatch = new Stopwatch();
var countResult = new List<int>();
// Begin timing
stopwatch.Start();

foreach(var itemValue in lstData)
{
if (CheckingValue(itemValue))
{
countResult.Add(itemValue);
}
}

// Stop timing
stopwatch.Stop();

// Write result
txtResult.Text = countResult.Count.ToString();
txtTime.Text = stopwatch.ElapsedTicks.ToString();
}
[/code]

Kết quả test:




[code language="c"]
--------------- O(1000) -----------------
Foreach: Time = 133
Linq: Time = 13246
LinqAsParallel: Time = 69348
--------------- O(900000) -----------------
Foreach: Time = 31887
Linq: Time = 25718
LinqAsParallel: Time = 27008
--------------- O(9000000) -----------------
Foreach: Time = 314022
Linq: Time = 263684
LinqAsParallel: Time = 265144
LinqAsParallel: Time = 302023
LinqAsParallel: Time = 264581
Linq: Time = 259860
Linq: Time = 261858
--------------- O(90000000) -----------------
Foreach: Time = 3361146
Linq: Time = 2707332
LinqAsParallel: Time = 2717361
LinqAsParallel: Time = 3562806
LinqAsParallel: Time = 3608036
Linq: Time = 2751347
LinqAsParallel: Time = 2726033
[/code]

Parallel LinQ LinQAsParallel test 1


Tạm luận: Chúng ta có thể thấy rằng xét về mặt thời gian




  1. Khi lượng dữ liệu nhỏ và vừa thì Foreach trội hơn nhiều so với LinQ và LinQAsParallel

  2. Khi lượng dữ liệu lớn dần chạm một ngưỡng nào đó thì Foreach xuống dần phong độ so với LinQ và LinQAsParallel, dữ liệu càng tăng Foreach càng chậm.

  3. Giữa LinQ và LinQAsParallel, khi Parallel làm việc, nó phụ thuộc nhiều vào sự nhàn rỗi của CPU vì vậy tốc độ không ổn định, khi dữ liệu quá nhỏ Parallel trở nên chậm đáng kể, khi dữ liệu vừa phải Parallel trở nên rất nhanh - vừa phải ở đây phụ thuộc vào khả năng cung ứng Thread của CPU, khi dữ liệu tăng cao, vượt quá lượng Thread mà CPU hiện tại có thể cung cấp - Parallel trở nên chậm lại(Chú ý rằng công việc này là cần sự đồng bộ data).


Bài test 2: Độ phức tạp được nâng lên O(500^n) tức có hai vòng for lòng nhau, 500 là số lần lập cho vòng for trong, tôi không thể test với dữ liệu lớn đáng kể vì sẽ bị ERROR "out of memmory", lượng RAM mà tool chiếm > 2.2GB




[code language="csharp"]

IDictionary<int, IList<int>> lstDataUP = new Dictionary<int, IList<int>>();

private Dictionary<int, IList<int>> GenerateListUP(Double max)
{
var result = new Dictionary<int, IList<int>>();
var rand = new Random();

for (int i = 0; i < max; i++)
{
IList<int> subresult = new List<int>();

for (int j = 0; j < maxsubItem; j++)
{
var randInt = rand.Next(500);
subresult.Add(randInt);
}

result.Add(i,subresult);
}
return result;
}

// Excuteing for test

private void CheckingWithLinqAsParallelUP()
{
var stopwatch = new Stopwatch();
var countResult = new List<int>();
// Begin timing
stopwatch.Start();

countResult = (from itemValue in lstDataUP.AsParallel()
from subitemValue in itemValue.Value.AsParallel()
where (CheckingValue(subitemValue))
select subitemValue).ToList();

// Stop timing
stopwatch.Stop();

txtResult.Text = countResult.Count.ToString();
// Write result
txtTime.Text = stopwatch.ElapsedTicks.ToString();
}

private void CheckingWithLinqUP()
{
var stopwatch = new Stopwatch();
var countResult = new List<int>();
// Begin timing
stopwatch.Start();

countResult = (from itemValue in lstDataUP
from subitemValue in itemValue.Value
where (CheckingValue(subitemValue))
select subitemValue).ToList();

// Stop timing
stopwatch.Stop();

txtResult.Text = countResult.Count.ToString();
// Write result
txtTime.Text = stopwatch.ElapsedTicks.ToString();
}

private void CheckingWithForeachUP()
{
var stopwatch = new Stopwatch();
var countResult = new List<int>();
// Begin timing
stopwatch.Start();

foreach (var itemValue in lstDataUP)
{
foreach (var subitemValue in itemValue.Value)
{
if (CheckingValue(subitemValue))
{
countResult.Add(subitemValue);
}
}
}

// Stop timing
stopwatch.Stop();

txtResult.Text = countResult.Count.ToString();
// Write result
txtTime.Text = stopwatch.ElapsedTicks.ToString();
}
[/code]

Kết quả test




[code language="c"]
--------------- O(500^1000) -----------------
Foreach: Time = 99412
Linq: Time = 1085896
LinqAsParallel: Time = 3105398
--------------- O(500^9000) -----------------
Foreach: Time = 202546
Linq: Time = 695352
LinqAsParallel: Time = 582843
--------------- O(500^90000) -----------------
Foreach: Time = 1605131
Linq: Time = 5647985
LinqAsParallel: Time = 4189570
--------------- O(500^100000) -----------------
Foreach: Time = 1784048
Linq: Time = 6613424
LinqAsParallel: Time = 4496013
--------------- O(500^500000) -----------------
Foreach: Time = 9376404
Linq: Time = 32156749
LinqAsParallel: Time = 22864588
[/code]

Parallel LinQ LinQAsParallel test 2


Tạm luận: qua bài test trên chúng ta thấy rằng




  1. Foreach luôn chiếm ưu thế trong tình huống này, dữ liệu càng lớn sự chênh lệch càng rõ

  2. Giữa LinQ và LinQAsParallel, khi dữ liệu lớn đến một mốc nào đó LinQAsParallel tỏ ra nhanh hơn và ngược lại

  3. Khi chúng ta quan tâm đến một vài điều kiện để cho phép vòng for con thực thi hoặc không thì sự chênh lệch giữa LinQ và LinQAsParallel càng xa


II> Kết luận
Như vậy, chúng ta có thể tạm kết luận rằng LinQ nhanh hơn hẳn Foreach nếu dữ liệu lớn đáng kể(> trăm ngàn dòng) và độ phức tạp tương đương O(n), nó thật sự chậm chạp khi dữ liệu là nhỏ ở mức vài ngàn dòng; trong khi đó nếu độ phức tạp tăng lên LinQ không phải là một sự lựu chọn tối ưu về tốc độ nếu đứng cạnh Foreach. Hãy cân nhắc đến việc sử dụng LinQ thay cho LinQAsParallel vì Parallel là một con dao hai lưỡi nếu chúng ta không hiểu hết thực trạng hệ thống và khả năng hiện tại của CPU cũng như lượng dữ liệu không cố định. Trong một số trường hợp chúng ta dùng Parallel sẽ bị Exception vì dữ liệu quá lớn trong khi Data cần thiết phải đồng bộ.


Các bài test được thực hiện trên Lap HP probook, I3 và 4G RAM, tình trạng sử dụng tài nguyên không nhiều.


Trên đây là những đánh giá chủ quan về mặt Query data(có đồng bộ) trong một dữ liệu dạng danh sách, tốc độ phụ thuộc nhiều vào thiết kế phần cứng của máy vì vậy hãy tham khảo các kết luận trên như một ý kiến cá nhân, các bạn có thể Comment để thảo luận về đề tài này.
Download SourceCode tại đây, chúc các bạn thành công!
Phạm Tuân


Chúc các bạn thành công!
PHẠM TUÂN