惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

N
News | PayPal Newsroom
P
Proofpoint News Feed
Cyberwarzone
Cyberwarzone
C
Cisco Blogs
SecWiki News
SecWiki News
Know Your Adversary
Know Your Adversary
cs.AI updates on arXiv.org
cs.AI updates on arXiv.org
Vercel News
Vercel News
freeCodeCamp Programming Tutorials: Python, JavaScript, Git & More
罗磊的独立博客
NISL@THU
NISL@THU
WordPress大学
WordPress大学
Hacker News - Newest:
Hacker News - Newest: "LLM"
T
Threat Research - Cisco Blogs
AI
AI
Simon Willison's Weblog
Simon Willison's Weblog
Security Archives - TechRepublic
Security Archives - TechRepublic
有赞技术团队
有赞技术团队
L
LINUX DO - 热门话题
Hacker News: Ask HN
Hacker News: Ask HN
V
V2EX
G
GRAHAM CLULEY
TaoSecurity Blog
TaoSecurity Blog
Hugging Face - Blog
Hugging Face - Blog
D
Darknet – Hacking Tools, Hacker News & Cyber Security
F
Fortinet All Blogs
博客园 - 叶小钗
博客园 - 三生石上(FineUI控件)
云风的 BLOG
云风的 BLOG
Recorded Future
Recorded Future
Latest news
Latest news
The Hacker News
The Hacker News
aimingoo的专栏
aimingoo的专栏
T
Troy Hunt's Blog
S
Schneier on Security
I
Intezer
Google DeepMind News
Google DeepMind News
A
Arctic Wolf
Apple Machine Learning Research
Apple Machine Learning Research
Threat Intelligence Blog | Flashpoint
Threat Intelligence Blog | Flashpoint
T
Threatpost
爱范儿
爱范儿
The Register - Security
The Register - Security
S
SegmentFault 最新的问题
Blog — PlanetScale
Blog — PlanetScale
博客园 - 聂微东
宝玉的分享
宝玉的分享
Recent Commits to openclaw:main
Recent Commits to openclaw:main
美团技术团队
B
Blog RSS Feed

Все публикации подряд на Хабре

