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.
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.
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 for
, foreach
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
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 for
, foreach
, while
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
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 foreach
, for
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:
- for vs forEach vs for/in vs for/of trong javascript
- Tài liệu làm chủ Python trong vòng 4 tuần (Phần 1)
- Tôi đã tối ưu WordPress nhanh hơn 18 lần như thế nào?
Xem thêm Việc làm Developer hấp dẫn trên TopDev