Kąt widzenia: mechanizmy niskopoziomowe — jak T[] i List<T> działają w pamięci, kiedy rośnie GC pressure, jak to zmierzyć i jak tego uniknąć
Przykład przewodni: serwis przetwarzający telemetrię czujników w czasie zbliżonym do rzeczywistego
Skąd się bierze różnica w wydajności
Intuicja mówi: „List jest wolniejszy bo jest dynamiczny.” To prawda, ale nie wyjaśnia dlaczego. Żeby podjąć właściwą decyzję w kodzie produkcyjnym, musisz wiedzieć, co konkretnie kosztuje.
T[] — ciągły blok pamięci bez narzutu
double[] readings = new double[1024];
W pamięci: jeden nieprzerwany blok 1024 * 8 = 8192 bajtów. Dostęp do readings[i] to dosłownie jedno dodawanie adresu bazowego i przesuniętia:
address = base_address + (index * sizeof(double))
CPU cache prefetcher rozumie ten wzorzec — potrafi z wyprzedzeniem ładować kolejne elementy. Iteracja po tablicy jest cache-friendly, bo dane siedzą obok siebie.
Rozmiar jest stały — po new double[1024] nie możesz dodać 1025 elementu. Bez możliwości zmiany rozmiaru — brak realokacji, brak kopowania, brak dodatkowej pracy GC.
List<T> — tablica z metadanymi i mechanizmem wzrostu
public class List<T>
{
private T[] _items; // wewnętrzna tablica
private int _size; // Count — ile elementów faktycznie jest
// ...
}
List<T> to opakowanie na tablicę. Gdy robisz list[i] — w środku to _items[i]. Dostęp przez indeks jest tak samo szybki jak w tablicy.
Koszt pojawia się przy Add:
public void Add(T item)
{
if (_size == _items.Length)
Grow(_size + 1); // ← tu zaczyna się problem
_items[_size++] = item;
}
private void Grow(int capacity)
{
int newCapacity = _items.Length == 0 ? DefaultCapacity : 2 * _items.Length;
// alokacja nowej tablicy + Array.Copy() wszystkich elementów
}
Strategia podwajania (2 * _items.Length) oznacza, że realokacje zdarzają się logarytmicznie: przy pojemnościach 4, 8, 16, 32, 64 … Ale każda realokacja to:
- Alokacja nowego bloku w heap
Array.Copywszystkich istniejących elementów- Stara tablica trafia do GC
Przy małych listach to niezauważalne. Przy tysiącach Add w gorącej ścieżce — GC.Collect potrafi cię zaskoczyć.
Przykład: serwis telemetrii czujników
Wyobraź sobie serwis przyjmujący pakiety UDP od czujników temperatury w hali produkcyjnej. Każdy pakiet to tablica 256 odczytów double. Przychodzi 500 pakietów na sekundę.
Naiwna implementacja (problematyczna)
public class TelemetryProcessor
{
public List<double> ProcessPacket(byte[] rawPacket)
{
var readings = new List<double>(); // ← alokacja przy każdym pakiecie
for (int i = 0; i < rawPacket.Length; i += 8)
{
readings.Add(BitConverter.ToDouble(rawPacket, i)); // ← potencjalne resize
}
return readings;
}
public double CalculateAverage(List<double> readings)
{
double sum = 0;
foreach (var r in readings) sum += r;
return sum / readings.Count;
}
}
Przy 500 pakietach/s tworzymy 500 obiektów List<double> na sekundę. Każdy z nich zawiera wewnętrzną tablicę. GC musi je wszystkie zbierać. Przy dłuższej pracy — GC Gen2, pauzy, problemy z latencją.
Poprawna implementacja z T[] i Capacity
Jeśli wiesz z góry rozmiar — użyj tablicy:
public class TelemetryProcessor
{
private const int ReadingsPerPacket = 256;
public double[] ProcessPacket(byte[] rawPacket)
{
// Rozmiar znany z góry — jedna alokacja, żadnych resize
var readings = new double[ReadingsPerPacket];
for (int i = 0; i < ReadingsPerPacket; i++)
{
readings[i] = BitConverter.ToDouble(rawPacket, i * 8);
}
return readings;
}
public double CalculateAverage(double[] readings)
{
double sum = 0;
// Iteracja po T[] — cache-friendly, CLR optymalizuje bounds checking
for (int i = 0; i < readings.Length; i++)
sum += readings[i];
return sum / readings.Length;
}
}
ArrayPool<T> — gdy T[] też alokuje za dużo
Tablica rozwiązuje problem resize, ale przy 500 pakietach/s nadal tworzysz 500 double[256] na sekundę. Każda z nich trafia do GC.
Rozwiązanie: wypożycz tablicę, zamiast alokować nową.
public class TelemetryProcessor
{
private const int ReadingsPerPacket = 256;
// Singleton współdzielony w całej aplikacji
private static readonly ArrayPool<double> _pool = ArrayPool<double>.Shared;
public void ProcessPacket(byte[] rawPacket, out double average)
{
// Wypożyczamy tablicę z puli — może być większa niż żądana
double[] readings = _pool.Rent(ReadingsPerPacket);
try
{
for (int i = 0; i < ReadingsPerPacket; i++)
readings[i] = BitConverter.ToDouble(rawPacket, i * 8);
average = CalculateAverage(readings, ReadingsPerPacket);
}
finally
{
// Oddajemy tablicę do puli — GC nie musi jej zbierać
_pool.Return(readings, clearArray: false);
}
}
private static double CalculateAverage(double[] readings, int count)
{
double sum = 0;
for (int i = 0; i < count; i++)
sum += readings[i];
return sum / count;
}
}
Uwaga krytyczna: _pool.Rent(256) może zwrócić tablicę o rozmiarze 512 lub 1024 — pula trzyma tablice w „wiaderkach” potęg dwójki. Zawsze używaj count, nie readings.Length.
Uwaga bezpieczeństwa: zwróconej tablicy nie wolno zatrzymywać — następny Rent może zwrócić tę samą tablicę komuś innemu. Wzorzec try/finally jest tu obowiązkowy.
Span<T> — przetwarzanie bez alokacji
Gdy chcesz przetworzyć fragment tablicy bez kopiowania:
public void ProcessBatch(byte[] rawPackets, int packetCount)
{
// Span to widok na istniejącą pamięć — zero alokacji
ReadOnlySpan<byte> data = rawPackets;
for (int p = 0; p < packetCount; p++)
{
// Wycinek bez kopiowania danych
ReadOnlySpan<byte> packetSlice = data.Slice(p * 2048, 2048);
ProcessSinglePacket(packetSlice);
}
}
private void ProcessSinglePacket(ReadOnlySpan<byte> packet)
{
// Span<double> z reinterpretacją pamięci — nadal zero alokacji
ReadOnlySpan<double> readings = MemoryMarshal.Cast<byte, double>(packet);
double sum = 0;
foreach (double r in readings)
sum += r;
// ... przetwarzanie
}
MemoryMarshal.Cast reinterpretuje bajty jako doubles bez żadnego kopiowania. Całe przetwarzanie jednego pakietu — zero alokacji na stercie.
Ograniczenie Span<T>: nie możesz go przechowywać w polach klasy (tylko Memory<T>) i nie możesz użyć go po operacjach async.
Benchmark — żeby nie zgadywać
Zamiast intuicji — dane. BenchmarkDotNet:
[MemoryDiagnoser]
[SimpleJob(RuntimeMoniker.Net90)]
public class CollectionBenchmarks
{
private const int N = 1024;
private static readonly ArrayPool<double> Pool = ArrayPool<double>.Shared;
[Benchmark(Baseline = true)]
public double ListNoCapacity()
{
var list = new List<double>();
for (int i = 0; i < N; i++) list.Add(i * 1.5);
return list[N - 1];
}
[Benchmark]
public double ListWithCapacity()
{
var list = new List<double>(N);
for (int i = 0; i < N; i++) list.Add(i * 1.5);
return list[N - 1];
}
[Benchmark]
public double Array()
{
var arr = new double[N];
for (int i = 0; i < N; i++) arr[i] = i * 1.5;
return arr[N - 1];
}
[Benchmark]
public double ArrayPooled()
{
var arr = Pool.Rent(N);
try
{
for (int i = 0; i < N; i++) arr[i] = i * 1.5;
return arr[N - 1];
}
finally
{
Pool.Return(arr);
}
}
}
Typowe wyniki (N=1024, .NET 9):
| Method | Mean | Allocated |
|---|---|---|
| ListNoCapacity | 3.21 µs | 17.1 KB |
| ListWithCapacity | 1.18 µs | 8.1 KB |
| Array | 0.89 µs | 8.0 KB |
| ArrayPooled | 0.31 µs | 0 B |
ArrayPooled nie alokuje nic — bo tablica pochodzi z puli. To właśnie oznacza 0 B w kolumnie Allocated. W gorącej ścieżce przekłada się to bezpośrednio na mniejszą częstotliwość GC.
Count vs Capacity — co naprawdę zużywa pamięć
var list = new List<int>();
for (int i = 0; i < 5; i++) list.Add(i);
Console.WriteLine(list.Count); // 5 — ile elementów masz
Console.WriteLine(list.Capacity); // 8 — ile miejsca zarezerwowano
Pojemność po 5 Add: 4 → 8 (podwojenie przy czwartym Add). Pięć elementów zajmuje miejsce dla ośmiu. Strata 37.5% zarezerwowanej pamięci.
Jeśli tworzysz listę, wypełniasz ją i trzymasz długo (cache, singleton):
var list = BuildLargeList(); // powiedzmy 10 000 elementów, Capacity = 16384
list.TrimExcess(); // zwalnia nadmiar — Capacity == Count
TrimExcess() alokuje nową tablicę o rozmiarze Count i kopiuje dane. Sens tylko gdy lista żyje długo i nie będzie już modyfikowana.
Kiedy co
| Scenariusz | Wybór | Dlaczego |
|---|---|---|
| Znany rozmiar, dane statyczne | T[] | Jedna alokacja, brak narzutu |
| Nieznany rozmiar, budowanie stopniowo | List<T>(capacity) | Capacity eliminuje resize |
| Gorąca ścieżka, 1000+/s | ArrayPool<T> | Zero alokacji na GC heap |
| Przetwarzanie fragmentu bez kopii | Span<T> / Memory<T> | Zero alokacji |
| API publiczne (metoda zwraca kolekcję) | IReadOnlyList<T> | Kontrakt bez exposowania Add/Remove |
Uwaga: IReadOnlyList<T> i ImmutableArray<T> to temat na osobny post — dotyczą kontraktu API, nie wydajności alokacji.
Interesuje Cię ta sama decyzja z perspektywy projektowania API, nie tylko wydajności? Zobacz Array vs List w C# — API design i decyzje. Podstawy samej listy znajdziesz w List<T> w C#.
Podsumowanie
T[] i List<T> różnią się głównie w jednym miejscu: co dzieje się, gdy kolekcja musi rosnąć. Tablica nie rośnie — jedna alokacja, koniec. Lista rośnie przez podwajanie — koszt kopiowania + GC pressure przy każdej realokacji.
W praktyce produkcyjnej trzy zasady:
- Znasz rozmiar z góry →
T[] - Nie znasz rozmiaru, ale masz przybliżenie →
new List<T>(capacity) - Gorąca ścieżka, alokacje się liczą →
ArrayPool<T>+Span<T>
Stopwatch i intuicja kłamią — BenchmarkDotNet z [MemoryDiagnoser] mówi prawdę.
Następny post: Array vs List w C# — API Design i Decyhttps://dev-hobby.pl/csharp/array-vs-list/zje Projektowe — nie wydajność, ale kontrakt: co zwracać z metod publicznych, kiedy IReadOnlyList<T>, a kiedy ImmutableArray<T>, i jak uniknąć wycieków List<T> przez interfejsy.
🚀 Co dalej?
Zobacz to w praktyce na wideo i pobierz darmową roadmapę, żeby ułożyć naukę w spójną ścieżkę do pierwszej pracy.
- 🗺️ Pobierz darmową roadmapę Junior .NET Developer — 12 kroków od podstaw C# do pierwszej pracy: dev-hobby.pl
- 🎬 Subskrybuj kanał YouTube — nowe filmy co tydzień.
114 comments
Dodaj komentarz
Musisz się zalogować, aby móc dodać komentarz.
Zamień wiedzę w umiejętności
Pobierz darmową Roadmapę .NET i ułóż takie tematy jak ten w spójną ścieżkę do pierwszej pracy.
Pobieram roadmapę →
Wow! In the end I got a website from where I be capable of in fact get valuable information concerning my study and knowledge. Mimi Reinold Lionel
Thank you ever so for you article. Much thanks again. Awesome. Patricia Mischa Streeto
Way cool! Some extremely valid points! I appreciate you penning this write-up and also the rest of the website is really good. Raquela Rowney Gino
Thanks for your whole work on this website. My niece takes pleasure in working on research and it is simple to grasp why. We all notice all relating to the powerful ways you render vital tricks via your website and therefore welcome response from website visitors on this topic while our own simple princess is without question understanding so much. Take pleasure in the rest of the year. Your performing a very good job. Robina Peadar Darrin
It is best to participate in a contest for among the best blogs on the web. Laureen Paddy Zumstein
matter to be really one thing that I think I might never understand. Eleanore Julian Aprile
thanks Jill. I always wonder how insights into the making of writing are viewed, especially ones that draw on personal circumstances. Are they too immature? Should they be hidden from sight? But they help me understand not only the process of writing but the process of living, and how the two are connected. Pepita Travus Aholla
Zourbuth, thanks for the incredible plug-in! It really looks great! Marilee Allistir Sontag
Very informative information. I plan to share this with friends and family. I also plan to visit the web site you gave us. Really liked the picture of the hand reaching for the credit card! Nerte Delano Sherill
Thank you ever so for you blog article. Really looking forward to read more. Want more. Philippine Cullie Amaryl
Hurrah! After all I got a weblog from where I know how to truly take valuable facts regarding my study and knowledge. Christen Dukey Palermo
Wow, superb blog layout! How long have you been blogging for? Shawn Brig Gard
Id have to examine with you here. Which is not one thing I usually do! I enjoy studying a submit that may make people think. Also, thanks for allowing me to comment! Malinda Ase Veleda
Asking questions are genuinely pleasant thing if you are not understanding anything completely, except this post offers pleasant understanding yet.| Collen Roarke Samul
Way cool! Some extremely valid points! I appreciate you writing this write-up and the rest of the website is very good. Vivia Lazar Burney
Thank you so much and I am looking ahead to touch you. Lorilyn Garold Touber Christian Cece Brandyn
Good blog post. I certainly love this site. Stick with it! Katrine Bradford Carmella
thanks
Hey there! Do you know if they make any plugins to assist with SEO? Aloysia Teador Winni
I visited various sites but the audio quality for audio songs current at this web page is genuinely superb.| Vanda Germain Alesandrini
Hi friends, how is the whole thing, and what you desire to say on the topic of this piece of writing, in my view its actually amazing for me. Nora Bradford Leake
I am continually searching online for articles that can facilitate me. Thanks! Alidia Costa Sulamith
Thank you so much for sharing your experience with Wealthy Affiliate, and I wish you success with your business! Patty Sanders Luthanen
I really appreciate this post. I have been looking all over for this! Thank goodness I found it on Bing. You have made my day! Thx again! Karel Micky Cavanaugh
Everything is very open with a clear clarification of the issues. It was really informative. Your website is extremely helpful. Thanks for sharing! Enrichetta Richard Karly
thanks
Yes. So much good stuff here. I especially love practicing gratitude. Selma Balduin Annamarie
Superb, what a website it is! This website gives useful information to us, keep it up. Deb Davis Neille
thanks
I have read a few good stuff here. Certainly worth bookmarking for revisiting. I wonder how much effort you put to create such a excellent informative site. Sukey Svend Karlin
thanks
This is one awesome blog article. Much thanks again. Really Cool. Ingunna Natty Millford
thanks
I like it when folks come together and share views. Great site, stick with it! Krystal Lorne Bessie
thanks
Everyone loves it when folks get together and share ideas. Great site, stick with it! Marlane Albie Hesky
Everything is very open with a very clear description of the issues. It was really informative. Your website is useful. Thank you for sharing! Kylen Jarret Niple
thanks
Pretty! This was a really wonderful article. Thanks for providing this information. Dale Leonidas Lenhard
thanks
excellent issues altogether, you simply gained a new reader. Georgeanne Sherlocke Cown
thanks
Way cool! Some extremely valid points! I appreciate you writing this write-up plus the rest of the website is really good. Hilda Hamilton Diane-Marie
thanks
bardzo ciekawy artykuł, pisz dalej…
dzieki
Chciałbym zostać praktykantem, podczas gdy ty piszesz, jak mogę zapisać się na blog?
dzieki
Wszyscy uwielbiają, gdy ludzie spotykają się i dzielą przemyśleniami. Świetna strona internetowa, kontynuuj dobrą pracę!
dzieki
Wow, fantastyczny układ bloga! Jak długo blogujesz? dzięki tobie blogowanie wygląda na łatwe. Ogólny wygląd Twojej witryny internetowej jest świetny, podobnie jak zawartość!
dzieki
Ta informacja jest bezcenna. Jak mogę dowiedzieć się więcej?
dzieki wielkie
dobra robota, tak trzymaj!
dzieki
WOW, właśnie tego szukałem. Przyszedłem tutaj, wyszukując słowo kluczowe
dzieki
Mój programista próbuje przekonać mnie do przejścia na .net z PHP.
dobry wybór. dzieki
Sprawdzałem wiele twoich wpisów i muszę powiedzieć, że całkiem niezłe rzeczy. Na pewno dodam twoją stronę do zakładek.
dzieki
bardzo doceniam Twoja robote!
dzieki
super wooww..
dzieki
Ten projekt jest niesamowity! Oczywiście wiesz, jak zapewnić rozrywkę czytelnikowi.
dzieki
Czy nie masz nic przeciwko, jeśli zacytuję kilka Twoich postów, o ile podam informacje o autorach i źródłach z powrotem do Twojej witryny? Mój blog znajduje się dokładnie w tej samej niszy, co Twoja, a moi goście z pewnością skorzystaliby z wielu informacji, które tu podajesz. Daj mi znać, jeśli ci to odpowiada. Pozdrowienia!
dzieki. bedzie mi bardzo miło, nie ma problemu
Szacunek dla autora strony, kilka dobrych selektywnych informacji.
dzieki
Wspaniała strona internetowa. Wiele pomocnych informacji tutaj.
dzieki
Dziś surfuję po sieci ponad 4 godziny, ale nigdy nie znalazłem interesującego artykułu takiego jak Twój. To jest dla mnie wystarczająco warte. Osobiście, jeśli wszyscy właściciele witryn i blogerzy stworzyli dobre treści, tak jak Ty, Internet będzie o wiele bardziej przydatny niż kiedykolwiek wcześniej.
dzieki
bardzo dobrze się to czyta merytoryczna zawartosc!
dzieki
Świetny blog tutaj! Dodatkowo twoja strona www zmienia się bardzo szybko!
dzieki
Cześć. Odkryłem Twoją witrynę za pośrednictwem Google, gdy szukałem podobnej sprawy, Twoja witryna pojawiła się. Wygląda na to, że jest dobra. Dodałem go do zakładek w moich zakładkach Google, aby ją odwiedzić.
dzięki
Zrobiłeś tam kilka fajnych uwag. Poszukałem tematu w Internecie i stwierdziłem, że większość osób zgadza się z twoją witryną.
dzieki
Lubię czytać post, który skłoni ludzi do myślenia. Dziękuję również za umożliwienie mi komentowania. Melissa Christos Sheehan
dzieki
W sumie wspaniałe wydania, właśnie otrzymałeś nowego czytelnika. Nicol Niven Breanne
dzieki
Lubię czytać post, który sprawi, że ludzie myślą. Wielkie dzięki za umożliwienie mi komentowania!
dzieki
To moja ulubioną tę stronę internetową, wydaje się bardzo korzystna, nieoceniona. Eliza
dziękuje
dobra robota, czytam wszystko co jest
dzieki
Dobry artykuł, wspaniała robota, czymaj tak dalej
dzięki
bardzo dobry post
This is very interesting, You are a very skilled blogger. I’ve joined your feed and look forward to seeking more of your excellent post. Also, I’ve shared your web site in my social networks!
thanks
We are a gaggle of volunteers and opening a brand new scheme in our community. You’ve performed an impressive task and our entire community will probably be thankful to you.
Thank you very much
Bardzo inspirujący artykuł! Cieszę się, że natrafiłem na Twojego bloga
Wspaniałe podejście do tematu. Mam nadzieję, że napiszesz więcej na ten temat
Świetne porady! Chętnie przetestuję to w swoim kodzie.
Świetny artykuł! Bardzo klarowne wyjaśnienia i przykłady
Artykuł jest inspirujący! Dzięki niemu mam ochotę eksperymentować i pogłębiać swoją wiedzę na ten temat
Twoje przykłady kodu są bardzo pomocne. Pomagają zrozumieć, jak zastosować te koncepcje w praktyce
Twój artykuł zawsze jest pierwszym miejscem, gdzie zaglądam, gdy chcę dowiedzieć się więcej na temat
Masz umiejętność przekazywania informacji w sposób, który sprawia, że każdy może zrozumieć. To niesamowite
Thank you for sharing all these wonderful blogposts.
Zawsze znajduję tutaj wartościowe informacje. Dzięki za to
Ciekawy punkt widzenia. Wartościowe spojrzenie na temat. Świetne praktyczne przykłady. To, co najbardziej lubię w Twoich artykułach