Обновить

Почему ваш Parallel.ForEach впустую сжигает CPU — ускоряем обработку данных до 600+ раз

Время на прочтение7 мин
Охват и читатели5.6K
Всего голосов 3: ↑1 и ↓2-1
Комментарии14

Комментарии 14

???

Parallel.ForEach(items.Distinct(), item =>{ Process(item);});

Это убирает только полностью одинаковые элементы внутри одного прохода и не решает задачи Last-Write-Wins по ключу, кэширования результатов между вызовами, адаптивного выбора стратегии выполнения и повторного использования уже вычисленных значений. Поэтому для простых коллекций Distinct() действительно может быть достаточен, а Principium.Parallel ориентирован на сценарии, где дубликаты определяются по ключу, данные приходят батчами, а стоимость вычислений достаточно высока, чтобы выигрыш от коалесинга и кэша был существенным.

Distinct() решает другую задачу — удаление одинаковых элементов в рамках одного перечисления. Principium.Parallel работает с произвольными объектами через keySelector, поддерживает Last-Write-Wins по ключу, кэширование между вызовами и адаптивно переключается между параллельным выполнением, коалесингом и кэшем в зависимости от уровня дубликатов. Поэтому это не альтернатива Distinct(), а решение для более широкого класса задач.

То есть Вам надо различать, являются ли данные последними? Тогда это не удаление дубликатов. То есть совсем не та проблема, которая заявлена.

Parallel.ForEach(items.GroupBy(x => x.Criterion).Select(x => x.Last()), Process);

Это проприетарная ллм-либа, рождённая за пару дней, без адекватной архитектуры, надзора и ревью ллм-ного кода.

Там даже в простейшей документации косяки
netstandard - очевидно не существует
netstandard - очевидно не существует
<Project Sdk="Microsoft.NET.Sdk">

  <PropertyGroup>
    <OutputType>Exe</OutputType>
    <TargetFramework>net6.0</TargetFramework>
    <RootNamespace>Test_lib_neuroslop</RootNamespace>
    <ImplicitUsings>enable</ImplicitUsings>
    <Nullable>enable</Nullable>
  </PropertyGroup>

</Project>
error: NU1202: Package Principium.Parallel 1.0.1 is not compatible with net6.0 (.NETCoreApp,Version=v6.0). Package Principium.Parallel 1.0.1 supports: net8.0 (.NETCoreApp,Version=v8.0)

В либе обвязка под лицензию и чуток простеньких абстракций, которые зачем-то оборачивают самый стандартный дотнет.

Фактически там просто обёртка над ConcurrentDictionary и Dictionary с смешными(с точки зрения производительности) штуками под капотом.

Но проблема возникает тогда, когда внутри коллекции появляются дубликаты.

Garbage in - garbage out.

Не всегда. Во многих реальных системах дубликаты — это не ошибка данных, а нормальная часть бизнес-процесса. Например, события телеметрии, обновления состояния сущностей, сообщения из очередей, ETL-пайплайны, CDC-потоки из БД, пересчёт эмбеддингов или агрегация логов. Там один и тот же ключ может встречаться десятки или сотни раз в одном батче, и задача как раз состоит в том, чтобы эффективно обработать такие данные. Principium.Parallel не исправляет "плохие данные", а оптимизирует типичный сценарий, когда повторения ключей являются ожидаемым свойством входного потока.

И что предлагаете? Делать фильтрованную копию многогиговой базы, чтобы по ней прошвырнуться?

Нет, речь не про создание копии или предварительную “чистку” многогиговой базы. В большинстве таких сценариев данные уже приходят батчами или потоками, и повторения неизбежны из-за природы доставки (ретраи, at-least-once семантика, повторные события, обновления состояния). Поэтому задача не в том, чтобы заранее построить идеальный набор данных, а в том, чтобы при обработке не выполнять одинаковую тяжёлую работу повторно для одного и того же ключа. Principium.Parallel работает именно на этом уровне — он коалесит вычисления, кэширует результат и переиспользует его внутри и между проходами, не требуя предварительного копирования или фильтрации исходного массива.

П.С. вроде не на свой вопрос ответ написал, но всё-равно по сути библиотеки, пусть остаётся

В очереди постоянно встречаются повторяющиеся события:

...

В большинстве случаев интересует только последнее состояние.

Ну так сделайте нормальную очередь.

Очередь отвечает за доставку событий, а не за их семантическое слияние. Повторные события возникают из-за ретраев, at-least-once доставки, ребалансировки партиций, повторной публикации продьюсером, сетевых таймаутов и восстановления после сбоев — и это считается нормальной моделью работы Kafka/RabbitMQ/Azure Service Bus. Поэтому “последнее состояние” — это уже задача потребителя, а не очереди. Именно там и появляется необходимость в LWW-логике, коалесинге и кэшировании, которые и закрывает Principium.Parallel: он не заменяет очередь, а обрабатывает неизбежную реальность дубликатов в потоках данных.

Когда у Вас в руках молоток, всё вокруг кажется гвоздями.

но когда вокруг одни гвозди, то что в руках - не кажется молотком:)

Ох, блин, прочитал заголовок, думал что-то новое и интересное, а тут нейрослоп пишет про редкий сценарий где кэш поможет. Зачем в заголовке было писать «ваш Parallel.ForEach впустую сжигает CPU»? Мой ниче не сжигает, я не идиот и там где очевидно что кэш поможет - использую его.

Зарегистрируйтесь на Хабре, чтобы оставить комментарий

Публикации