Ловим музу за клавиатуру: как айтишнику стать автором Что умеет Midjourney в 2026? Мой немного грустный разбор этого шикарного инструмента Никто не любит писать тесты, но ИИ может исправить это IPv8 выглядит как мечта. Поэтому почти наверняка не взлетит Производители вернули в продажу материнки с DDR3. Что происходит? Управление агентом с телефона через Telegram теперь в KodaCode От координации к лидерству: как меняется роль руководителя разработки Я сделала родителям бизнес вместо пенсии: зарабатываем 70 тысяч, мама не даёт продать В три раза быстрее приемка товара и оптимизация трудозатрат на 73%: как «РСТ-Инвент» помог Gulliver Group ИИ-шечный мир победил? О влиянии искусственного интеллекта на игропром Кремль снижает давление на Телеграмм пока Европа строит интернет по паспорту Как CEO, CTO и CIO за 8 часов собрали ИИ-директора, который умеет держать позицию под давлением Как (не) потерять домен за выходные Вместо 8 разных VPS: как я организовал практику студентам на одном сервере Почему твой Open Source проект не замечают? R&D: искусство управления неопределенностью в разработке AI-дефляция: вакансий для разработчиков больше, а рост зарплат — худший за 15 лет Мы отдали управление роботами OpenClaw. Что из этого вышло Галактический ID: система идентификации для всех форм разумной жизни Шесть основ бизнес-анализа: начинаем с вопроса «Кто в игре?» Код-ревью, в котором дело не в коде Данные переехали. Команда — нет Системной подход к сдаче OSWE в 2025 Почему комната управления реактором покрашена в цвет морской пены 4 YAML-файла вместо PySpark: как аналитикам строить пайплайны без разработчиков LLM-агент для поиска свободных доменов: автоматизируем подбор Когда, зачем и как правильно начинать новую сессию в Claude Code? Как я заставил нейросеть писать макросы для FreeCAD Анатомия ИИ‑агента для подбора персонала. От тысячи резюме к топ‑10 за минуты Опыт разработчика как экономика внимания Автономность как точка невозврата: кто будет субъектом в цифровом будущем Обучение ИИ в «диких» условиях: как рутинные действия превращаются в датасеты Как измерить LLM для задач кибербеза: обзор открытых бенчмарков Где хранить код? Сравнение GitHub, GitLab и Bitbucket Математика объясняет, почему нормальное распределение встречается повсюду Почему ваш FinOps не работает: 12 тезисов от практиков Как подписать проектную документацию УКЭП с использованием бесплатных лицензий Pilot Адаптивное администрирование Sigla Vision Я грузил уран в бочки, а потом 20 лет строил ИТ в атомной отрасли Чем позвонить с Эвереста? История и обзор спутниковой связи. Часть 2 Как языковая модель помогает контролировать качество инструктажей по охране труда в металлургии Как не передать на desktop свой IP в РКН Анатомия SAP Privileges: как устроено управление правами в macOS MoneyDev: Сказка про три главных слова Обновлённый токенизатор видео K-VAE 2.0 от Сбера Как сделать диспетчеризацию дома на 1284 квартиры почти бесплатно Как мы разогнали железную дорогу Мы дали агентам рутину. Теперь надо решить — что делать с освободившимся временем Токсичный контент, промпт-хакинг и защита ИИ — всё о Guardrails для LLM Умный город начинается с точного взгляда: как «Фалькон Тех» меняет пространство к лучшему Навайбкодил приложение для анализа графов Почему Дюну так интересно читать? Упрощаем работу с рутиной или как стать Гендальфом Белым Деконструкция Go: CPU, RAM и что там происходит. Go Assembler база. Часть 1.1 Какие профессии исчезнут из-за ИИ, а какие появятся? И что с этим делать Как мы построили IT-отдел, где хочется расти: архитектурные встречи, прозрачные метрики и книжные подарки Rufler: Делаем из Claude Code автономный рой через один YAML-конфиг Sing-box и белый список приложений Как построить надёжный обмен сообщениями в микросервисах: лучшие практики для enterprise OpenAI строит MLM-пирамиду, а McKinsey и Accenture помогают ей в этом Дом, который не построил Фишер (Часть 2) «Сверхзвуковой математик» против «Вдумчивого логиста»: битва алгоритмов 3D-упаковки Мультимодальные модели – грубый и дорогой инструмент Разговоры ничего не стоят. Код тоже Проверки физических лиц: с кого начнет ФНС Топ-10 бесплатных нейросетей для создания видео в 2026 году Первые слои кода: как наши решения сегодня определяют архитектуру ИИ на десятилетия Разработка нового статического анализатора: PVS-Studio JavaScript Поиск уязвимостей ПО: базовый минимум или роскошный максимум Почему оценка персонала не работает как инструмент управления Как мы разработали ИИ-ассистента и сократили рутину продуктовой команды на 50% Как я ушел из найма, нажарил косточек и продал на маркетплейсах на 168 млн в год Когда 1С:ERP уже внедрена, а нормального производственного плана всё ещё нет Как я сделал Claude мультимодальным, подключив к нему Qwen Omni Как приглашение на вакансию мечты превращается в атаку Infrastructure as Code: философия и лучшие практики IaC Тестируем Yandex Code Assistant на задаче, в которой нужно хранить секреты nxs-universal-chart v3.0: новое поколение универсального Helm-чарта Callback Injection: Техника, которая отправила Microsoft Defender в глухой нокаут «Все идеи на стол»: митап как способ вывести проект из тупика Сегодня я узнал нечто новое о GPU благодаря багу в своей игре Как заставить LLM ̶ ̶г̶а̶л̶л̶ю̶ ̶ эволюционировать Карта событий как фундамент аналитики: практический кейс для E-commerce Что выбрать для AI: x86, ARM или RISC-V? Дайджест железа за март Роль соматических мутаций в развитии аутоиммунных заболеваний: путь к избирательной терапии Mythos от Anthropic — тревожный сигнал для всех, а не только для банков Guardrails для LLM на Java: как приручить промпт‑инъекции и токсичные ответы Green-VLA: как мы собрали VLA-модель для реального антропоморфного робота и не потеряли обобщение Финансовая гонка вооружений: почему умные люди добровольно в ней участвуют Эра ИИ-агентов наступила: выбираем лучшего цифрового сотрудника # Практический опыт внедрения WinCC Redundancy на производственном предприятии Сделал MVP за 3 дня, а потом неделю прикручивал оплату. Оно того стоило? Физика против Маска: почему Starship V3 может оказаться ещё одной катастрофой Нефть Венесуэлы: крупнейшие запасы в мире, но не крупнейшая нефтяная держава JPA 4. Переосмысление Hibernate Почему зеркальная фотокамера Nikon D5 десятилетней давности идеально подошла для миссии «Артемида-2» Проект «Уровень-Спутник» или как мы сделали платформу для гидрологов «Замедлиться, чтобы ускориться»: почему ИИ повышает цену ошибок в требованиях и архитектуре Как с нуля поднять трафик IT-компании на 1657% при бюджете 55 тыс. и выжить Pixel-perfect Downsampling — идеальная отрисовка 50 миллионов точек без потерь
Убейте GC-спайки в играх и сервисах: коллекции без аллокаций на .NET
Архипов Владимир · 2026-06-01 · via Все публикации подряд на Хабре

