Queuing Methods
Spire_Jeff
Posts: 1,917
I saw the queuing method posted in a recent technote and I was not happy to see all of the string manipulation. I think that string operations are some of the most processor intensive commands, so I decided to test the performance of the provided queue versus the two queues I have written. Here are the results:
The two queues I use are storing the data in a structure (or it could be an array depending on needs) and using two pointers one that indicates insertion point and one the indicates current command to send. You can see that using pointers is WAY faster than string concatenation.
I also found one other problem with the queue provided by AMX. They have a limit of 50000 chars for queuing, but netlinx concatenation falls apart at 16000 chars. If you are using 200 character commands (think to a touch panel more than to a 232 device), then you will hit the problem area at around 79 commands being queued. Be aware of this limitation when using the queue they provide.
Jeff
Line 1 (10:58:20.046):: ********************************************************* Line 2 (10:58:20.062):: * TEST 1 REPORT: Load String Queue Line 3 (10:58:20.062):: * Most recent 5 runs: Line 4 (10:58:20.062):: * 1: 4542ms Line 5 (10:58:20.062):: * 2: 4562ms Line 6 (10:58:20.062):: * 3: 5252ms Line 7 (10:58:20.062):: * 4: 4544ms Line 8 (10:58:20.062):: * 5: 4542ms Line 9 (10:58:20.062):: *---------------------------------------------------------- Line 10 (10:58:20.062):: * Average run time: 4687ms - over 5 tests Line 11 (10:58:20.062):: ********************************************************* Line 12 (10:58:20.062):: ********************************************************* Line 13 (10:58:20.062):: * TEST 2 REPORT: Load Structure Queue, no tracking Line 14 (10:58:20.062):: * Most recent 5 runs: Line 15 (10:58:20.062):: * 1: 199ms Line 16 (10:58:20.062):: * 2: 229ms Line 17 (10:58:20.062):: * 3: 227ms Line 18 (10:58:20.062):: * 4: 198ms Line 19 (10:58:20.062):: * 5: 196ms Line 20 (10:58:20.062):: *---------------------------------------------------------- Line 21 (10:58:20.062):: * Average run time: 209ms - over 5 tests Line 22 (10:58:20.078):: ********************************************************* Line 23 (10:58:20.078):: ********************************************************* Line 24 (10:58:20.078):: * TEST 3 REPORT: Load Structure Queue, last command tracking Line 25 (10:58:20.078):: * Most recent 5 runs: Line 26 (10:58:20.078):: * 1: 206ms Line 27 (10:58:20.078):: * 2: 241ms Line 28 (10:58:20.078):: * 3: 238ms Line 29 (10:58:20.078):: * 4: 207ms Line 30 (10:58:20.078):: * 5: 204ms Line 31 (10:58:20.078):: *---------------------------------------------------------- Line 32 (10:58:20.078):: * Average run time: 218ms - over 5 tests Line 33 (10:58:20.078):: ********************************************************* Line 34 (10:58:20.078):: ********************************************************* Line 35 (10:58:20.078):: * TEST 4 REPORT: Send String Queue Line 36 (10:58:20.078):: * Most recent 5 runs: Line 37 (10:58:20.078):: * 1: 3232ms Line 38 (10:58:20.078):: * 2: 3299ms Line 39 (10:58:20.078):: * 3: 3687ms Line 40 (10:58:20.093):: * 4: 3234ms Line 41 (10:58:20.093):: * 5: 3232ms Line 42 (10:58:20.093):: *---------------------------------------------------------- Line 43 (10:58:20.093):: * Average run time: 3336ms - over 5 tests Line 44 (10:58:20.093):: ********************************************************* Line 45 (10:58:20.093):: ********************************************************* Line 46 (10:58:20.093):: * TEST 5 REPORT: Send Structure Queue, no tracking Line 47 (10:58:20.093):: * Most recent 5 runs: Line 48 (10:58:20.093):: * 1: 257ms Line 49 (10:58:20.093):: * 2: 304ms Line 50 (10:58:20.093):: * 3: 269ms Line 51 (10:58:20.093):: * 4: 256ms Line 52 (10:58:20.093):: * 5: 257ms Line 53 (10:58:20.093):: *---------------------------------------------------------- Line 54 (10:58:20.093):: * Average run time: 268ms - over 5 tests Line 55 (10:58:20.093):: ********************************************************* Line 56 (10:58:20.093):: ********************************************************* Line 57 (10:58:20.093):: * TEST 6 REPORT: Send Structure Queue, last command tracking Line 58 (10:58:20.109):: * Most recent 5 runs: Line 59 (10:58:20.109):: * 1: 233ms Line 60 (10:58:20.109):: * 2: 253ms Line 61 (10:58:20.109):: * 3: 232ms Line 62 (10:58:20.109):: * 4: 233ms Line 63 (10:58:20.109):: * 5: 234ms Line 64 (10:58:20.109):: *---------------------------------------------------------- Line 65 (10:58:20.109):: * Average run time: 236ms - over 5 tests Line 66 (10:58:20.109):: *********************************************************
The two queues I use are storing the data in a structure (or it could be an array depending on needs) and using two pointers one that indicates insertion point and one the indicates current command to send. You can see that using pointers is WAY faster than string concatenation.
I also found one other problem with the queue provided by AMX. They have a limit of 50000 chars for queuing, but netlinx concatenation falls apart at 16000 chars. If you are using 200 character commands (think to a touch panel more than to a 232 device), then you will hit the problem area at around 79 commands being queued. Be aware of this limitation when using the queue they provide.
Jeff
0
Comments
Plus loading the queue is usually event related, push a button or a string_event and you load a command into queue and possibly a follow up query. So how fast does queue loading really need to be?
The easiest way to get around that is what I believe Spire_Jeff was getting at. Rather than continuously shifting the queue upwards so that it is always firing out the command thats sitting in index 1 on the array use two pointers. One which tracking the next available position to fill and one which point to the next position to fire out. That way you can create something along the lines of:
Then use a timeline to fire out the commands at an interval the device likes. When a command is fired out increment the out pointer and if it is the same as the in pointer set them both back to 1.
Jeff
P.S.
As I look at this code, I am thinking that reversing the if and else code and dropping the ! could make the code more efficient gotta change it now.
I guess "find_string" is a time consuming process when you think about it and remove string possibly even longer.
Not sure if I'm ready to convert but it is an interested concept for queueing which I hadn't considered. Changing the pointer for song lists yes but for a send_command or send_string queue I never would have thunk of it.
I have gone both ways on this. In the situation that queue was written for, I started by throwing errors, but I recently switched to overwrite. It doesn't happen often, but when there is a little lag, it makes more sense to overwrite old messages. It also does not make sense to dump the queue when the device drops offline for a second or two (IP device).
Jeff
possible to show more of your code, definition and Time_Line? Thanks much!
I generally use a combination of the timed based cueing and feedback. If you set up your timeline which fires the commands out to do so at the interval specified as the minimum consecutive command gap (if your lucky it may be in the protocol, otherwise 40ms seems to be about right for a lot of devices) but combine it with an ACK timeout (say 1 second). That way each iteration of the timeline will fire of the next command in the cue if the previous command has been an acknowledged, otherwise it will skip it and wait until the next timeline event until the timeout has been reached. If the ACK times out it will try and send the command again up to 3 times, after 3 connsecutive time outs it will bin the command and flag an error - either in RMS if applicable or just send it to 0:0:0 so I can see it as I debug. That way you're verifying all commands for devices that give you nice feedback (for the ones that don't you can always set up some seperate polling logic and make sure its always in the state your code is expecting it to be so no one can play with those damn hardware buttons), and making sure your system doesn't completely lock up in the event of something going wrong. Additionally it will always be firing out the commands as fast as the device can accept them.
Jeff
Here's one that I use - similar to Jeff's. It uses two functions, One to queue the command, the second to send the command. It also uses a timeline, array, and a structure. This one's not so fancy though, as I don't wait for ACK responses from the device. It could be modified to test for ACK and resend if an error message is received, but this has worked well enough for me. One caveat is that if you fire enough commands at it to wrap the array around so that the queuing position passes the sending position, you can cause problems. One advantage of this method though is that it's simple to send commands to multiple devices and the Queue keeps track of which msg goes to which device.
--John
Jeff - couple of questions.
nCurrent_command is the command that just was sent correct?
nCurrent_Queue is the location of the last command in the Q?
You have one Q for all the devices in the system?
Do you run into situations where your volume control "stutters" because a command for another device falls into the Q between volume ramping commands? (say you are monitoring the projector(s) power status every 5 seconds and the user just happens to be changing the volume during that time.)
I know that I tend to use separate Q's for each device, As well as triggering the next command from the ACK or NAK from the device. (I too will throw out error codes for NAKs and try up to 5 times for each command - mostly for the RMS apps), so one Q for all the devices with a set timeline makes me a little nervous.
I think it would be more useful if there is a way to insert 10 miliseconds into the outgoing buffer *after* the command has been sent so that the next command is being executed properly by the device. This will save implementing any ques, I think!?
But I think the command listed above will just insert spacing between each character sent. good for slowing down a serial data stream, butnot for queing commands going to the device. I think.
Reading this line makes me think it may work like JohnMichnr describe and insert a delay between all subsequent characters.
Hmmm, which way does it actually work? If it's the 1st method then it could be usefull to delay individual commands similar to queue timing except you're using the output buffer as the queue and you would then have to send this delay to the buffer after every command to insert this delay. I don't really see a reason for the second 2nd interpretation since if you have to slow down every character it should be running at a lower baud unless there's some funky timing requirements.
I don't use one central queue for all the devices on the system but I think you're right, using one queue for a lot of devices could potentially hurt you on real-time stuff like volume control, PTZ, etc.
--John
If we're all guessing, I think it's the first one, it puts in a one-time delay. I think the example is if you send that naked string, and immediately send a second send_command, there will be a 10 ms delay before transmitting characters.
e.g.
I'll try to test it this weekend if no one posts a definitive answer.
--John
Here's the code I was using if anyone else wants to fiddle about. I just open the debug window and change the variables to see what happens and like I said I don't see it doing anything reliably so I wouldn't use this at all for anything. Maybe it's me but....
Code used: FYI, I used a crossover cable from port1 to port 2.
I tried about every combination of string vs commands, before the string and after the string, a couple strings vs 10 strings, 10 milliseconds vs various milliseconds and nothing I did gave anything near the result I was hoping for.
Oh well.
This is a que that I use frequently. It doesn't use a lot of overhead and you can set the time between commands by adjusting the wait time at the bottom. It works great for me;