Skip to content

Add string-keyed fast path to NS.apply avoiding URI.create per element - #1712

Merged
olabusayoT merged 1 commit into
apache:mainfrom
olabusayoT:daf-3092-ns-apply-string-fastpath
Aug 18, 2026
Merged

Add string-keyed fast path to NS.apply avoiding URI.create per element#1712
olabusayoT merged 1 commit into
apache:mainfrom
olabusayoT:daf-3092-ns-apply-string-fastpath

Conversation

@olabusayoT

@olabusayoT olabusayoT commented Jul 31, 2026

Copy link
Copy Markdown
Contributor

NextElementResolver's OnlyOnePossibilityForNextElement and SeveralPossibilitiesForNextElement both call NS(namespace) on every element resolution when the infoset source has namespaces. NS.apply(String) previously ran URI.create plus a ReentrantReadWriteLock-guarded WeakHashMap lookup on every call, even for a namespace string seen many times before. A ConcurrentHashMap keyed by the raw string now short-circuits repeats before they ever reach URI.create or the lock.

DAFFODIL-3092

@olabusayoT
olabusayoT force-pushed the daf-3092-ns-apply-string-fastpath branch from ad5839b to 8070521 Compare July 31, 2026 16:32

@jadams-tresys jadams-tresys left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

+1

Changes look reasonable. Have you verified any performance gains? Not sure why this didn't show up in my profiling.

@olabusayoT

olabusayoT commented Aug 7, 2026

Copy link
Copy Markdown
Contributor Author

+1

Changes look reasonable. Have you verified any performance gains? Not sure why this didn't show up in my profiling.

Yes I did! See results below for daffodil performance unparse. I used a delimited schema with 20 plain field, 1 valuenlength OVC, interleaved with another OVC element.

+--------------+----------------+-------------------+--------------------+-----------------------+-----------------+--------------------+
|     Run      |  main latency  |  main throughput  |  fastpath latency  |  fastpath throughput  |  latency change |  throughput change |
+--------------+----------------+-------------------+--------------------+-----------------------+-----------------+--------------------+
| N=20         | 394.5ms        | 2.53files/sec     | 294.7ms            | 3.39files/sec         | -25.30%         | 33.70%             |
| N=20(repeat) | 389.9ms        | 2.56files/sec     | 297.1ms            | 3.36files/sec         | -23.80%         | 31.20%             |
| N=50         | 367.9ms        | 2.72files/sec     | 261.1ms            | 3.83files/sec         | -29.10%         | 40.90%             |
+--------------+----------------+-------------------+--------------------+-----------------------+-----------------+--------------------+

* bounded in practice by the number of distinct namespace URI strings an
* application actually uses, which is small and fixed per schema.
*/
private val stringToNS = new java.util.concurrent.ConcurrentHashMap[String, NS]()

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is it possible to change our NS object to be a UniquenessCache[String,NS] so it keys off of Strings instead of URI's, and then we avoid the double caches?

We can still keep the apply(uri: URI) method for convenience, but it can just call apply(uri.toString). I believe URI.toString either returns the string it was created with, or it calculates it once and then caches that for future calls, so the extra toString shouldn't add much overhead.

* hit there requires first parsing the string via URI.create and then taking
* a lock - fine for schema-compile-time use, but real per-call overhead on a
* per-element hot path. This map memoizes by the raw string itself, so a
* previously-seen string never reaches URI.create or the lock at all.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do you know if the overhead is primarily from the locking used by the UniquenessCache or from URI.create? The benefit of the UniquenessCache over the ConcurrentHashMap is it will evict entries so we don't have an ever growing cache.

That said, I'm not sure there's really a huge concern that the cached namespaces will take up a ton of memory though, mainly because in practice there just won't be that many different namespaces used at the same time. And thinking about it more, having the ConcurrentHashMap here kind of defeats the purpose of the UniquenessCache since it's size will grow unbounded.

So I think we should either have just the UniquenessCache[String, NS] or just the ConcurrentHashMap[String, NS], and switch to the latter if we are seeing worse performance caused by the UniquenessCache locking (especially with parallel tests where the locking and multiple parallel readers/writers could become more of an issue).

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There was no discernable difference between UniquenessCache and ConcurrentHashMap implementation so I went with the UniquenessCache option. Looks like the URI.create was the overhead.

@stevedlawrence

Copy link
Copy Markdown
Member

See results below for daffodil performance unparse