8 мин

10K

Каждый разработчик на .NET сталкивался с этим. Всё работает быстро, но иногда случается внезапный фриз. Игра проседает с 60 до 30 FPS на секунду. Сервис отвечает на запрос 100 мс вместо обычных 10. UI дёргается.

Виновник — Garbage Collector.

Когда GC решает собрать мусор, он останавливает все потоки приложения (Stop-The-World). Для игр и real-time сервисов это катастрофа.

Стандартные коллекции .NET создают мусор везде:

// Каждая из этих операций аллоцирует память
var list = new List<int>();           // аллокация
list.Add(42);                          // может аллоцировать при росте capacity
var result = list.Where(x => x > 10); // аллокация итератора
var arr = list.ToArray();              // аллокация массива
list.Clear();                          // O(N) — обход массива, но не аллокация

В типичном игровом кадре может быть сотни таких операций. Мусор накапливается, GC собирает его в самый неподходящий момент — во время босс-файта.

Что с этим делают обычно

Подход

Проблемы

Ограничивать аллокации вручную

Очень сложно, легко ошибиться

Использовать ArrayPool<T>

Неудобно, нет поддержки Dictionary/HashSet, нужно вручную возвращать

Использовать struct и Span<T>

Не покрывает все сценарии (словари, хеш-сеты)

Unity Collections + Burst

Работает только в Unity, сложный API

Свои пулы

Долго писать и отлаживать, особенно для сложных коллекций

Решение: GC-free коллекции с пулингом и знакомым API

GcFreeCollections — библиотека для .NET 8.0+ и Unity, которая заменяет стандартные коллекции на их GC-free аналоги.

dotnet add package GcFreeCollections --version 1.0.0

Ключевые возможности:

  1. PooledList<T> — замена List<T> без аллокаций после прогрева

  2. PooledDictionary<TKey,TValue> — замена Dictionary<K,V>

  3. PooledHashSet<T> — замена HashSet<T>

  4. PooledQueue<T>, PooledStack<T>, PooledPriorityQueue<T,P>

  5. PooledMemoryStream — замена MemoryStream

  6. PooledStringBuilder — замена StringBuilder с меньшим числом аллокаций

  7. PooledQuery — LINQ-пайплайн без аллокаций

  8. HotPath.Enter() — отлавливает случайные аллокации в DEBUG

  9. Leak tracking — находит утечки пулированных объектов

  10. Reference Quarantine — безопасная очистка через Maintain()

Чем отличается от стандартных коллекций

Характеристика

List<T>

PooledList<T>

Аллокация при создании

Есть (new List<T>())

Нет (берётся из пула)

Аллокация при добавлении элементов

Есть (при росте capacity)

Нет после прогрева (capacity фиксирован или пул расширяется)

Аллокация при Clear()

Нет, но O(N) обход

Нет, O(1) (Hot-first Clear)

Возврат памяти

Только когда GC соберёт

Явный возврат в пул (Dispose)

Отслеживание утечек

Нет

Да (LogLeaks)

Защита от аллокаций в hot path

Нет

Да (HotPath.Enter)

Ключевое отличие: стандартные коллекции оперируют на уровне "создал-использовал-забыл-пусть-GC разбирается". GcFreeCollections — "взял-из-пула-использовал-вернул-в-пул".

