Liệu thực sự Linq có nhanh hơn vòng lặp for, foreach, while

Bài viết được sự cho phép của tác giả Phạm Công Sơn

Bài viết trước tôi có nêu vấn đề so sánh giữa 2 mảng mà không sử dụng vòng lặp và thay vào đó là sử dụng Linq. Và nhiều bạn cũng đã hỏi vậy tốc độ xử lý giữa dùng vòng lặp và Linq thì dùng cái nào nhanh hơn.

  10 điều bạn có thể làm với Linux mà bạn không thể làm với Windows
  3 hướng dẫn để bắt đầu kinh doanh trong lĩnh vực nhân sự

Thực ra vấn đề so sánh tốc độ giữa Linq và vòng lặp không còn là chủ đề mới. Các bạn có thể search trên google là ra cả đống.

Liệu thực sự Linq có nhanh hơn vòng lặp for,foreach, while

Có thể thấy là có nhiều kết quả trên google cho vấn đề này.

Tuy nhiên thực sự nhiều câu trả lời cũng chưa thỏa đáng. Chính vì vậy tôi đã làm bài viết này với những kiểm chứng và chính các bạn cũng có thể tự trải nghiệm được.

Nếu như chỉ thực hiện vòng lặp một cách thuần túy. Tôi cam đoan rằng vòng lặp forforeach chắc chắn là nhanh hơn Linq. Và đây là ví dụ minh chứng.

1. Kiểm nghiệm tính tổng số của một mảng
Thực hiện so sánh
Phương thức Kết quả tính tổng Thời gian thực hiện (ticks)
Sử dụng vòng lặp for
Sử dụng vòng lặp foreach
Sử dụng lệnh Join của Linq
Đây là code mà tôi thực hiện kiểm chứng
private long OnStopWatch(Action action)
{
var st = new Stopwatch();
st.Start();
action();
st.Stop();
return st.ElapsedTicks;
}

private void CreateListRandom(int totalNumber, List list1)
{
for (var i = 1; i <= totalNumber; i++)
{
list1.Add(Singleton.Inst.Next(0, 100));
}
}

public void Test1(int totalNumber)
{
if (totalNumber > 50000) throw new Exception("Vui lòng chọn số lượng mảng nhỏ hơn 50000");

var list = new List { };
CreateListRandom(totalNumber, list);

var sumByFor = 0;
var t1 = OnStopWatch(() =>
{
for (var i = 0; i < list.Count; i++) { sumByFor += list[i]; } }); var sumByForEach = 0; var t2 = OnStopWatch(() =>
{
foreach (var item1 in list)
{
sumByForEach += item1;
}
});

var sumByJoinLinq = 0;
var t3 = OnStopWatch(() =>
{
sumByJoinLinq = list.Sum(item => item);
});

this.SetData("Result", new { t1, sumByFor, t2, sumByForEach, t3, sumByJoinLinq, list1 = list.JoinString(item => item, " ") });
}

Rõ ràng với vòng lặp đơn thuần thì for nhanh nhất, Linq là chậm nhất

Nhưng đấy là chúng ta mới chỉ thực hiện với một vòng lặp đơn thuần. Trong thực tế khi duyệt phần tử ta có thể phải sử dụng forforeachwhile lồng nhau thì sao? Ví dụ như sắp xếp hoặc như trong bài trước của tôi là đối sánh giữa 2 phần tử của mảng thì sẽ như thế nào

Dưới đây là kiểm chứng mà tôi tìm ra những phần tử có chung ở 2 mảng rồi thực hiện tính tổng các phần tử này.
2. Kiểm nghiệm tính tổng số phần tử chung giữa 2 mảng
Thực hiện so sánh
Phương thức Kết quả tính tổng Thời gian thực hiện (ticks)
Sử dụng vòng lặp for
Sử dụng vòng lặp foreach
Sử dụng lệnh Join của Linq

Các bạn có thể thấy điều gì không. Thực tế nếu dùng Linq lại cho thấy tốc độ nhanh hơn rất nhiều.

Dưới đây là code tôi thực hiện kiểm chứng
private void CreateListRandom(int totalNumber, List list1, List list2)
{
for (var i = 1; i <= totalNumber; i++)
{
list1.Add(Singleton.Inst.Next(0, 100));
list2.Add(Singleton.Inst.Next(0, 100));
}
}

