SRT: Sorting an Array in Place When the Scan Has to Keep Running

The Array operand on an SRT accepts SINT, INT, DINT and REAL, and 1756-RM018A prints no fifth entry. No structures, no strings, no array of a user-defined type — so on a 1756-L83E or a 5069-L330ERM alike, the instruction that sorts an array cannot sort records, and every design built on it deals with that first. So the useful version of this instruction is narrow and worth knowing exactly: copy the values you care about into a scratch array, sort the copy, and read the answer out by index. That gives you a median, a trimmed mean, the third-largest reading, or an ordered list of setpoints, for the cost of one COP and one sort, and it leaves the original data in the order it arrived. Sorting the live array instead is how a parallel array quietly stops matching its neighbour.

It sorts numbers. Ascending. In place. In one scan, with no way to spread the work.

SRT operand block with Array SortBuf element 0, Dim to vary 0, Control SortCtrl, Length 51, Position 0, beside the CONTROL structure showing EN, DN and ER

Five operands, no destination. The array the instruction is pointed at is the array it rewrites.

In place means the original order is gone

There is no Destination operand on an SRT.

The instruction rewrites the array you hand it, and there is no undo and no copy kept, which matters more than it sounds because of what usually sits beside that array. A checkweigher keeps PartWeight as a REAL[51] and PartSerial as a DINT[51] alongside it, element 17 of one belonging with element 17 of the other, and that correspondence is the only thing linking a weight to a part. Sort PartWeight and the link is broken — not corrupted in a way anything can detect, just silently rearranged, with PartWeight[17] now belonging to some other part entirely while PartSerial[17] still says what it always said.

Nothing faults. The traceability record is simply wrong from that scan onward, and stays wrong.

One COP in front of the sort removes the whole problem.

COP PartWeight[0] SortBuf[0] 51 costs 0.186 + (51 × 0.02) µs on a 5580, which is 1.2 µs, and after it the original array is untouched and the sorted copy answers whatever question you had.

Sort in place only when the array genuinely is the thing being ordered, such as setpoints an operator edits.

What you get is ascending, and there is no switch

SRT sorts into ascending order, and there is nothing to configure.

If you want the largest value, read the last element rather than looking for a setting — with a Length of 51 sorted into SortBuf[0] through SortBuf[50], the maximum is SortBuf[50], the minimum is SortBuf[0], and the median is SortBuf[25].

Reading down from the top with a decrementing index gives a descending list at no cost, and when that index is a tag rather than a constant, indirect addressing is doing the real work.

The median is the reason most people end up here.

A mean over a sample window is one wild reading away from being wrong, and a checkweigher, a load cell or a level transmitter produces exactly that reading — a 24.6 kg part that samples at 41.2 kg because somebody leaned on the frame.

One sample in 51 moves a 51-point mean by 0.33 kg. It moves the median by nothing.

Sort the 51 samples and read SortBuf[25] and the bad reading has been pushed to SortBuf[50]

Advertisement
, where it counts for one element out of 51 and changes the answer not at all. That property is worth more than it costs in a lot of process measurement, and it is the difference between a filtered value you can alarm on and one you cannot. Where the window is long and the maths is heavier, what AVE and STD cost over the same array is the comparison to make before choosing.

A REAL 51-element weight buffer before and after the sort, with a 41.2 kg outlier at index 8 moving to index 50 and the median marked at 24.60 kg

The outlier does not disappear. It moves to the end, where it counts for one element out of 51.

The faults, and the one that has its own code

Three ways to fault this one, and two different codes.

A .POS below zero or a .LEN below zero is a Type 4, Code 21 major fault, and it is the only Code 21 in this corner of the instruction set, which makes it a useful thing to recognise: the fault log is telling you a control tag holds a negative number, not that an array was overrun. A Dimension to vary greater than the number of dimensions the array has is Type 4, Code 20. A Length that reaches past the end of the array is Type 4, Code 20 as well. The reference adds that when .ER is set the instruction does not execute, so a control tag left in an error state gives you a rung that scans, looks live, and sorts nothing.