Быстрый старт

Шаг 1. Подключение пространства имён

using GcFreeCollections;

Шаг 2. PooledList — замена List

// Вместо var list = new List<int>();
using var list = PooledList<int>.Create();
list.Add(42);
list.Add(100);
list.Add(73);
foreach (var x in list) // struct enumerator, без аллокаций
{
    Console.WriteLine(x);
}
list.Clear(); // O(1), без обхода массива
// В конце using автоматически вернёт list в пул

Важно: после Clear() элементы не зануляются мгновенно. Вместо этого используется Reference Quarantine.

Шаг 3. Hot-first Clear и Maintain

// В игровом кадре
using var enemies = PooledList<Enemy>.Create();
// ... добавляем врагов, работаем ...
enemies.Clear(); // O(1) — быстрая очистка
// В конце кадра (или раз в несколько кадров)
PooledGlobals.Maintain(); // Постепенно чистим ссылки на Enemy

Шаг 4. PooledDictionary

using var dict = PooledDictionary<string, int>.Create();
dict["health"] = 100;
dict.Add("mana", 50);
if (dict.TryGetValue("health", out int health))
{
    Console.WriteLine($"Health: {health}");
}
foreach (var kv in dict) // struct enumerator
{
    Console.WriteLine($"{kv.Key}: {kv.Value}");
}

Шаг 5. PooledHashSet

using var visited = PooledHashSet<int>.Create();
visited.Add(42);
visited.Add(100);
if (visited.Contains(42))
{
    Console.WriteLine("Found");
}

Шаг 6. LINQ-пайплайн без GC

using var numbers = PooledList<int>.Create(10000);
for (int i = 0; i < 10000; i++) numbers.Add(i);
// Вместо numbers.Where(x => x > 10).Select(x => x * 2).Take(256).ToList()
using var result = numbers
    .Where(x => x > 10)
    .Select(x => x * 2)
    .Take(256)
    .ToPooledList(capacityHint: 256);
// Ни одной аллокации на всём пайплайне

Шаг 7. PooledStringBuilder

using var sb = PooledStringBuilder.CreatePooled(128);
sb.Append("Player ");
sb.Append(123);
sb.Append(" HP");
string text = sb.ToString(); // Единственная аллокация — финальная строка

Шаг 8. PooledMemoryStream

using var ms = PooledMemoryStream.Create();
ms.Write(Encoding.UTF8.GetBytes("hello world"));
ms.Position = 0;
using var reader = new StreamReader(ms);
string content = reader.ReadToEnd();

Полный рабочий пример: игровой менеджер врагов

using GcFreeCollections;
public class EnemyManager
{
    private readonly PooledList<Enemy> _allEnemies;
    private readonly PooledList<Enemy> _nearbyEnemies;
    private readonly PooledHashSet<int> _deadEnemyIds;
    
    public EnemyManager(int maxEnemies)
    {
        _allEnemies = PooledList<Enemy>.Create(maxEnemies);
        _nearbyEnemies = PooledList<Enemy>.Create(64);
        _deadEnemyIds = PooledHashSet<int>.Create();
    }
    
    public void SpawnEnemy(Enemy enemy)
    {
        _allEnemies.Add(enemy);
    }
    
    public void UpdateNearbyEnemies(Vector3 playerPosition, float radius)
    {
        // Быстрая очистка (O(1))
        _nearbyEnemies.Clear();
        
        // Поиск ближайших врагов
        foreach (var enemy in _allEnemies)
        {
            if (Vector3.Distance(enemy.Position, playerPosition) < radius)
            {
                _nearbyEnemies.Add(enemy);
            }
        }
    }
    
    public void HandleDeaths()
    {
        // Используем временную коллекцию для хранения ID умерших
        using var toRemove = PooledList<int>.Create();
        
        for (int i = 0; i < _allEnemies.Count; i++)
        {
            if (_allEnemies[i].IsDead)
            {
                toRemove.Add(i);
                _deadEnemyIds.Add(_allEnemies[i].Id);
            }
        }
        
        // Удаляем мёртвых (с конца, чтобы не сбивать индексы)
        for (int i = toRemove.Count - 1; i >= 0; i--)
        {
            _allEnemies.RemoveAt(toRemove[i]);
        }
    }
    
