libplctag/libplctag.NET

Performance Issue - AutoRead

VitorKawao opened this issue · 9 comments

Hello every one,

Sometime ago I create this topic: #324

I was having some issues with my client computer.

So I get a plc to test better on my computer.

I noticed a loss of performance with many tags, using auto read.The timeout that I initially set was enough (safe) for fewer tags. But when I try to initialize much more tags, suddenly the timeout is not enough anymore. The time that the api takes to initialize a tag is constantly increasing. So there is a time that it took more than my timeout, then the tag is not initialized.
(I could add more time at the timeout, but it took much time to initialize 1800 tags, about 5 minutes).
Is there any way that I could do it faster?
Below my code and test cenarious

Plc Info
Type: 1769-L35E CompactLogix5335E Controller
Revision: 20.19
System Overhead Time Slice: 45%

My program

List<Tag> myTags = new List<Tag>();
private void InitTags()
{
	Stopwatch sw = new Stopwatch();
	sw.Start();
	foreach (string tag in tags)
	{
		var myTag = new Tag
		{
			Name = tag,
			Gateway = "192.168.1.151",
			Path = "1,0",
			PlcType = PlcType.ControlLogix,
			Protocol = Protocol.ab_eip,
			ElementSize = 1,
			ElementCount = 1,
			AutoSyncReadInterval = TimeSpan.FromMilliseconds(1000),
			AutoSyncWriteInterval = TimeSpan.FromMilliseconds(100),
			Timeout = TimeSpan.FromMilliseconds(30000),
			AllowPacking = true
		};
		myTag.ReadCompleted += MyTag_ReadCompleted;
		myTag.WriteCompleted += MyTag_WriteCompleted;
		Stopwatch s = new Stopwatch();
		s.Start();
		try
		{
			myTag.Initialize();
			myTags.Add(myTag);
			s.Stop();
			Console.WriteLine($"Initialized {myTag.Name} in {s.ElapsedMilliseconds}ms - {DateTime.Now}.{DateTime.Now.Millisecond}");
		}
		catch (Exception e)
		{
			Console.WriteLine($"Exception in {myTag.Name} - {e.Message}");
		}
	}
	sw.Stop();
	Console.WriteLine($"Initialized {tags.Count} in {sw.ElapsedMilliseconds}ms");
}
private void MyTag_WriteCompleted(object sender, TagEventArgs e)
{
	
}

private void MyTag_ReadCompleted(object sender, TagEventArgs e)
{
	if (sender is Tag tag)
	{
		Console.WriteLine($"ReadComplete {tag.Name}: {DateTime.Now}.{DateTime.Now.Millisecond}");
	}
}

I am using 1780 tags.

1º Test (logs at file1)
log1.txt

I used 1000 at AutoSyncReadInterval .

It took 264702 ms to initialize all tags.
First item: 109ms to initialize
Second Item: 4ms to initialize
Last item: 1178ms to initialize

The first tag is slow than it goes fast but the time is increasing after some time

2º Test (logs at file2)
log2.txt

I used 2000 at AutoSyncReadInterval .
It is not super fast but it was better

It took 17519 ms to initialize all tags.
First Item: 86ms to initialize
Second Item: 3ms to initialize
Last item: 9ms to initialize

The first tag is slower than it goes fast.

3º Test (logs at file3 and file4)
log3.txt
log4.txt

If I did not save the Tag in memory, I was keeping it at a List, it goes fast but not all tags enter at ReadCompleted event.

//List<Tag> myTags = new List<Tag>();
//myTags.Add(myTag);

I used 1000 at AutoSyncReadInterval

It took 12893 ms to initialize all tags.
First Item: 94ms to initialize
Second Item: 4ms to initialize
Last item: 3ms to initialize

But as you can see at file4, not all tags enter at ReadCompleted event,
Some tags enter but not all.
I do not need to keep the Tag, but I need that all tags enter at ReadCompleted.

Best Regards,
Vítor Guedes

Have you reconsidered using the async APIs? It will allow you to perform initialization of multiple tags concurrently.
For example:

var myTags = tagNames.Select(tagName=> new Tag()
    {
        Name = tagName,
        Gateway = "192.168.1.151",
        ....
    }).ToList();

await Task.WhenAll(myTags.Select(tag => tag.InitializeAsync());

Have you reconsidered using the async APIs? It will allow you to perform initialization of multiple tags concurrently.

InitializeAsync is faster than Initialize but when I init the tags than Finalize them with dispose, and try to Init again there is a crash sometimes

An unhandled exception of type 'System.ExecutionEngineException' occurred in Unknown Module.

Description: The application requested process termination through System.Environment.FailFast(string message).
Message: A callback was made on a garbage collected delegate of type 'libplctag.NativeImport!libplctag.NativeImport.plctag+callback_func_ex::Invoke'.
Stack:

There should be no reason to use an object after it has been Disposed, and there are checks on basically every method/property to ensure it does throw an exception. This is true for all types that implement IDisposable in the .NET ecosystem, not just libplctag.NET.

Is there any other way to stop auto read for a while?

I don't think it would work. You could destroy the tag handle and remake it. However, I think this is probably a feature that should work. All that said, I have not actually tried setting the auto timeouts to zero while the tag handle is open.

@kyle-github - what are you referring to when you say "this is probably a feature that should work"? If you're referring to the ability to temporarily suspend autoread - then I agree that this would be useful.

Without analysing the code or running it, it superficially appears that the core library would support this: https://github.com/libplctag/libplctag/blob/release/src/lib/lib.c#L333
But the .NET library bans setting tag parameters after initialization: https://github.com/libplctag/libplctag.NET/blob/master/src/libplctag/NativeTagWrapper.cs#L657
I will set up a new issue to discuss the ability to change tag parameters after initialization.

If you were instead referring to be able to use a tag handle after calling plc_tag_destroy on it, that might be OK in the C library, but I don't think we'd make use of that ability in the .NET library unless there was a compelling reason.

Once the tag handle is destroyed, you cannot get it back. They are 32-bit handles, so after 2 billion or so creates and deletes, you will possibly get the same ID. If I had the ability to do the API over again, I would have used 64-bit handles.

Hi @VitorKawao - the only action I can see out of this thread is to be able to temporarily suspend AutoRead. This is being tracked in #337 so I will close this issue.

I understand that on your project you're moving away from AutoSync anyway - best of luck!

Ok, thank you