Chciałbym Ci przedstawić moje testy wydajnościowe przeszukiwania kolekcji. W C# mamy kilka możliwości przeszukania kolekcji. Możemy użyć LINQ, pętle for czy foreach. Pewnie znajdzie się ktoś kto przeszukuje pętlą while. Ja skupię się tylko na LINQ oraz pętlach for i foreach.
Do testów użyję BenchmarkDotNet a do zapewnienia losowości danych użyję Bogus. W tym artykule nie zamierzam szerzej omawiać tych bibliotek. Jeśli ich nie znasz to zachęcam do zapoznania się z nimi. Zwłaszcza z BenchmarkDotNet.
Testy
Co prawda większość z nas wie, że najszybsza będzi pętla for. No ale sprawdźmy 🙂 Zamierzam uruchomić testy dla listy zawierającej następującą ilość elementów:
- 10
- 100
- 1000
- 10000
- 100000
- 1000000
- 10000000
Klasa, która zawiera wszystkie nasze testy wygląda następująco:
using System;
using System.Collections.Generic;
using System.Linq;
using BenchmarkDotNet.Attributes;
using Bogus;
namespace LinqPerformanceTester
{
[SimpleJob(targetCount: 5)]
[MemoryDiagnoser]
public class CollectionFinderTest
{
private IList<Item> _items;
[Params(10, 100, 1000, 10000, 100000, 1000000, 10000000)]
public int N;
[GlobalSetup]
public void Setup()
{
var idx = 0;
var random = new Bogus.Randomizer();
_items = new Faker<Item>()
.RuleFor(x => x.Id, f => idx++)
.RuleFor(x => x.Name, f => f.Lorem.Word())
.Generate(N);
}
[Benchmark]
public void ForLoopFindString()
{
for (var i = 0; i < _items.Count; i++)
{
if (_items[i].Name.Contains("aaa"))
{
return;
}
}
}
[Benchmark]
public void ForLoopFindInt()
{
for (var i = 0; i < _items.Count; i++)
{
if (_items[i].Id == _items.Count - 1)
{
return;
}
}
}
[Benchmark]
public void ForEachLoopFindString()
{
foreach (var item in _items)
{
if (item.Name.Contains("aaa"))
{
return;
}
}
}
[Benchmark]
public void ForEachLoopFindInt()
{
foreach (var item in _items)
{
if (item.Id == _items.Count - 1)
{
return;
}
}
}
[Benchmark]
public void LinqFirstOrDefaultFindString()
{
_items.FirstOrDefault(x => x.Name.Contains("aaa"));
}
[Benchmark]
public void LinqFirstOrDefaultFindInt()
{
_items.FirstOrDefault(x => x.Id == _items.Count - 1);
}
}
}
Jak można zauważyć mamy listę obiektów (_items) klasy Item:
public class Item
{
public int Id { get; set; }
public string Name { get; set; }
}
Będziemy szukać stringa (Name) lub inta (Id). Nie chodzi mi tutaj o to żeby coś znaleźć ale żeby szukać 🙂 Myślę, że nazwy metod są wystarczającym opisem tego co sprawdzamy w danej metodzie.
Wyniki
Wyniki dla przeszukiwania tablicy po Id:

Wyniki dla przeszukiwania tablicy po Name:

Wnioski
Czas wyszukiwania oraz ilość zaalokowanej pamięci różni się dla poszczególnych metod. Różnice te są mniejsze lub większe w zaleźności od liczby elementów. Tak jak można zauważyć pętla for jest najszybsza i alokuje najmniej pamięci.
Kod źródłowy znajduje się na github https://github.com/letyshub/PerformanceTests