· Engineering · 71 min read
Five Doors Into One Siemens Controller: What Each One Costs You
How you ask a Siemens PLC for data matters more than which protocol you use. Reading the same hundred values off the same controller, a client that groups its requests is a hundred times faster on Modbus and five to fifteen times faster on S7Comm. That is larger than any difference between the protocols themselves. A measured look at what protocol and driver choice costs a PLC, a network and your budget.

How you ask a Siemens PLC for data matters more than which protocol you use. Reading the same hundred values off the same controller, a client that groups its requests is a hundred times faster on Modbus and five to fifteen times faster on S7Comm. That is not a tuning detail. It is larger than any difference between the protocols themselves.
- 1. The pitch, and the bill
- 2. How I measured it
- 3. A read costs one request, not one byte
- 4. The curve
- 5. Where your values sit
- 6. Six ways in, side by side
- 7. The controller matters more than its age
- 8. What the traffic costs the controller
- 9. Security is cheap to use and expensive to start
- 10. What actually stopped, and why
- 11. The second bill
- 12. So what should you do?
- 13. Choosing per device, without rewriting per device
- 14. What I got wrong
- 15. What this does not tell you
- 16. The point
- 17. Version history
1. The pitch, and the bill
Walk any trade fair and you hear the same answer. Standardise on OPC UA and talking to machines becomes a solved problem.
Most of that is true. OPC UA really is a standard, really is vendor-neutral, and really does carry an information model the older protocols do not have. For many plants it is the right choice.
What the pitch leaves out is the bill, and it arrives in one of two ways.
The first is a rollout. A brownfield line that has run for fifteen years is told to standardise. Nobody re-specifies the controllers, because the controllers work fine. Then the network carries several times the traffic it used to, and the CPUs spend part of every cycle on communication they were never sized for.
The second is slower, and much harder to see coming. An Industry 4.0 project starts small and works well. So it grows. Somebody adds a dashboard. A quality system wants the same values, faster. Maintenance wants trends. Someone wants all of it archived. Every request is reasonable on its own, and every one gets granted, because the last one was fine.
Nobody ever decides to overload a controller, or a network. You arrive there one good idea at a time, until the PLC can no longer serve the information and run the machine at the same time.
Both roads end in the same place. The line stops running properly. Not dramatically. It just gets less reliable, in ways that take a long time to trace back to a connectivity decision.
So this article asks a narrow question. Not "which protocol is best", but what does it cost to read data out of a controller you already own — and how much of that cost is the protocol, how much is the controller, and how much is your own client.
Other vendors get the same treatment in their own articles, starting with Beckhoff’s TwinCAT controllers. A third article crosses vendor lines on purpose. It runs the same jobs on the controllers of seven vendors.
2. How I measured it
Five Siemens controllers. Six ways in. One number that changes: how many values I ask for at once.
The curve below and the connect times in the security section come from September, from an earlier build of the driver. The six-way table, the controller load, OPC UA over separate variables and the subscriptions were measured in October, from the driver exactly as it ships.
2.1. One run
A run is one controller, one protocol, one size. It opens a connection, then reads all the values together, twenty times over, with the order shuffled each round.
Every figure in this article is one of those reads. Each row reports the average of its twenty, with the two fastest and two slowest dropped, so one slow round cannot move it.
The connection is timed separately and is not in the result. Nothing is ever written: this is a read benchmark, because reading is what a shopfloor does almost all of the time.
The sizes are 10, 25, 50, 100, 200, 500, 1000, 2000 and 3000 values. Where a controller or a protocol cannot manage a size, that row is left out rather than quietly shrunk, so a row always says what it really read.
2.2. The values
Every value is one element of the same array, and I always read elements 0 to N-1. They sit next to each other in memory.
That is deliberate, and it is the best case for one of the things being measured. More on that below, because it matters.
They are also all the same simple type, and that is deliberate too.
An earlier round of this work varied the opposite thing. Not how many values, but what kind. Nine types that every controller and every protocol here can handle. Then twenty-five that most can, which already leaves out Modbus and the oldest controller. Then fifty-six involving structure members, nested structures and single cells of a matrix, which only the protocols that address by name can express at all.
That answered a different and equally real question — what each protocol can reach — and those findings still stand. S7Comm cannot name a structure member, whether you ask it for ten of them or three thousand. But it makes a poor curve, because each set runs on a different subset of the bench, so the rows are not comparable as the set changes.
So this curve holds the type constant at something every device supports, and varies only the count. Capability is a separate axis, and mixing the two is how you end up comparing three different machines and calling it a trend.
2.3. Three things that make the numbers trustworthy
A control that should not move. Every measurement has a twin that reads values spaced far enough apart that no client can merge them. If the gains came from anything other than merging, the twins would move too. They agree within 5% on every controller.
The connection is timed separately. It used to be inside the result, which quietly flattened every small measurement, because both halves of a comparison paid the same fixed cost. On a nine-value run from an earlier sweep, Modbus read 0.9x that way and 6.2x with the connection taken out — the same measurement, and one of those numbers says merging does not work.
A discarded warm-up run. The first merged run in a process pays to compile the merging code: 1428 ms cold, 330 ms warm, while its twin moved 2%. Without that warm-up the benchmark reported a 2.5x slowdown that does not exist. I published that slowdown once. It was my own JVM.
3. A read costs one request, not one byte
Start here, because everything else follows from it.
Over classic S7Comm I read one contiguous block of memory and varied only how many bytes it held:
| Bytes read | S7-1200 G2 | Old S7-1212C |
|---|---|---|
1 | 2.07 ms | 12.62 ms |
10 | 1.99 ms | 12.60 ms |
100 | 1.97 ms | 12.62 ms |
200 | 2.06 ms | 12.95 ms |
Flat. Two hundred times the data for the same time.
The tables further down say the same thing from the other direction. On one controller, reading a hundred values takes 1.9 ms in one request and 318 ms in a hundred requests — for ten times fewer bytes in the slow case.
A request to a PLC is almost all fixed cost. The controller has to notice the request, schedule it around the program it is running, build an answer and send it. The data itself is nearly free.
So the way to make a client faster is not to ask for less. It is to ask fewer times.
4. The curve
Which raises the obvious question: how much fewer, and when does it stop helping?
I ran the same values at nine sizes, with the client merging requests and without, on four controllers. Every figure below is one batched read against one batched read — the same hundred or thousand values, asked for once each way.
4.1. On Modbus it is enormous
Modbus has no way to ask for several things at once. One request is one contiguous range of registers and nothing else. So a client that does not merge sends one request per value, and a client that does packs up to 125 registers into each.
| Values | Old S7-1212C | S7-1212C G2 | S7-1511 | S7-1516 | Requests |
|---|---|---|---|---|---|
10 | 14.1x | 10.5x | 10.3x | 10.3x | 10 → 1 |
25 | 25.6x | 25.5x | 25.6x | 25.8x | 25 → 1 |
50 | 59.7x | 52.8x | 50.5x | 51.8x | 50 → 1 |
100 | 97.4x | 127.4x | 107.8x | 103.3x | 100 → 1 |
200 | 99.5x | 109.8x | 106.7x | 103.3x | 200 → 2 |
500 | — | 126.5x | 122.6x | 130.4x | 500 → 4 |
1000 | — | 125.5x | 118.9x | — | 1000 → 8 |
The last column is the whole explanation. Up to a hundred values the merged version is one request, so the ratio is simply how many requests you avoided. After that the 125-register limit starts to bite and the merged version needs several too, so the ratio stops climbing.
The gaps are rows where the unmerged read never finished. More on that below.
4.2. On S7Comm it is smaller, and it depends on the controller
S7Comm can carry many items in one request. So an unmerged client still gets its values without one request per value — but each item costs a twelve-byte header inside the request, so the request fills up and has to be split.
How many splits depends on the negotiated request size, and that is what separates the controllers.
| Values | Old S7-1212C (240 B) | S7-1212C G2 (960 B) | S7-1511 (960 B) | S7-1516 (960 B) | Requests, 240 B | Requests, 960 B |
|---|---|---|---|---|---|---|
10 | 1.9x | 1.5x | 1.7x | 1.8x | 1 → 1 | 1 → 1 |
25 | 4.3x | 1.9x | 3.0x | 2.5x | 1 → 2 | 1 → 1 |
50 | 7.5x | 2.9x | 4.4x | 3.8x | 1 → 3 | 1 → 1 |
100 | 15.2x | 5.3x | 10.0x | 6.6x | 1 → 6 | 1 → 2 |
200 | 14.9x | 13.1x | 18.7x | 12.3x | 2 → 11 | 1 → 3 |
500 | 14.5x | 18.1x | 19.7x | 13.8x | 5 → 27 | 2 → 7 |
1000 | 14.0x | 20.8x | 25.3x | 16.2x | 10 → 53 | 3 → 13 |
2000 | 13.7x | 23.4x | 28.6x | 18.1x | 20 → 106 | 5 → 26 |
3000 | 14.2x | 23.4x | 29.9x | 20.0x | 29 → 158 | 7 → 38 |
Two request columns, because S7Comm needs two. Modbus sends one request per value on every controller, so one column describes them all. S7Comm’s count depends on how much it can fit in a request, and the negotiated size is not the same on every device: 960 bytes on three of these, 240 on the old S7-1212C. Same protocol, same values, different arithmetic.
Which is what the ratio columns are really showing. At a hundred values the small-PDU controller goes from six requests to one and gains 15.2x; the large-PDU ones go from two to one and gain 5.3x to 10x. The worse your request size, the more merging is worth to you.
At three thousand values an unmerged read takes 38 requests on the 960-byte controllers and 158 on the 240-byte one. Merged, it takes 7 and 29.
4.3. What plateaus is the improvement, not the protocol
Read those two tables carefully, because they are easy to misread — I misread them myself first.
On Modbus the ratio stops climbing at about a hundred values. On S7Comm it keeps climbing much further: the small-request controller settles around fifteen from a hundred values on, while the larger ones are still improving at three thousand.
But a plateau in the ratio is not a reason to stop batching. If you read ten thousand values, Modbus still gives you its hundredfold and S7Comm still gives you its twenty or thirty. The saving does not go away. It simply stops getting better.
So there is no need to chop a large read into chunks of a hundred. It would not hurt much, and it would not help either. Ask for what you need in as few requests as your client will build, and the curve says you will be at or near the best available.
The two tables also say something you can act on without measuring anything:
How much grouping is worth to you depends on whether your protocol can already carry several items in one request. If it cannot, a client that does not group is throwing away two orders of magnitude.
5. Where your values sit
Merging works by reading a span of memory in one go. If your values are neighbors, that span is nearly all data you asked for.
If they are scattered across a data block with big gaps between them, the client is reading bytes nobody wants in order to save round trips. At some point that trade stops paying, and if they are far enough apart it buys nothing at all.
Every number above reads one contiguous array, which is the best case for merging. On your plant it will depend on your layout.
For OPC UA the same array is the worst case. Its server treats every element as a separate node read through an index range, and on the G2 it answered separate variables about eight times as fast as array elements. So every OPC UA row in this article up to the next section is a lower bound. The section after the six-way table measures OPC UA the way most projects are written.
Modbus on a SIMATIC is unusually well placed here. You cannot speak Modbus to an S7 at all without building a data block for it and mapping your values into register ranges by hand, so they end up beside each other because the protocol made you put them there. The layout that makes merging work is the layout you were forced into anyway.
6. Six ways in, side by side
Now the protocols against each other. One controller, one read carrying 80 values, every protocol in the same run, with the driver as it ships:
| How you ask | One read | Requests | Bytes |
|---|---|---|---|
S7Comm, merged | 3.6 ms | 1 | 216 B |
Modbus, merged | 3.7 ms | 1 | 181 B |
S7CommPlus, merged 🔒* | 6.2 ms | 1 | 340 B |
S7Comm | 11 ms | 2 | 1,520 B |
S7CommPlus 🔒* | 19 ms | 6 | 2,126 B |
OPC UA, separate variables | 26 ms | 1 | 3,580 B |
S7 Web API 🔒 | 159 ms | ~1 | 11,386 B |
OPC UA, array elements | 179 ms | 1 | 3,810 B |
OPC UA, array elements, secured 🔒 | 181 ms | 1 | 3,888 B |
Modbus | 258 ms | 80 | 1,840 B |
Look at the two Modbus rows before anything else. Same controller, same 80 values, same protocol. One of them asks once and takes 3.7 ms. The other asks 80 times and takes 258. That is the whole argument of this article in two rows.
One figure to explain before the rest: the web interface’s request count is approximate. Its traffic is TLS records rather than a protocol the capture can read, so those are what was counted.
🔒 marks the ways in that were measured with the channel encrypted and the client authenticated: S7CommPlus, the web interface over HTTPS and OPC UA when secured. Everything else ran in the clear.
*S7CommPlus comes in several versions. The older ones can run without any protection. Only the newest is fully secured, with the client authenticated and the channel encrypted. Our driver implements only that version, so every S7CommPlus figure in this article was measured with full security.
Five things in that table.
S7Comm is the cheapest thing a SIMATIC can do, and it ships with the controller. It is also unauthenticated, unencrypted and blind to variable names: it reads bytes at an address, so your client has to know the memory layout from somewhere else. Move a variable in your program and something breaks quietly, with no compiler to catch it.
Plain S7CommPlus costs about five times the merged S7Comm read and buys two real things. Variable names instead of addresses, and a secured session. It is free and already on the controller. Merged, it comes close to merged S7Comm, for reasons the last point in this list explains, and under heavy load it can pull ahead, as the next section shows.
Read as array elements, OPC UA costs fifty times the merged S7Comm read and nine times plain S7CommPlus. Array elements are its worst case. Read as separate variables it takes 26 ms, seven times the merged S7Comm read and a little behind plain S7CommPlus; a later section compares the two shapes on all three CPUs. Note the requests column while you are there: OPC UA reads all 80 values in a single request. Its cost is not round trips. It is what the server does with them.
Connecting is a separate cost, paid once per connection, and there the protocols differ even more. The security section below has those figures.
The bytes column does not predict the time column. OPC UA over array elements moves 3,810 bytes in one request and takes 179 ms. Plain Modbus moves half that and takes 258 ms across 80 requests. The web interface moves three times as much as OPC UA and is faster. Whatever you are paying for, it is not data volume.
And the same weakness that makes S7Comm blind is what makes it tunable. Because an S7Comm address is a position rather than a name, a client can see that your values are neighbors and fetch them in one span. A protocol that uses names cannot do that from the request alone: arrInt[7] and arrInt[8] are two opaque names, and nothing in the request says they sit next to each other.
S7CommPlus can do better than that too, in data blocks with standard (non-optimized) access. There our client merges S7CommPlus reads as well, and the S7CommPlus, merged row measures the result. The feature is a direct result of this benchmark work: it grew out of surprises we ran into while building a hybrid S7Comm/S7CommPlus driver. In a block with optimized access it does not apply, and S7CommPlus reads one item per value. The cheapest door is still the one with the most headroom left in the client.
6.1. Under heavy load, merged S7CommPlus can win
At 80 values merged S7Comm is the fastest way into a SIMATIC. Ask for 2500 at once and the two S7 protocols change places on the controller that can least afford it:
| CPU | S7Comm, merged | Requests | Cycle | S7CommPlus, merged 🔒* | Requests | Cycle |
|---|---|---|---|---|---|---|
S7-1212C G2 | 35 ms | 6 | 0% | 38 ms | 1 | 0% |
S7-1511 | 51 ms | 6 | 0% | 57 ms | 1 | 0% |
Old S7-1212C | 407 ms | 24 | 16% | 157 ms | 1 | 5% |
On the G2 and the S7-1511 the two are level. Neither costs the cycle anything. Those CPUs have room to spare. The old S7-1212C is the controller where it matters most: the only one on this bench that ever reaches its communication cap. There merged S7CommPlus answers 2.6 times faster and costs the cycle a third as much. Asked for the same read every 200 ms, merged S7Comm reaches the cap; merged S7CommPlus costs 13%.
The reason is the request size, explained further down. Classic S7Comm negotiates 240-byte requests on that CPU and needs 24 of them for 2500 values, where S7CommPlus needs one. On a small controller every request costs time it does not have. So on the controller with the least to spare, the newer protocol is the gentler one: a faster answer means less time spent serving the client, and less chance of pushing an old PLC past its limit while it runs the machine.
And merged S7CommPlus gives you more than speed. Everything S7Comm cannot do comes with it:
Symbolic access. You ask for a variable by its name, not by a byte address that breaks silently when the program changes.
Authentication. The session is opened against the controller’s protection level and its password.
A signed and encrypted session. The traffic runs inside TLS.
Richer types. Structures, including user-defined types, nested structures, multi-dimensional arrays and single cells of them: everything S7Comm cannot address.
Merging works in data blocks with standard (non-optimized) access. What it cannot merge, such as a single cell of a matrix, it reads item by item, so nothing is lost; it is only slower.
6.2. OPC UA over separate variables
Most projects publish separate variables over OPC UA, not the elements of one array. So a later run reads that shape, one node per value, and puts it beside both S7 protocols at the same counts, each asked plainly and merged. Time for one read, at one read every 2 s:
| CPU | Values | S7Comm, merged | S7Comm | S7CommPlus, merged 🔒* | S7CommPlus 🔒* | OPC UA |
|---|---|---|---|---|---|---|
S7-1212C G2 | 80 | 3.6 ms | 11 ms | 6.2 ms | 19 ms | 26 ms |
S7-1212C G2 | 500 | 11 ms | 54 ms | 13 ms | 140 ms | 139 ms |
S7-1212C G2 | 2500 | 35 ms | 242 ms | 38 ms | 1 012 ms | — |
S7-1511 | 80 | 4.8 ms | 32 ms | 8.2 ms | 54 ms | 466 ms |
S7-1511 | 500 | 15 ms | 174 ms | 18 ms | 378 ms | 2 596 ms |
S7-1511 | 2500 | 51 ms | 847 ms | 57 ms | 2 189 ms | 32.5 s (array elements) |
Old S7-1212C | 80 | 13 ms | 181 ms | 29 ms | 239 ms | 735 ms (array elements) |
Old S7-1212C | 500 | 90 ms | 1 086 ms | 52 ms | 1 495 ms | 4 356 ms (array elements) |
Old S7-1212C | 2500 | 407 ms | 5 401 ms | 157 ms | 7 783 ms | — |
Merged, the two S7 protocols are close. On the G2 and the S7-1511 merged S7Comm is 1.1 to 1.7 times faster than merged S7CommPlus. On the old S7-1212C merged S7CommPlus is the faster of the two from 500 values on. Plain S7Comm stays 1.3 to 4 times ahead of plain S7CommPlus.
OPC UA is the slowest protocol on every CPU. On the G2 it is about level with plain S7CommPlus at 500 values and four to ten times behind merged S7CommPlus. The fiftyfold gap in the six-way table was mostly the array.
On the S7-1511 the gap is widest: OPC UA is seven to nine times behind plain S7CommPlus and 57 to 148 times behind merged. Its server spends about 5 ms per variable, whatever the variables look like. Separate variables help there too, but only by about 2.3 times.
There is no OPC UA figure at 2500 separate values. The two S7-1200s serve at most 1000 values over OPC UA, and the old S7-1212C had no memory left for a block of separate variables at all. The S7-1511 read 2500 only as array elements, in three requests and about 33 seconds.
7. The controller matters more than its age
The four controllers are not equally fast, and the reason is not what people assume.
One takes six times longer per request than another of the same family. The old S7-1212C needs about 12.6 ms for a request the G2 answers in 2.0 ms. Both are S7-1200s.
And it negotiates a 240-byte request where the others manage 960. A smaller request holds fewer values, so the same read needs more of them.
Those two facts explain every slow row on that controller. Its idle cycle does not: it is twice the G2’s, but a read takes four to twenty times as long. Its age does not come into it, except that both facts are things the newer generation improved.
It is worth being careful in the other direction too. The S7-1511 on this bench is three to six times slower per request than the S7-1516 over unmerged S7Comm. Merged S7Comm and Modbus show no such gap. It is the smallest S7-1500 and the S7-1516 is a much faster class, which is the likely explanation but not a proven one. Read its absolute times as that controller’s, not as "an S7-1500’s".
7.1. The setting that is worth a factor of two
A Siemens PLC reserves a share of its cycle for answering questions. In TIA it is Cycle load due to communication.
Same controller, same program, same values:
| S7-1511, communication cap | S7Comm, merged |
|---|---|
20% | 1511 ms |
50% | 737 ms |
Twice as fast, from a setting.
And the default is not the same on every family. Only the S7-1500 ships with 50%. The S7-300, the S7-400 and the S7-1200 all ship with 20%.
The S7-1200 is the interesting one there. It is not an older controller. It is the S7-1500’s sibling, the low-cost variant of the same generation. So this is not old hardware against new. Siemens give the more capable controller two and a half times the communication budget and leave the less capable one at 20%. The allowance tracks how much headroom a controller has, not how much it needs.
Two consequences. Any figure you quote has to say what the cap was. And before you compare two controllers in your own plant, check they are set the same, or you are measuring the setting.
7.2. What does not cost anything
I expected the PLC’s own program to matter. It does not, at least not here.
On the controller with the busiest program on the bench — an OPC UA server block, a Modbus server block and a model railway’s worth of inert logic — switching all three off changed nothing measurable. Every protocol landed within 0.6% of where it was.
That is measured rather than assumed, and it is worth knowing because "the PLC is busy" is the first thing everyone blames.
One honest qualification. Switching off the OPC UA block only skipped a function call. The server itself lives in the device configuration and kept answering, so this does not show that an OPC UA server is free.
8. What the traffic costs the controller
Everything so far measures what your client experiences. There is a second question, and on a busy plant it is the one that decides whether you have a problem: what does all this asking cost the PLC?
A PLC is not a server. It runs your program in a loop and answers questions in the gaps. Ask too much and the loop gets longer.
A longer loop does not look slow. Decisions are taken once per scan, so stretching the scan does not make the machine slower in any way an operator would call slow. It makes it less precise. The cut lands a millimetre off. The actuator overshoots, sometimes. The reject rate creeps up.
That is close to the worst diagnostic signature a fault can have. It is intermittent. It looks mechanical. Everything a maintenance engineer would sensibly check sits downstream of the cause. Nobody walks up to a machine cutting slightly out of tolerance and asks what protocol the historian is using.
This is also where the second story from the beginning of this article ends up. Nobody adds the dashboard that breaks the line. They add the eleventh one.
8.1. Measuring it from inside the CPU
Every number so far was taken flat out: ask again the moment the answer arrives. That is the right way to compare protocols and the wrong way to ask this question, because flat out saturates the machine and everything converges on the same ceiling.
So this needs a different test. A small block in the PLC program counts its scans and sums their durations, so every rate yields the exact mean cycle under that load. The client polls 80, 500 or 2500 values at a held rate, from once every 2 s to every 10 ms, three passes each, against an idle baseline taken the same way. A positive control proves the figure can move before a flat row is read as "no cost".
The cost is stated as the communication share: how much longer the mean cycle got, as a share of the loaded cycle. A workload that reaches the communication cap from earlier stops growing the cycle and starts waiting, so a cell at the cap measured the limit, not the load. Every CPU was set to a 20% cap, the S7-1200 default and deliberately below the S7-1500’s 50%, so all four were measured against the same limit.
| Protocol | Old S7-1212C | G2 | S7-1511 | S7-1516 |
|---|---|---|---|---|
S7Comm, merged | at the cap | 1% | 1% | 1% |
S7Comm | at the cap | 1% | 1% | 3% |
S7CommPlus, merged 🔒* | at the cap | 1% | 1% | — |
S7CommPlus 🔒* | at the cap | 2% | 0% | — |
OPC UA | at the cap | 1% | 0% | — |
Modbus TCP, merged | at the cap | 0% | 8% | 1% |
Modbus TCP | at the cap | 0% | 13% | 1% |
S7 Web API 🔒 | — | 2% | 1% | — |
| Poll every | S7Comm, merged | S7Comm | S7CommPlus, merged 🔒* | S7CommPlus 🔒* | OPC UA | Modbus TCP, merged | Modbus TCP |
|---|---|---|---|---|---|---|---|
2 s | idle | 2% | idle | 3% | 8% | idle | 18% |
500 ms | 1% | 7% | 1% | 10% | at the cap | 1% | at the cap |
200 ms | 2% | at the cap | 4% | at the cap | at the cap | 2% | at the cap |
50 ms | 7% | at the cap | 14% | at the cap | at the cap | 9% | at the cap |
20 ms | not stable | at the cap | at the cap | at the cap | at the cap | at the cap | at the cap |
10 ms | at the cap | at the cap | at the cap | at the cap | at the cap | at the cap | at the cap |
Five things come out of that.
Only one of these controllers has a load curve at all. On the G2, the S7-1511 and the S7-1516, no protocol cost the cycle more than 3% at any rate this bench could drive, with one exception: Modbus on the S7-1511, at 8% merged and 13% asked plainly. That CPU handles all its communication through one slow channel, one message at a time, at about 0.7 ms per Modbus request against 0.01 to 0.03 ms on the G2 and the S7-1516. Modbus runs inside the user program, so on the S7-1511 that wait lands in the cycle. If your hardware is current and your client is sensible, this is probably not your problem. Better to know that before you go looking for it.
Asking badly costs more than the protocol does. Merged, that controller polls every 200 ms for 2% of its cycle. Unmerged, it reaches the cap there. Same protocol, same hardware, same eighty values. The only difference is how the client asked. Plain Modbus, one request per value, costs it 18% already at one read every 2 s.
The heavier protocols reach the cap first. On the old S7-1212C, OPC UA costs 8% already at one read every 2 s and hits the cap at one every 500 ms. Plain S7CommPlus hits it at 200 ms. Merged, the same protocol costs 4% there.
At large reads, merged S7CommPlus beats merged S7Comm on that controller. At 2500 values it costs the old S7-1212C 1% at one read every 2 s, 5% every 500 ms and 13% every 200 ms, where merged S7Comm cost 4%, 16% and the cap. The reason is the request size from earlier: classic S7Comm negotiates 240 bytes on this CPU and needs 24 requests for 2500 values, S7CommPlus needs one. At 80 values merged S7Comm is still the cheaper of the two.
And at the cap it stops. That is the communication cap from earlier, doing exactly what it is for. Ask faster and you do not get more data. You get the same data and a stretched cycle.
That last one is worth dwelling on, because it makes the failure quiet. A controller at its cap does not refuse anything. It just stops keeping up, and the first place that shows is the machine rather than the monitoring.
8.2. Subscriptions
Subscriptions were measured on all three CPUs that offer them: S7CommPlus on all three, OPC UA on the G2 and the S7-1511, at 80, 500 and 2500 values. The values did not change during the test, so it measures what watching costs and what a cyclic subscription delivers. Delivering changes was not part of this run.
S7CommPlus delivers what it is asked, down to 100 ms. All three CPUs refuse a faster cycle. At every accepted one, every value arrived at the requested rate, whether the client asked merged or plainly. Subscriptions read item by item; the merged range reads do not apply to them.
OPC UA sends only what changed. With static values each server delivered every value once, at the start, and then nothing, at any requested rate. The protocol works that way; no server limit is involved. The servers also cap how much they will watch: the S7-1511 accepted 1000 of 2500 values, the G2 200 of 500. The old S7-1212C’s server does not watch array elements at all.
Only the old S7-1212C shows what watching costs. The cycle figures of the G2 and the S7-1511 did not move even under the heaviest subscription, so they cannot tell, which is not the same as free. On the old S7-1212C, watching 80 values cost 16 µs, 1% of the cycle, and delivering them every 100 ms 88 µs, 5%. Polling the same 80 values every 50 ms cost it 257 µs.
9. Security is cheap to use and expensive to start
This surprised me, and it is the most useful thing in the article for anyone currently arguing about it.
Look back at the six-way table. One read of 80 values over OPC UA takes 179 ms unsecured and 181 ms signed and encrypted. That is a difference of 1%.
Push it further. On one controller I timed a single batched read at several sizes:
| Values in one read | Unsecured | Secured |
|---|---|---|
600 | 7.47 s | 7.48 s |
700 | 8.71 s | 8.71 s |
1000 | 12.40 s | 12.47 s |
Encryption costs about a millisecond per read at 700 values, and 70 ms at a thousand. It is too small to plan around.
The cost is all in the handshake, and that is where it hurts. Opening the connection is a separate number, and it is not small:
| How you connect | S7-1212C G2 | S7-1511 | Old S7-1212C |
|---|---|---|---|
Modbus | 82 ms | 62 ms | 84 ms |
S7Comm | 71 ms | 78 ms | 149 ms |
S7 Web API 🔒 | 421 ms | 908 ms | 4264 ms |
S7CommPlus 🔒* | 572 ms | 1153 ms | 10086 ms |
OPC UA | 2048 ms | 211 ms | 444 ms |
OPC UA, secured 🔒 | 9175 ms | 5819 ms | 28472 ms |
The table has no row for merged S7CommPlus because merging does not touch the connection. Both open the same session: in a later sweep, plain and merged S7CommPlus exchanged the same bytes while connecting, on all three CPUs, to within 0.2%. The S7CommPlus row stands for both.
The connect column is a ladder. Modbus is the only protocol with no handshake at all. There is no session to establish because Modbus has no concept of one, so its 82 ms on the G2 is a TCP socket and nothing else. S7Comm does negotiate, but barely: a hundred bytes or so each way, and that exchange is where the request size from earlier gets agreed. S7CommPlus sets up a proper session and costs half a second. OPC UA costs two seconds, and nine with security switched on.
You pay that once per connection. So it matters enormously if your client reconnects for every poll, and not at all if it holds the connection open.
Read the bottom row twice. Turning OPC UA’s security on takes the connection from two seconds to nine on one controller, and from under half a second to twenty-eight and a half seconds on the old one. That is not a rounding error on a startup cost. That is a client that looks broken.
And S7CommPlus is in the same territory on that controller — ten seconds to open a session, against 149 ms for S7Comm on the same wire.
Which gives a rule you can act on:
Hold your connections open. If security is making your system unusable, the problem is almost certainly your connection handling and not your policy.
That is not a small ask in practice. Plenty of tooling connects, reads and disconnects on a schedule, because that is the simple thing to write, and on that old controller a secured OPC UA poll built that way spends twenty-eight seconds connecting to do 214 ms of work. Every time.
Which is where people reach for the obvious fix and turn security off. Then they are paying a licence fee for a protocol that is now protecting nothing, and the thing that was actually wrong was never the encryption.
9.1. "It uses TLS" and "it is secure" are different claims
Both S7CommPlus and OPC UA can encrypt the channel, and both can demand a credential. So the lazy version of this comparison, where one is protected and the other is not, is simply wrong.
Where the identity lives. On an S7-1500 you set a protection level in TIA and give it a password. That gates the controller, and it is one gate for everyone who connects. OPC UA does separate two things that S7CommPlus runs together: the security of the channel, and who you are. You can require certificates to open the connection and a named user to use it.
But do not mistake authentication for authorisation. On most controllers in the field, getting in is all you get. There is no way over OPC UA to admit one user for reading and another for reading and writing. Everyone who passes the gate has the same access.
That is starting to change. With S7-1500 / ET 200SP / ET 200pro ≥ FW V3.1 (TIA ≥ V19) can grant permissions per user. Michael Grollmus walks through the setup in his video. If your controllers are that new, it is worth the effort. Anything older is still one gate with the same access for everyone.
On those older controllers, the one door that can tell two users apart is the web interface. You can give one account read access and another read and write.
It does not go further than that. There is no way to say that this user may write these tags and not those. But until recently, read-only against read-write was a distinction none of the other doors could make at all. That made it an odd place for it to live: the one least suited to moving data was the only one that could express it.
Whether anyone else can check it. OPC UA is written to a public specification, so you can state your posture in terms an auditor recognises and a second tool can verify. S7CommPlus is a Siemens mechanism. It has been publicly reverse-engineered, and Siemens have their own bulletin on that, but it is not a standard you can point anyone at.
And on OPC UA all of it is optional. It was off by default on two of my controllers. "We use OPC UA" is not a statement about security.
10. What actually stopped, and why
Several rows in my sweep failed. Almost none of them are what they look like.
Nearly every failure was my own timeout running out. The client gives a read request a deadline when it is submitted. An unmerged Modbus read of 1000 values is a thousand requests against one deadline, so once the clock passes five seconds every remaining value fails at once. Raise the timeout to sixty seconds and those rows pass. That is a speed difference crossing a default, not a protocol refusing to do something.
One controller’s OPC UA server does not get cheaper per value as the batch grows. It needs about 12.4 ms per value whether you ask for six hundred or a thousand. So it never refuses; it simply takes longer than whatever timeout the client set. Where it "stops" is wherever you put your deadline.
There was exactly one real size limit in the whole sweep. The web interface on one controller refuses a request body over 64 KiB, which a thousand reads exceeds. Bisected to the byte. Our driver had assumed twice that for a controller that reports nothing, and it now assumes the smaller number.
The general lesson is worth more than any of the specifics. A failure in a benchmark is evidence of something, and the something is usually your own configuration.
11. The second bill
There is a cost that never appears in a benchmark, and on a plant-sized rollout it often decides the argument.
S7Comm, S7CommPlus and the web interface are all on board and free. Nothing to order, nothing to renew.
OPC UA is a separately licensed product. They are runtime licences: Basic on an S7-1200, and Small, Medium or Large on an S7-1500, picked by how big the CPU is.
Nothing enforces it. There is no key to install and no check on the controller. TIA tells you which size your configuration needs, and the CPU serves OPC UA whether you bought one or not. So it is a legal obligation rather than a technical gate, which makes it the kind of cost that surfaces in an audit rather than at commissioning.
Siemens do not publish a price list, so treat these as orders of magnitude from public distributor listings, converted to euros at an approximate rate:
| Licence | Covers | Roughly |
|---|---|---|
S7-1200 Basic | S7-1200 | EUR 60 – 95 |
S7-1500 Small | CPUs up to 1513 | around EUR 100 |
S7-1500 Medium | CPUs up to 1516 | around EUR 320 |
S7-1500 Large | all S7-1500 CPUs | around EUR 400 |
Per controller. Twenty controllers is a four-figure sum. Two hundred is a five-figure one.
And there is a loop worth naming, because it is invisible in a feature comparison. The price is set by how big the CPU is, not by how much OPC UA you use:
Choosing the heavier protocol can force a larger CPU, and a larger CPU costs more to licence for the heavier protocol.
Decide the protocol and the hardware together. Deciding the protocol afterwards is how a project finds a line item nobody budgeted for.
11.1. And the CPU decides how much you can publish
There is a harder version of that trap, and it has nothing to do with speed.
Over OPC UA, how many data points a controller can publish is fixed by the CPU, and TIA checks it when the project compiles. Go over and the project does not build. You do not find this out on the shopfloor. You find it out at your desk, after the hardware is bought.
| Controller | Allowed |
|---|---|
S7-1212C | 2000 nodes |
S7-1212C G2 | 2000 nodes |
S7-1511 | 1000 nodes |
An array counts as one node per element. So a single array of three thousand values does not fit any of them.
Yes, the S7-1500 number is the smaller one. It also has a way out that the 1200s appear not to have. Through its standard SIMATIC interface an array is one node, read by index range, so it costs one node however long it is — ten thousand elements compile there without complaint. Whether an S7-1200 offers that route at all is an open question, and our evidence says it does not.
If that holds, an S7-1200 cannot publish more than two thousand OPC UA data points by any route.
So picture a simple machine that needs a small CPU and has one large array to publish. Over OPC UA you may end up buying a bigger controller, not because the small one is too slow, but because your project will not compile on it.
That same S7-1200 serves the same three thousand elements over S7CommPlus, in one request, with nothing configured on the PLC. And at the one size both protocols can reach, S7CommPlus reads the array 2.7 times faster as well. Published as separate variables, the G2’s two are level, but then the node limit is the one you are spending.
12. So what should you do?
Ask for what you need in as few requests as your client will build. This is the single highest-value change available to most installations and it costs nothing.
Check whether your client merges adjacent addresses, and on which protocols. If you are on Modbus and it does not, you are leaving two orders of magnitude on the table. If you are on S7Comm you are leaving somewhere between five and thirty times, depending on the controller. On S7CommPlus over a standard-access data block, between three and fifty times, growing with the size of the read.
On an old or small SIMATIC, prefer merged S7CommPlus over a standard-access data block. On the old S7-1212C it read 2500 values 2.6 times faster than merged S7Comm and cost the cycle a third as much, with names, authentication and encryption on top.
Hold your connections open. Security is nearly free once connected and expensive to establish. A client that reconnects per poll pays the worst of both.
Before you buy OPC UA for variable names on Siemens hardware, price S7CommPlus. It is on the controller and it costs nothing. Read merged from a standard-access block, it is four to ten times faster than OPC UA over separate variables on the G2, and 57 to 148 times faster on the S7-1511. Read item by item, it is still about level with OPC UA on the G2. Buy OPC UA for vendor-neutrality, for the information model, or for a security story an auditor recognises. Do not buy it for naming you already have.
If your read volume is modest and your security requirements are real, buy OPC UA. The overhead is affordable at low rates and you get certificates, user authentication and selectable encryption to a public specification. This is where the standard advice is simply right. Budget the licence per CPU before you price the project.
If you have many vendors on the floor, you want one API — and there are two ways to get one. OPC UA gives you one by making every device speak the same protocol. The other way is to put the single API in your own application, and let it speak whatever each device speaks best. Which of those you want is a real decision with real trade-offs, and it gets a chapter of its own further down rather than a bullet here.
Before you replace a slow controller, check its request cost and its request size. A six-fold difference per request and a 240-byte limit explained every slow row on my oldest device. Both are cheaper to work around in software than in hardware.
Check the communication cap on every controller you are comparing. It is worth a factor of two and the default is not the same on every family.
And measure on your oldest, busiest machine, not your newest. Watch the cycle time rather than the response time. A controller that is answering you at the cost of its own scan will not tell you so; it will just get less precise, and you will look for the cause somewhere else.
And if security is the axis you care about most, it may be worth waiting a few weeks. On 13 October 2026 we are publishing a series on S7 security, written with Siemens under coordinated disclosure. This article has deliberately stayed on what these protocols cost. That one is about what they actually protect, and if you are close to a decision that turns on the difference, read it before you commit.
13. Choosing per device, without rewriting per device
A disclosure first, because this is published by a company with an interest in its conclusions. I work on ToddySoft Connect, which provides drivers for every protocol here, and every number came out of its test suite. The tables came first and the argument came out of them. You should still know about my interest before you weigh my conclusions.
OPC UA’s strongest selling point is not speed and it is not really security either. It is the unified API. One client, one address space, one set of semantics, across controllers from vendors who agree on nothing else. That is a genuine engineering good and the free protocols offer nothing like it.
But look at how it delivers that. The abstraction lives on the wire. You get one API because every device speaks the same protocol, which means every device must speak it, including the ones that are worst at it. You cannot buy the unification without paying for the transport on every controller you own.
There is another place to put that abstraction. Move it up a layer, so you have one API in your application and many protocols underneath, and the protocol becomes a deployment detail instead of an architectural commitment.
So the new cell runs OPC UA, signed and encrypted, because there the security costs almost nothing. The twelve-year-old controller at the far end of the hall runs S7CommPlus, still named, still free. The old S7-300 that speaks nothing else runs S7Comm. Your application does not know the difference.
That is what ToddySoft Connect does, and it is the reason this bench exists at all: every protocol in this article is a driver we maintain, so the cost of each one is a question we have to keep answering anyway.
Read the next section before you take that as a recommendation.
13.1. What that does not buy you
A uniform API is not uniform capability. No driver layer conjures a type a device does not have. What it can do is turn a rewrite into a constraint you design around.
An address-based protocol has a cost no time column shows. The same value sits at a different address on every controller, so your configuration changes per device, and a stale address does not fail loudly. It reads the neighboring bytes and decodes them into a plausible number.
And you do not get OPC UA’s information model. A unified access API gives you names, types and values. It does not give you semantics: what this machine is, what this value means, in a form another vendor’s software can interpret. If you need that, you need OPC UA.
13.2. What the bench has already changed
Look again at what the tables say about S7Comm. It is the cheapest thing a SIMATIC can do, and its one crippling limitation is that it cannot name anything.
For a long time I read that as a statement about what S7Comm cannot do. It is not. It is a statement about where the layout comes from — and there is a controller on the other end of the wire that already knows it.
The first version of this article said an S7 driver was being built that would fetch names and types over S7CommPlus, then switch to S7Comm to move the data, and keep the merging because the reads would be positional again.
It landed differently, and stayed inside one protocol. In a data block with standard access, our client reads the layout over S7CommPlus by name, then reads the data over S7CommPlus by byte range. Every range read carries a check that the block’s layout has not changed since. If it has, the client reads by name again and reloads the layout. The session stays authenticated and encrypted.
It got the same treatment as everything else: measured, with the off-switch built in first. Switched off, it reproduces the old figures. Switched on, 2500 values read 27 to 50 times faster depending on the CPU, at 2.4 instead of about 40 bytes per value, and on the old S7-1212C faster and cheaper for the cycle than merged S7Comm.
14. What I got wrong
Every section above has a correction folded into it. Not one was caught by review. Each was caught by somebody asking an obvious question of a number that looked fine.
"Merging is worth 7.6x on S7Comm, and 18x on Modbus." Both figures were diluted by the benchmark’s own design. Each run read every value individually before reading them in batches, and nothing can merge a request carrying one value — so both halves of every comparison paid that phase, which capped every ratio near the number of batched rounds. Measured batched read against batched read, it is 100 to 130 times on Modbus and 14 to 30 on S7Comm.
"Our optimizer makes some controllers slower." JVM warm-up, not the driver. The benchmark was measuring its own compilation.
"The old S7-1212C is slow because it is old." Its 240-byte request size and its six-fold per-request cost explain it. The PLC program contributes nothing measurable.
"Unmerged Modbus cannot read 2000 values." It can. That was our own five-second timeout, applied once per read request.
"OPC UA hits a request-size limit somewhere above a hundred values." No. One controller’s server needs 12.4 ms per value and ran past the deadline we set it.
"OPC UA is a hundred times slower than S7Comm on a SIMATIC." Only for the shape we first read it in. Every OPC UA value was an array element, which is that protocol’s worst case. Read as separate variables, the G2’s server was about seven times faster, and close to plain S7CommPlus.
Six mistakes, one shape. Five of the six were our own configuration mistaken for a property of somebody else’s product. Which is worth remembering the next time a benchmark tells you something convenient.
15. What this does not tell you
The load test is one bench, with one cap. Every CPU was set to a 20% communication cap, the S7-1200 default. The controllers were running an idle program. A controller already at ninety percent of its cycle has less room than any figure here suggests, which is the case that matters most and the one I cannot measure for you.
Reads only — but not as big a gap as that sounds. Every figure here is a read. Separate driver tests put writing at about the same speed as reading on almost every protocol. There is one asymmetry, and it follows from everything above: when merging reads, a client can over-read and throw away the bytes between your values. When merging writes it cannot, because those bytes belong to something else and writing them would destroy it. So where your values have gaps between them, a write splits into more chunks than the same read does, and gains less from merging.
One bench, one vendor, five controllers. The ratios travel better than the absolute times.
Contiguous values, so the tables are a bracket rather than a prediction. Every figure reads one array in order. That makes the merged rows the best a client could possibly do, and the unmerged rows the worst. Your plant sits somewhere between the two, and where it sits depends on how your data block is laid out. The gap between the two rows is the size of the prize, not the size of your winnings. For OPC UA it runs the other way: the array is its worst case.
The ratio ceiling is partly the benchmark. Twenty batched rounds caps it near 21; the shape is real, the height is not all protocol.
Every figure states its communication cap, and you should too. It is worth a factor of two.
16. The point
There is no best protocol. There is a controller with a budget, a network with a budget, a threat model, and a set of options that spend those budgets very differently.
But the biggest number in this whole article is not a protocol choice at all. It is the difference between a client that asks well and one that does not — a hundredfold on Modbus, and up to thirty on S7Comm, on the same wire, to the same controller, for the same data.
Group your reads. Hold your connections open. Measure before you commit. And when somebody tells you a protocol is the answer, ask them what it costs.
17. Version history
2026-10-06: Link to the cross-vendor comparison. A lock marks the ways in that ran encrypted and authenticated.
2026-10-05: Controller load, OPC UA over separate variables and subscriptions re-measured with the driver exactly as it ships. The six-way table rebuilt on one run (G2, 80 values) and sorted by read time. New section on heavy load, where merged S7CommPlus beats merged S7Comm on the old S7-1212C, with what S7CommPlus adds besides speed. Subscriptions measured with values that hold still; the findings on changing values removed until they are measured the same way.
2026-09-30: Controller load on four CPUs and OPC UA over separate variables added. S7Comm labels corrected. Link to the TwinCAT article.
2026-09-25: Charts for the curve. A note on per-user access rights, after reader feedback.
2026-09-16: First published.