Check .ER before trusting the order.

There is also a difference between controller families here that is easy to read past in the description, and it is about where the instruction stops. On CompactLogix 5370 and ControlLogix 5570 the scope of the instruction is constrained by the base tag, which means an SRT pointed at an array inside a structure can cross member boundaries and sort data belonging to the members after it. On CompactLogix 5380, ControlLogix 5580 and ControlLogix 5590 the manual says the data is constrained by the specified member instead. Same instruction, same project file, different blast radius, and if a routine is being moved from an older controller to a newer one — or the other way — this is one of the quiet behaviour changes that comes with it. The recipe arrays inside a UDT are where this turns up in practice.

Why the published timing is flat, and what to do about it

Here is the honest part. The number most people want is not printed anywhere.

LOGIX-RM002B lists SRT twice, at 0.443 µs and 0.449 µs, and both entries are flat — no + (x * something) term of the kind every other file instruction in the same table carries.

FFU has one. BSL has one. AVE and STD have large ones. SRT has none at all.

A sort whose cost does not depend on how many elements it sorts is not a thing that exists, so the table is not telling you what a long sort costs. Treat that flat figure as the overhead of entering and leaving the instruction, and nothing more.

What governs the cost is Length, and it does not grow in a straight line.

Rockwell does not publish which algorithm the instruction uses, so the useful statement is the safe one: comparison sorts grow faster than linearly with the number of elements, so doubling the array more than doubles the time, and a sort that is comfortable at 51 elements is not necessarily comfortable at 500. That is a reason to measure rather than to extrapolate. The measurement is easy and takes two minutes: right-click the program, open Properties, go to the Monitor tab, and read Max and Last with the sort running, then bypass the rung and read them again. The difference is your number, on your controller, at your array size, and LOGIX-RM002B says as much in its own introduction — the times are approximations that vary with project configuration and programming technique.

There is no Mode operand on an SRT.

That is the part that decides where it can live. FAL and FSC can be told to spread their work over several scans, and that instruction pair has its own article on this site; SRT cannot, so whatever the sort costs, it costs in one scan of one task, and the only lever left is how often the rung goes true and how many elements it is given. The instruction is transitional, which helps: the reference says the relay ladder toggles rung-condition-in from false to true for it to execute, and in Structured Text the same rule applies — SRT(SortBuf[0],0,SortCtrl); runs when the condition around it has been false and then true, not on every pass through the routine.

A table of published rows: FFU, BSL, FLL, FAL, AVE and STD each with a per-element term, and SRT with a flat 0.443 microseconds and no length term

Every other file instruction in the table carries a term in x. The one that sorts does not, which is the gap to measure rather than to guess at.

Sorting records, when the instruction will not

The moment the data is a structure, SRT is out.

The usual answer is an index sort done by hand: build a DINT[n] of indices, sort a key array with SRT, and use the sorted keys to look up records through indirect addressing, which keeps the records themselves still and moves only numbers. The other answer, and often the better one, is to ask whether the controller needs the order at all. A batch report that wants the ten heaviest parts of a shift is a database question rather than a control question, and a controller that hands over an unsorted array and a timestamp has done its job. Sorting 500 records for a screen nobody opens twice a shift shows up in scan time long after its author has left.

Sort the smallest thing that answers the question.

Sort the smallest thing that answers the question, and notice when nothing in the control logic reads the result.

Advertisement

What to check next

Put the sorted array and its neighbour side by side in a watch window and confirm that nothing else in the project depends on the order you just destroyed — that check takes a minute and it is the one that matters most.

Then measure the sort with the Monitor tab at the array size you actually run, not the size you tested with, and write that number into the rung comment where the next person will find it.

If the sort filters a measurement rather than ordering a list, check whether a median of 51 is covering for something a properly scaled analogue input should have handled upstream. A sort is an expensive place to fix a noisy signal.