    public void Update()
    {
        // Обновляем AI для всех врагов
        foreach (var enemy in _allEnemies)
        {
            enemy.Update();
        }
    }
    
    public void EndOfFrame()
    {
        // Постепенная очистка ссылок
        PooledGlobals.Maintain();
    }
    
    public void Dispose()
    {
        _allEnemies.Dispose();
        _nearbyEnemies.Dispose();
        _deadEnemyIds.Dispose();
    }
}

Производительность

Тестовый стенд: Intel Core i5-11400F, Windows 11, .NET 8, BenchmarkDotNet 0.15.8

List — Add + итерация

N

List<T> Mean

List<T> Alloc

PooledList<T> Mean

PooledList<T> Alloc

Speedup

Alloc gain

1000

1,665 ns

4,056 B

1,503 ns

56 B

1.11×

72×

10000

16,312 ns

40,056 B

14,319 ns

56 B

1.14×

715×

Dictionary — Add + TryGetValue

N

Dictionary Mean

Dictionary Alloc

PooledDictionary Mean

PooledDictionary Alloc

Speedup

Alloc gain

1000

21,293 ns

31,016 B

25,407 ns

88 B

0.84×

352×

10000

348,706 ns

283,042 B

310,081 ns

88 B

1.12×

3,216×

Note: Dictionary на 1000 элементах немного медленнее, но экономит 352× памяти.

HashSet — Add + Contains

N

HashSet Mean

HashSet Alloc

PooledHashSet Mean

PooledHashSet Alloc

Speedup

Alloc gain

1000

12,518 ns

58,664 B

6,377 ns

72 B

1.96×

815×

10000

177,439 ns

538,656 B

55,427 ns

72 B

3.20×

7,481×

LINQ — Where/Select/Take/ToList

N

LINQ Mean

LINQ Alloc

PooledQuery Mean

PooledQuery Alloc

Speedup

Alloc gain

1000

2,367 ns

6,496 B

1,845 ns

112 B

1.28×

58×

10000

12,522 ns

42,496 B

10,726 ns

112 B

1.17×

379×

StringBuilder / PooledStringBuilder

N

StringBuilder Mean

StringBuilder Alloc

PooledStringBuilder Mean

PooledStringBuilder Alloc

Speedup

Alloc gain

1000

37 ns

408 B

49 ns

80 B

0.75×

5.1×

Note: PooledStringBuilder пока медленнее для маленьких строк, но экономит память.

Где применяется

1. Игровая разработка (Unity / Godot / Monogame)

Проблема: GC-спайки вызывают frame hitches.
Решение: Pooled-коллекции в Update/FixedUpdate.

void Update()
{
    using var activeEnemies = PooledList<Enemy>.Create();
    GetActiveEnemies(activeEnemies);
    
    foreach (var enemy in activeEnemies)
    {
        enemy.UpdateAI();
    }
} // Автоматический возврат в пул

2. Real-time сервисы (FinTech / Trading)

Проблема: GC-паузы влияют на latency-sensitive операции.
Решение: Zero-аллокации в обработке заявок.

public void ProcessOrders(ReadOnlySpan<Order> orders)
{
    using var buyOrders = PooledList<Order>.Create(orders.Length);
    using var sellOrders = PooledList<Order>.Create(orders.Length);
    
    foreach (var order in orders)
    {
        if (order.Type == OrderType.Buy)
            buyOrders.Add(order);
        else
            sellOrders.Add(order);
    }
    
    MatchOrders(buyOrders.AsSpan(), sellOrders.AsSpan());
}

3. Сетевые серверы (Multiplayer games)

Проблема: Тысячи подключений, каждое создаёт мусор.
Решение: Переиспользуемые коллекции для каждого клиента.

public class GameRoom
{
    private readonly PooledList<Player> _players;
    private readonly PooledList<Message> _pendingMessages;
    
    public void Broadcast(Message msg)
    {
        for (int i = 0; i < _players.Count; i++)
        {
            _players[i].Send(msg);
        }
    }
}

4. VR/AR приложения

Проблема: Любой пропущенный кадр вызывает дискомфорт.
Решение: Стабильный frame pacing без GC.

5. Аудио/DSP обработка

