Efficiency and Pylogix
I receive quite a few questions about how to speed pylogix
up. It is important to understand how
pylogix reads/writes tags, once we have a good understanding, there are a few
different strategies, depending on your usage.
I’ll cover a few use cases.
How Pylogix Reads/Writes Tags
When you create an instance of pylogix, there is a
dictionary KnownTags that stores each unique tag name read and its data type.
The data type is necessary for a write because the PLC needs to know how many
bytes you are sending. These data types
are stored in the dictionary CIPTypes.
I’m only going to cover the basic types, not STRUCTS (UDT’s, CONTROL,
TIMER, COUNTER, etc.). Dealing with
STRUCTS is a different subject.
Since the PLC needs to know the type when writing a value to
a tag, the first time a user reads or writes, I first make a read to get the
type, that is saved in KnownTags dictionary.
The next time you interact with the tag, I used the saved type. Of course, this happens within the same
instance of PLC, if you create a new instance, the memory of known tags will be
lost. This process isn’t the most
efficient, I chose user simplicity over pure performance.
With a simple script to read a single tag, if you were to
watch the process in Wireshark, you would see the following:
- Register Session
- Forward Open (specifies the connection parameters)
- Symbolic Read (to get data type)
- Symbolic Read (to get value)
- Forward Close
- Unregister Session
When discussing efficiency using pylogix, it is that
Symbolic Read to get the data type that we are talking about. Register/Unregister/FowardOpen/ForwardClose
will happen once per new connection and the Symbolic Read must happen each time
you request the value with Read (or write a value with Write).
Being More Efficient
There are a few things as a user you can do to be more
efficient if performance is that important to you. When reading many unique
tags, providing the data type up front will save the discovery packet. An example:
ret = comm.Read("ProductPointer", datatype=0xc4) # 0xc4 = DINT
Personally, I almost never use this, that is mainly because
for my use cases, the extra time typing in the data type takes longer than
letting pylogix get it for me. If you
are going to connect and read 500 unique tags then get out, it might make sense
to give the data type up front, this would save 500 packets.
Probably the most effective way to be efficient is to
read/write using lists. Providing the
Read method a list of tags allows pylogix to pack multiple requests into a
single packet. These days, pylogix will
pack the data-type discovery and value read/write in to packets. The number that fits in a request depends on
the data type and connection size.
Modern controllers use a packet size of 4002 bytes. Older controllers
only support 508-byte packets.
Of course, you can utilize both lists and provide the data
type to gain more efficiency. When
utilizing lists, you must also provide the length of the read, even if it is
just 1.
For ultimate performance, arrange your data in the PLC in
arrays. Reading/Writing arrays is by far
the most efficient way to transfer data.
This will often mean more work in the PLC to organize the data, but if
maximum performance is required, there is no faster way.
Use Case Examples
We’ll go over a few use cases and show the performance of
each. We’re going to read 10 tags,
showing the various ways it can be done.
Gaining efficiency with each example.
I’ll be using a 5069 CompactLogix controller, which is very fast, these
tests would be slower with the older series, or a ControlLogix with EN2T. The first example, I’d consider wrong, but I
see it happen a lot, so we’ll demonstrate it.
We’re going to run each test 20 times and take the average time. Here are the tags:
BaseDINT, BaseSTRING, BaseINT, BaseREAL, BaseSINT, BaseLINT,
BaseTIMER.ACC, BaseBOOL, BaseCOUNTER.PRE, BaseBITS.0
First Test (Wrong)
In this test, we read the tags one at a time, what we did
incorrectly is create an instance of PLC with each read. This will open/close a connection with each
read. Not good. This test took an average of 152.255ms and
took 222 packets.
The packet count was high because each read included
register/unregister session and forward open/forward close. I show this example because I see users make
this mistake. It’s easy to do an not
realize the impact.
Second Test (Corrected)
In this test, we reversed the order of the with/for. Create an instance of PLC, then iterate the
tag list. Doing this will keep the
connection open, using one connection, which eliminates all the extra
sessions. This test took an average of
30.639 and 69 packets. A lot better, in
fact, the biggest gain we’ll get. We can
improve a little more though.
Third Test (Read using Lists)
with pylogix.PLC("192.168.1.10") as comm:
The tags are already in list format, so this time we’ll use
it. Not only will it be faster, but the
code is simplified. This test took an
average of 25.834ms and took 20 packets.
The reason for the reduction in packets is because multiple tag reads
are packed into a packet. In this case,
one packet to get all 10 data types and one packet to get the value.
Fourth Test (Include Data Type)
This test includes the data type with the read, which
eliminates the packet to get the type. The
number of packets was reduced to 12 and took an average of 17.256ms. I’m personally not a fan of this much
optimization, the extra time it took to type out the list in that format wasn’t
worth the 8ms saved, but your application may benefit from it.
Comments
Post a Comment