Действующие лица:

МП (Молодой Пол) — год в индустрии, глаза горят, в голове свежий Clean Architecture. Верит, что облако бесконечно, а new — почти как комментарий: написал и забыл.

ДП (Дядя Паша) — 47 лет, седая борода, архитектор. Первый продакшен — в 12: школьная программа по астрономии, которая мерила знания и ломала нервы отличникам. Сейчас — терраса в Палермо, бокал холодного Мальбек, на экране — метрики и паузы GC.


Диалог:

МП:
— Дядь Паш, беда. Подняли микросервис под телеметрию. Всё красиво: объекты, «Стратегия», SOLID — прям учебник. Но счёт из облака как за чугунный мост. Латентность пляшет, как танго на стероидах. Может, просто накинуть ещё инстансов в Microsoft Azure?

ДП:
(не спеша отрывается от терминала)
— Инстансов… чтобы они быстрее мусор перемалывали? Скажи лучше: зачем ты для каждой точки — а их миллионы — создаёшь отдельный class?

МП:
— Ну… ООП же. Ссылочные типы, гибкость. Плюс, современные GC — они же быстрые, миллисекунды…

ДП:
— Миллисекунды тут, миллисекунды там — и у тебя уже не сервис, а астматик. Каждый new — это поход в Heap: найти место, записать заголовок, привязать ссылки. А потом приходит сборщик и начинает за тобой подметать. Миллион объектов — и CPU вместо работы считает чужой мусор.

МП:
— То есть назад, в пещеры? Массивы, указатели, как в C++?

ДП:
— Не путай аскезу с эффективностью. Возьми struct. Если у тебя данные — просто числа, без поведения, без мутаций — делай readonly struct.
Они лягут в память плотно. Без заголовков. Без разрывов. Кэш их любит. Процессор — тем более. А GC…
(делает глоток)
— GC даже не узнает, что они существовали.

МП:
— И это даст профит? Серьёзно? Звучит как микрооптимизация ради самоуспокоения.

ДП:
— Микро — это когда один объект. У тебя — миллионы. Это уже экономика, Пол.
Плотность данных. Предсказуемость. Меньше аллокаций — меньше пауз. Меньше пауз — стабильнее latency.
А стабильность — это деньги.


МП:
— Стой, дядь Паш. Я же не дурак, я помню: структуры — это стек. А стек — это кэш потока, он же не резиновый! Всего 1 мегабайт по дефолту в Windows, ну чуть больше в Linux. Если я туда засуну миллион своих точек — я же получу StackOverflowException быстрее, чем успею сказать «Мальбек». Ты мне предлагаешь систему уронить?

ДП:
(усмехается, рассматривая вино на свет)
— Молодец, теорию учил. Но путаешь тёплое с мягким. Если ты создаёшь локальную переменную-структуру в методе — она в стеке. Но если ты создаёшь массив структур new PointStruct[1_000_000] — где он будет лежать, а?

МП:
(задумался)
— Массив… массив — это объект. Значит, сам массив в куче (Heap).

ДП:
— Именно. Массив в куче, но внутри него — не ссылки на объекты, разбросанные по всей памяти, а сами структуры. Плотным блоком. Ты получаешь лучшее из двух миров: объём кучи и скорость стека за счёт локальности данных.

МП:
— Ладно, тут подловил. Но если мне реально нужно больше стека под тяжёлые вычисления? В тех же рекурсиях или спанах?

ДП:
— Для этого есть старый добрый фокус с созданием потока. Ты же знаешь, что в конструкторе new Thread() можно вторым параметром передать maxStackSize? Хочешь 10 мегабайт — бери 10. Но это, Пол... что Пашка по нашему, костыль для тех, кто не умеет в итерацию.

— Настоящий профи не раздувает стек, он заставляет данные лежать так, чтобы процессор их «щёлкал» не глядя.
Садись, смотри на бенчмарки...


📊 Результаты, которые ДП показывает на мониторе:

Вот теперь цифры похожи на правду. Разница в 16% (Ratio 0.84) в пользу структур — это уже аргумент, за который можно уважать код, а не оправдывать его.

И да, на твоём i7-8750H (Coffee Lake) могло быть ещё жирнее. В логе видно [AttachedDebugger] — рантайм слегка «держит тормоз». Но для демонстрации принципа — более чем.

Метод

Среднее время

Отношение

Аллокации

Sum_Class_Read

21.15 ms

1.00 (База)

1 B

Sum_ReadonlyStruct_Read

17.63 ms

0.84 (на 16% быстрее)

2 B

Репозиторий с бенчмарками:
👉 https://github.com/PavelPavlovIT/DotNet-Performance-Struct-Vs-Class.git


ДП:
— Видишь, Пол? Даже на твоём ноутбуке мы просто «бесплатно» получили 16% скорости. А теперь представь, что это не сумма, а сложная математика в tight-loop на сервере с сотнями ядер.
Там, где классы заставят процессор захлебнуться в ожидании данных из памяти — структуры будут пролетать со свистом.


🧠 Почему это «взрывает мозг»

Чтобы не было магии уровня «заменил class на struct и пошёл дальше», давай по-честному:

1. CPU Cache Prefetching
Процессор — не дурак. Если видит линейный доступ (массив структур), он заранее тянет данные в L1/L2 кэш.
С классами — хаос. Он не знает, куда поведёт следующая ссылка → ждёт RAM.

2. Никакого Dereferencing
Класс:
адрес → переход → заголовок → данные

Структура:
данные

Минус один прыжок = минус латентность на каждом элементе.

3. Плотность данных
64 байта (cache line):

  • структуры → несколько элементов сразу

  • классы → дай бог один, с учётом заголовков

Больше данных за один fetch → меньше походов в память.


МП:
— (чешет затылок, глядя в монитор) Ну хорошо, цифры я вижу. В 4 раза быстрее. Но почему? .NET тот же, процессор тот же, код почти одинаковый. В чем прикол-то?

ДП:
(наливает ещё немного Мальбек, рисует пальцем на столе два прямоугольника)
— Прикол в «железе», Пол. Когда у тебя массив классов — это коробка с визитками. Внутри только адреса.
Чтобы достать данные — процессор бегает по памяти, как курьер по району.

— А массив структур — это книга. Всё лежит подряд.
Процессор открывает страницу — и читает без остановки. Это и есть Locality of Reference.

МП:
— То есть структуры быстрее, потому что они «ближе» к процессору?

ДП:
— Именно. И это ещё не всё. Класс — это «живой» объект для GC. Миллион объектов — миллион проверок.
А массив структур — один блок. GC посмотрел — и пошёл дальше.

МП:
— Получается, это не про синтаксис, а про железо?

ДП:
— В точку. Не понимаешь, где лежат данные — ты не архитектор. Ты — художник.
Рисуешь красиво, а процессор в это время задыхается.

— Вот поэтому на платформе «Я хочу знать» у меня целый раздел про Memory Management. Без этого любой high-load складывается сам в себя.


МП:
— Слушай, дядь Паш... Я ведь реально думал, что всё знаю про .NET. А тут — база, и я на ней поплыл. Сколько ещё таких «дыр» у меня в голове?

ДП:
(допивает Мальбек и прищуривается)
— В голове у тебя, Пол, пока не архитектура, а эскиз карандашом. Знать синтаксис и строить системы — разные лиги.

МП:
— И как понять, на каком я уровне? Опять собесы?

ДП:
— Я для таких, как ты, систему сделал. 15+ тысяч вопросов, почти 100 категорий. От базы до продакшена.
Пройдёшь — увидишь, где у тебя фундамент трещит.

— Называется «Я хочу знать .NET».

МП:
— И что, покажешь?

ДП:
— Держи: iwanttoknow.net.
Если не боишься увидеть правду о своих скиллах.