Great improvements! Is N the number of threads. Considering N=50 has a better improvement than N=20, that makes me thing the locking is a significant part of the overhead and so dropping UniquenessCache in favor of ConcurrentHashMap might be the best approach.

@olabusayoT

Copy link
Copy Markdown
Contributor Author

See results below for daffodil performance unparse

Great improvements! Is N the number of threads. Considering N=50 has a better improvement than N=20, that makes me thing the locking is a significant part of the overhead and so dropping UniquenessCache in favor of ConcurrentHashMap might be the best approach.

No the N was number of files, I retested with different threads though and these were the results

  +--------+------+--------------+--------------+----------+----------------+----------------+----------+                                                                                                                           
  | N      | T    | main lat(ms) | fastp lat(ms) |   lat D% |  main thr(f/s) | fastp thr(f/s) |   thr D% |                                                                                                                          
  +--------+------+--------------+--------------+----------+----------------+----------------+----------+                                                                                                                           
  | 20     | 1    |        125.0 |        110.2 |   -11.9% |           7.96 |           9.04 |   +13.5% |                                                                                                                           
  | 20     | 2    |        210.3 |        190.8 |    -9.3% |           9.68 |          10.74 |   +10.9% |                                                                                                                           
  | 20     | 4    |        439.5 |        408.3 |    -7.1% |           9.28 |          10.06 |    +8.3% |                                                                                                                           
  | 20     | 8    |        880.9 |        865.5 |    -1.7% |           8.73 |           9.06 |    +3.7% |                                                                                                                           
  | 20     | 32   |       1365.9 |       1215.4 |   -11.0% |           8.17 |           9.18 |   +12.3% |                                                                                                                           
  +--------+------+--------------+--------------+----------+----------------+----------------+----------+                                                                                                                           
  | 50     | 1    |         86.3 |         74.9 |   -13.2% |          11.60 |          13.39 |   +15.4% |                                                                                                                           
  | 50     | 2    |        120.4 |        105.4 |   -12.5% |          16.90 |          19.22 |   +13.7% |                                                                                                                           
  | 50     | 4    |        214.7 |        184.7 |   -14.0% |          19.34 |          21.99 |   +13.7% |                                                                                                                           
  | 50     | 8    |        437.2 |        423.4 |    -3.1% |          19.16 |          19.69 |    +2.8% |                                                                                                                           
  | 50     | 32   |        757.5 |        750.2 |    -1.0% |          16.57 |          16.44 |    -0.8% |                                                                                                                           
  +--------+------+--------------+--------------+----------+----------------+----------------+----------+                                                                                                                           
  | 300    | 1    |         56.0 |         46.5 |   -16.9% |          17.87 |          21.49 |   +20.3% |                                                                                                                           
  | 300    | 2    |         66.1 |         56.3 |   -14.8% |          30.50 |          35.73 |   +17.1% |                                                                                                                           
  | 300    | 4    |         95.4 |         84.0 |   -11.9% |          42.78 |          48.73 |   +13.9% |                                                                                                                           
  | 300    | 8    |        191.2 |        176.5 |    -7.7% |          43.10 |          45.26 |    +5.0% |                                                                                                                           
  | 300    | 32   |        309.0 |        265.4 |   -14.1% |          38.83 |          45.64 |   +17.5% |                                                                                                                           
  +--------+------+--------------+--------------+----------+----------------+----------------+----------+   

@stevedlawrence stevedlawrence left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

+1 to latest changes

NextElementResolver's OnlyOnePossibilityForNextElement and
SeveralPossibilitiesForNextElement both call NS(namespace) on every
element resolution when the infoset source has namespaces. NS.apply(String)
previously ran URI.create plus a ReentrantReadWriteLock-guarded WeakHashMap
lookup on every call, even for a namespace string seen many times before.

NS now extends UniquenessCache[String, NS], keyed directly by the
namespace string instead of URI. A cache hit never touches URI.create;
URI.create only runs in valueFromKey on a genuine cache miss. apply(URI)
delegates to apply(uri.toString).

DAFFODIL-3092
@olabusayoT
olabusayoT force-pushed the daf-3092-ns-apply-string-fastpath branch from 2c7a1ea to 6d39bd9 Compare August 18, 2026 20:25
@olabusayoT
olabusayoT merged commit d902f94 into apache:main Aug 18, 2026
10 of 11 checks passed
@olabusayoT
olabusayoT deleted the daf-3092-ns-apply-string-fastpath branch August 18, 2026 20:44
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants