Claus Ibsen created CAMEL-26898:
-----------------------------------
Summary: camel-core - Routing engine hot-path performance
improvements, part 2
Key: CAMEL-26898
URL: https://issues.apache.org/jira/browse/CAMEL-26898
Project: Camel
Issue Type: Improvement
Components: camel-core
Reporter: Claus Ibsen
Fix For: 4.25.0
Follow-up of CAMEL-25132, which covered the measured Tier 1 items: copying only
the IN message on redelivery, the TO_ENDPOINT field and lazily created internal
properties map, the lazy message traits map, the text-block URI fast path, the
IOConverter, the type-converter lookup cache and the JMX counter quick wins.
This ticket carries over what is left. The full analysis, with the numbers and
the method, is in the description of CAMEL-25132. The numbers were measured on
927b570d022d (4.23.0-SNAPSHOT), before those fixes, so re-verify each item
against current main before working on it. Benchmark multi-threaded items on
Linux, not macOS ({{System.nanoTime()}} does not scale across threads on macOS).
Split an item into a sub-task when work starts.
h2. Design work
* *JMX counters, striped cells* (CAMEL-25132 #1 b): each counter keeps its
fields in a padded cell picked by thread probe; JMX reads aggregate. Reusing
the previous node's end time for one {{nanoTime}} per node was declined in
CAMEL-26475: do not propose it again unless the striped counters have landed
and JMH shows the clock as the main cost.
* *RoutesOnly as the default statistics level* for Main and Spring Boot (#1 d).
A default/doc discussion: camel-management on the classpath silently switches
processor-level statistics on.
* *Redelivery follow-ups* (#2): {{RedeliveryTask}} implements {{AsyncCallback}}
(removes a capturing lambda per delivery), and builds its full redelivery state
only on the first failure.
* *Compact internal-properties map* (#3): about 260 B less per split or
multicast item. A naive bitmap version cost about 10% CPU in split; needs a
JMH-driven design.
* *Split/multicast sequential mode*: about 5 lock/unlock pairs and 5 atomics
per item (AQS release is 21-25% of CPU in single-threaded split). A lock-free
sequential fast path, or skip the processor-wide aggregate lock for built-in
strategies.
* *Per-node plumbing*: three reactive-queue hops per node (SimpleTask,
AsyncAfterTask, PipelineTask). Running the SimpleTask directly gave 5-8% on 10
nodes, but touches reactive ordering.
h2. Smaller items
* *Split per-item allocations*: a {{DefaultUnitOfWork}} with an eager
{{ReentrantLock}} per sub-exchange;
{{pairs.stream().filter(Objects::nonNull).toList()}} ({{MulticastProcessor}});
a {{PriorityQueue}} sized to N in sequential mode; a per-exchange
{{UseOriginalAggregationStrategy}}.
* *Non-streaming split* builds all sub-exchanges up front; create the copies
lazily (changes when {{onPrepare}} runs).
* *DefaultProducerCache miss path*: {{ServicePool.acquire}} locks and allocates
even when the entry exists (try {{get}} first); {{lastUsedProducer}} ping-pongs
between threads.
* *Enrich*: re-verify the "three copies on InOut" finding.
{{ExchangeHelper.doCopyResults}} already returns early when target == source,
so the default strategy's final copy may be cheap now.
* *Seda producer* takes the queue-reference lock twice per message
({{getCount}}, {{hasConsumers}}).
* *Simple language*: OGNL builds a {{BeanExpression}} and copies the exchange
per evaluation; the {{regex}}, {{in}}, {{is}}, {{range}} operators rebuild
their right-hand side each time; check {{$\{date:...:pattern\}}}.
* *Type converter*: {{getBody(type)}} with a null body retries converting the
Message itself; a successful conversion allocates a {{TypeConvertible}} key and
looks up twice.
* *CaseInsensitiveMap*: holes are never compacted under repeated put/remove of
the same key; the copy constructor re-hashes every key; an empty map is 344 B.
* *ExchangeVariableRepository* is a full lifecycle service with a lock, created
on the first {{setVariable}} and on every copy of an exchange with variables.
* *AbstractExchange.removeProperties* loops all internal property keys.
* *Engine core*: {{NodeHistoryAdvice}} setter calls per node (one precomputed
holder instead); {{CamelInternalProcessor}} and {{SendProcessor}} fire the
deprecated async-started event without the {{isEventNotificationApplicable()}}
guard; {{DefaultUnitOfWork}} takes its lock 4+ times per route pass;
{{RouteInflightRepositoryAdvice}} looks up the route counter twice per pass;
the TracingAdvice/BacklogTracer standby notifier in dev mode.
h2. Failure path (only matters when failures are frequent)
* {{ExceptionPolicy.createRedeliveryPolicy}} clones and re-parses about 24
options per failed attempt.
* {{DefaultExceptionPolicyStrategy}} allocates a TreeMap, two LinkedHashSets
and an ArrayList per exception.
* "Failed delivery" log strings are built before deciding whether to log; a DLC
regex-sanitizes {{deadLetterUri}} per call.
* {{RedeliveryPolicy}} uses a global lock and a shared {{Random}};
{{delayPattern}} is re-parsed per redelivery.
h2. Possible correctness bug (needs a test)
* {{SendProcessor}} / {{DefaultProducerCache}} emit {{ExchangeSent}} only if
some notifier accepted {{ExchangeSending}}. {{SqlTraceDevConsole}}'s notifier
ignores Sending but wants Sent.
_Claude Code on behalf of davsclaus_
--
This message was sent by Atlassian Jira
(v10.3.26#10030026)