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)

Reply via email to