public void Test2(int totalNumber)
{
if (totalNumber > 50000) throw new Exception("Vui lòng chọn số lượng mảng nhỏ hơn 50000");

// Tạo 2 mảng ngẫu nhiên
var list1 = new List { };
var list2 = new List { };
CreateListRandom(totalNumber, list1, list2);

var sumByFor = 0;
var t1 = OnStopWatch(() =>
{
for(var i = 0; i < list1.Count;i++)
{
var item1 = list1[i];
for(var j = 0; j < list2.Count;j++) { var item2 = list2[j]; if (item1 == item2) sumByFor += item1; } } }); var sumByForEach = 0; var t2 = OnStopWatch(() =>
{
foreach(var item1 in list1)
{
foreach(var item2 in list2)
{
if (item1 == item2)
sumByForEach += item1;
}
}
});

var sumByJoinLinq = 0;
var t3 = OnStopWatch(() =>
{
list1.Join(list2, item1 => item1, item2 => item2, (item1, item2) => sumByJoinLinq += item1).Count();
});

this.SetData("Result", new { t1, sumByFor, t2, sumByForEach, t3, sumByJoinLinq, list1 = list1.JoinString(item => item, " "), list2 = list2.JoinString(item => item, " ") });
}

Tôi cũng đã tìm tới Source code của thư viện Enumerable.cs của Microsoft để tìm hiểu nội dung hàm Join

public static IEnumerable Join<TOuter, TInner, TKey, TResult>(this IEnumerable outer, IEnumerable inner, Func<TOuter, TKey> outerKeySelector, Func<TInner, TKey> innerKeySelector, Func<TOuter, TInner, TResult> resultSelector)
{
if (outer == null) throw Error.ArgumentNull("outer");
if (inner == null) throw Error.ArgumentNull("inner");
if (outerKeySelector == null) throw Error.ArgumentNull("outerKeySelector");
if (innerKeySelector == null) throw Error.ArgumentNull("innerKeySelector");
if (resultSelector == null) throw Error.ArgumentNull("resultSelector");
return JoinIterator<TOuter, TInner, TKey, TResult>(outer, inner, outerKeySelector, innerKeySelector, resultSelector, null);
}

static IEnumerable JoinIterator<TOuter, TInner, TKey, TResult>(IEnumerable outer, IEnumerable inner, Func<TOuter, TKey> outerKeySelector, Func<TInner, TKey> innerKeySelector, Func<TOuter, TInner, TResult> resultSelector, IEqualityComparer comparer)
{
Lookup<TKey, TInner> lookup = Lookup<TKey, TInner>.CreateForJoin(inner, innerKeySelector, comparer);
foreach (TOuter item in outer)
{
Lookup<TKey, TInner>.Grouping g = lookup.GetGrouping(outerKeySelector(item), false);
if (g != null)
{
for (int i = 0; i < g.count; i++)
{
yield return resultSelector(item, g.elements[i]);
}
}
}
}

internal static Lookup<TKey, TElement> CreateForJoin(IEnumerable source, Func<TElement, TKey> keySelector, IEqualityComparer comparer)
{
Lookup<TKey, TElement> lookup = new Lookup<TKey, TElement>(comparer);
foreach (TElement item in source)
{
TKey key = keySelector(item);
if (key != null) lookup.GetGrouping(key, true).Add(item);
}
return lookup;
}

internal Grouping GetGrouping(TKey key, bool create)
{
int hashCode = InternalGetHashCode(key);
for (Grouping g = groupings[hashCode % groupings.Length]; g != null; g = g.hashNext)
if (g.hashCode == hashCode && comparer.Equals(g.key, key)) return g;
if (create)
{
if (count == groupings.Length) Resize();
int index = hashCode % groupings.Length;
Grouping g = new Grouping();
g.key = key;
g.hashCode = hashCode;
g.elements = new TElement[1];
g.hashNext = groupings[index];
groupings[index] = g;
if (lastGrouping == null)
{
g.next = g;
}
else
{
g.next = lastGrouping.next;
lastGrouping.next = g;
}
lastGrouping = g;
count++;
return g;
}
return null;
}

Đù. Code nó nhiều foreachfor vãi cả đ* mà méo hiểu sao nó nhanh thế nhỉ. Có lẽ có vấn đề gì đó. Hoặc có một thuật toán nào đó mà mình cũng chưa tìm hiểu tới. Nhưng phần nào cũng trả lời cho mọi người thấy là dùng Linq đúng chỗ sẽ tạo hiệu quả rất đáng ngạc nhiên. Và việc tôi dùng Linq để đối sánh giữa 2 mảng cũng rất là hợp lý. Hiện tại trong code mình cũng đã chuyển dần hết sang sử dụng Linq. Tuy nhiên cũng ko phải hoàn toàn. Trường hợp cần sử dụng for thì vẫn phải sử dụng.

Nếu bạn có ý kiến gì hay lý giải được tai sao một số trường hợp như dùng Join lại nhanh hơn for thì hãy chia sẻ để cùng trao đổi. Chúc cuối tuần vui vẻ

Sơn 20

Bài viết gốc được đăng tải tại sonpc20.com

Có thể bạn quan tâm:

Xem thêm Việc làm Developer hấp dẫn trên TopDev