mirror of
https://github.com/OPCFoundation/UA-.NETStandard.git
synced 2026-10-21 00:56:28 +08:00
d7c2ed6caf
# Description Every V2 monitored item (`Opc.Ua.Client.Subscriptions`) cost about 3.8 KB of managed heap on a 24-core machine (2.8 KB on 1 core). Most of that was per-item infrastructure whose size grows with the processor count. With 100k items, a client limited to 256 MB (container-like) ran out of memory during subscription setup. - **`OptionsMonitor<T>`**: `OnChange` listeners are kept in a `Listener[]` that is replaced as a whole on each add or remove (`Interlocked.CompareExchange`), instead of a `ConcurrentDictionary`. That dictionary's default concurrency level allocated one lock object per processor for every monitored item (~1.4 KB on 24 cores). Notification, dispose and double-dispose behave as before; listeners are now called in registration order. - **`MonitoredItem`**: pending changes are kept in a `Queue<Change>` guarded by the existing `m_pendingChangesLock` instead of a per-item `ConcurrentQueue<Change>` (~810 B, 32-slot segment). The queue is created on enqueue and dropped once the last change completes, so idle items hold none. `HasPendingChanges`, `TryGetPendingChange` and `CompleteChange` now take the lock. `TryRequeue` used to enqueue without the lock and now enqueues under it. - **`DefaultSubscription`** shares one `ILogger` across its monitored items instead of creating one per item. ## Measurements These numbers come from the `ServerLoadHarness` client GC line (harness changes are in #4844): `run --scenario sub --sessions 25 --nodes 100 --pub 500 --write-interval 100 --duration 10 --warmup 6`. All figures are client gen2 MB. | Config | Items | Before | After | |---|---|---|---| | 24 cores, `--subs 20` | 50k | 190.0 / 188.7 | 62.9 / 64.0 | | `--client-cores 2 --client-memory 256`, `--subs 20` | 50k | 142.6 | 54.6 | | `--client-cores 2 --client-memory 256`, `--subs 40` | 100k | out of memory | 122.6 (limit 192) | That saves about 2.6 KB per item on 24 cores and about 1.8 KB on 2 cores. The whole client heap is now about 1.3–1.4 KB per item. ## Related Issues - None ## Checklist - [x] I have signed the [CLA](https://opcfoundation.org/license/cla/ContributorLicenseAgreementv1.0.pdf) and read the [CONTRIBUTING](https://github.com/OPCFoundation/UA-.NETStandard/blob/master/CONTRIBUTING.md) doc. - [x] I have added tests that prove my fix is effective or that my feature works and increased code coverage. (`OptionsMonitorTests`: order, removal, duplicate callbacks, concurrent add/remove) - [x] I have added all necessary documentation. - [x] I have verified that my changes do not introduce (new) build or analyzer warnings. (`Opc.Ua.Client` Release build on all targets, including net48: 0 warnings) - [ ] I ran **all** tests locally using the **UA.slnx** solution against at least .net **framework** and .net **10**, and all passed. (Only `Opc.Ua.Client.Tests` on net10.0 was run: 3231 passed, 0 failed, 2 skipped.) - [ ] I fixed **all** failing and flaky tests in the CI pipelines and **all** CodeQL warnings. - [ ] I have addressed **all** PR feedback received. 🤖 Generated with [Claude Code](https://claude.com/claude-code) --------- Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>