@@ -450,6 +450,104 @@ Test names:
|
||||
- **ET 200SP CPU (1510SP / 1512SP)**: behaves as S7-1500 from `MB_SERVER`
|
||||
perspective. No known deltas [3].
|
||||
|
||||
## Performance (native S7comm driver)
|
||||
|
||||
This section covers the native S7comm driver (`ZB.MOM.WW.OtOpcUa.Driver.S7`),
|
||||
not the Modbus-on-S7 quirks above. Both share a CPU but use different ports,
|
||||
different libraries, and different optimization levers.
|
||||
|
||||
### Block-read coalescing
|
||||
|
||||
The S7 driver runs a coalescing planner before every read pass: same-area /
|
||||
same-DB tags are sorted by byte offset and merged into single
|
||||
`Plc.ReadBytesAsync` requests when the gap between them is small. Reading
|
||||
`DB1.DBW0`, `DB1.DBW2`, `DB1.DBW4` issues **one** 6-byte byte-range read
|
||||
covering offsets 0..6, sliced client-side instead of three multi-var items
|
||||
(let alone three individual `Plc.ReadAsync` round-trips). On a 50-tag
|
||||
contiguous workload this reduces wire traffic from 50 single reads (or 3
|
||||
multi-var batches at the 19-item PDU ceiling) to **1 byte-range PDU**.
|
||||
|
||||
#### Default 16-byte gap-merge threshold
|
||||
|
||||
The planner merges two adjacent ranges when the gap between them is at most
|
||||
16 bytes. The default reflects the cost arithmetic on a 240-byte default
|
||||
PDU: an S7 request frame is ~30 bytes and a per-item response header is
|
||||
~12 bytes, so over-fetching 16 bytes (which decode-time discards) is
|
||||
cheaper than paying for one extra PDU round-trip.
|
||||
|
||||
The math also holds for 480/960-byte PDUs but the relative cost flips —
|
||||
on a 960-byte PDU you can fit a much larger request and the over-fetch
|
||||
ceiling is less of a concern. Sites running the extended PDU on S7-1500
|
||||
can safely raise the threshold (see operator guidance below).
|
||||
|
||||
#### Opaque-size opt-out for STRING / array / structured-timestamp tags
|
||||
|
||||
Variable-width and header-prefixed tag types **never** participate in
|
||||
coalescing:
|
||||
|
||||
- **STRING / WSTRING** carry a 2-byte (or 4-byte) length header, and the
|
||||
per-tag width depends on the configured `StringLength`.
|
||||
- **CHAR / WCHAR** are routed through the dedicated `S7StringCodec` decode
|
||||
path, which expects an exact byte slice, not an offset into a larger
|
||||
buffer.
|
||||
- **DTL / DT / S5TIME / TIME / TOD / DATE-as-DateTime** route through
|
||||
`S7DateTimeCodec` for the same reason.
|
||||
- **Arrays** (`ElementCount > 1`) carry a per-tag width of `N × elementBytes`
|
||||
and would silently mis-decode if the slice landed mid-block.
|
||||
|
||||
Each opaque-size tag emits its own standalone `Plc.ReadBytesAsync` call.
|
||||
A STRING in the middle of a contiguous run of DBWs will split the
|
||||
neighbour reads into "before STRING" and "after STRING" merged ranges
|
||||
without straddling the STRING's bytes — verified by the
|
||||
`S7BlockCoalescingPlannerTests` unit suite.
|
||||
|
||||
#### Operator tuning: `BlockCoalescingGapBytes`
|
||||
|
||||
Surface knob in the driver options:
|
||||
|
||||
```jsonc
|
||||
{
|
||||
"Host": "10.0.0.50",
|
||||
"Port": 102,
|
||||
"CpuType": "S71500",
|
||||
"BlockCoalescingGapBytes": 16, // default
|
||||
// ...
|
||||
}
|
||||
```
|
||||
|
||||
Tuning guidance:
|
||||
|
||||
- **Raise the threshold (32-64 bytes)** when the PLC has chatty firmware
|
||||
(S7-1200 with default 240-byte PDU and many DBs scattered every few
|
||||
bytes). One fewer PDU round-trip beats over-fetching a kilobyte.
|
||||
- **Lower the threshold (4-8 bytes)** when DBs are sparsely populated
|
||||
with hot tags far apart — over-fetching dead bytes wastes the PDU
|
||||
envelope and the saved round-trip never materialises.
|
||||
- **Set to 0** to disable gap merging entirely (only literally adjacent
|
||||
ranges with `gap == 0` coalesce). Useful as a debugging knob: if a
|
||||
driver is misreading values you can flip the threshold to 0 to confirm
|
||||
the slice math isn't the culprit.
|
||||
- **Per-DB tuning isn't supported yet** — the knob is global per driver
|
||||
instance. If a site needs different policies for two DBs they live in
|
||||
different drivers (different `Host:Port` rows in the config DB).
|
||||
|
||||
#### Diagnostics counters
|
||||
|
||||
The driver surfaces three coalescing counters via `DriverHealth.Diagnostics`
|
||||
under the standard `<DriverType>.<Counter>` naming convention:
|
||||
|
||||
- `S7.TotalBlockReads` — number of `Plc.ReadBytesAsync` calls issued by
|
||||
the coalesced path. A fully-coalesced contiguous workload bumps this
|
||||
by 1 per `ReadAsync`.
|
||||
- `S7.TotalMultiVarBatches` — `Plc.ReadMultipleVarsAsync` batches issued
|
||||
for residual singletons that didn't merge. With perfect coalescing this
|
||||
stays at 0.
|
||||
- `S7.TotalSingleReads` — per-tag fallbacks (strings, dates, arrays,
|
||||
64-bit ints, anything that bypasses both the coalescer and the packer).
|
||||
|
||||
Observe via the `driver-diagnostics` RPC (`/api/v2/drivers/{id}/diagnostics`)
|
||||
or the Admin UI's per-driver dashboard.
|
||||
|
||||
## References
|
||||
|
||||
1. Siemens Industry Online Support, *Modbus/TCP Communication between SIMATIC S7-1500 / S7-1200 and Modbus/TCP Controllers with Instructions `MB_CLIENT` and `MB_SERVER`*, Entry ID 102020340, V6 (Feb 2021). https://cache.industry.siemens.com/dl/files/340/102020340/att_118119/v6/net_modbus_tcp_s7-1500_s7-1200_en.pdf
|
||||
|
||||
Reference in New Issue
Block a user