Проблема: Аллокации в аудио-потоке вызывают щелчки.
Решение: Pooled-буферы в реальном времени.

6. Unity Editor инструменты

Проблема: Стандартные коллекции в Editor Tooling создают мусор.
Решение: GC-free коллекции для парсеров и генераторов.

Сравнение с конкурентами

Библиотека

GC-free

Pooling

Dictionary

HashSet

LINQ pipeline

Hot Clear

Quarantine

Актуальность

.NET Standart

Unity Collections

✅ (Native)

NativeHashMap

NativeHashSet

Roaring Bitmaps

❌ (только int)

C5

❌ (устарела)

Самописный пул

❌ (сложно)

❌ (сложно)

Зависит

GcFreeCollections

Когда GcFreeCollections выигрывает:

  • Нужны знакомые API (List/Dictionary/HashSet) без аллокаций

  • Проект на чистом .NET (не только Unity)

  • Нужен LINQ-пайплайн без мусора

  • Важны инструменты отладки (Leak tracking, HotPath guard)

  • Не хотите писать свои пулы и отлаживать их

Когда стоит выбрать альтернативу:

  • Unity Collections + Burst — если вы активно используете Job System и вам нужна максимальная производительность за счёт нативного кода

  • Стандартные коллекции — для небольших проектов без требований к GC

  • Roaring Bitmaps — если вам нужно компактное хранение множеств целых чисел

  • Самописный пул — если у вас очень специфические требования и есть время на отладку

Отладка и инструменты

Отслеживание утечек (DEBUG)

// В конце сессии или при выгрузке уровня
PooledGlobals.LogLeaks(); // Логирует все объекты, не возвращённые в пул
PooledGlobals.AssertClosedPool(); // Бросает исключение, если есть утечки

Защита от случайных аллокаций в hot path (DEBUG)

void CriticalUpdate()
{
    using var hot = HotPath.Enter(); // Любая аллокация внутри выбросит исключение
    
    // Если здесь случится new List<int>() или другая аллокация — получите исключение
    using var list = PooledList<int>.Create(); // OK
    list.Add(42); // OK
}

Leak tracking для сложных сценариев

В DEBUG режиме каждый объект, взятый из пула, отслеживается. При вызове LogLeaks() вы увидите, где был создан объект, который не вернули в пул.

Unity Integration

using UnityEngine;
using GcFreeCollections;
public class PooledGameManager : MonoBehaviour
{
    private PooledList<GameObject> _activeProjectiles;
    
    void Start()
    {
        _activeProjectiles = PooledList<GameObject>.Create(256);
    }
    
    void Update()
    {
        _activeProjectiles.Clear(); // O(1) быстрая очистка
        
        foreach (var proj in FindObjectsOfType<Projectile>())
        {
            _activeProjectiles.Add(proj.gameObject);
        }
    }
    
    void LateUpdate()
    {
        // В конце кадра — постепенная очистка
        PooledGlobals.Maintain();
    }
    
    void OnDestroy()
    {
        _activeProjectiles.Dispose();
        PooledGlobals.AssertClosedPool(); // Убедимся, что всё вернули
    }
}

Бесплатное тестирование — без ограничений. Коммерческое использование требует лицензии.

NuGet: https://www.nuget.org/packages/GcFreeCollections

GitHub (бенчмарки): https://github.com/likeslines-maker/GcFreeCollections

GcFreeCollections — это библиотека GC-free коллекций для .NET и Unity.

Она решает конкретную проблему: непредсказуемые паузы из-за сборки мусора в real-time системах.

В отличие от стандартных коллекций:

  • Не создаёт мусора после прогрева (экономия памяти до 7481×)

  • Clear() работает за O(1) вместо O(N)

  • Есть пулинг из коробки

В отличие от Unity Collections:

  • Работает на любом .NET (не только Unity)

  • Привычный API (List/Dictionary/HashSet)

  • Есть LINQ-пайплайн без аллокаций

В отличие от самописных пулов:

  • Уже отлажено и протестировано

  • Есть инструменты отладки (Leak tracking, HotPath guard)

  • Поддерживает сложные структуры (Dictionary, HashSet)

Если ваш проект страдает от GC-спайков — попробуйте GcFreeCollections. Стабильный frame rate и предсказуемая латентность того стоят.