Professional OPC
Development Tools

logos

Online Forums

Technical support is provided through Support Forums below. Anybody can view them; you need to Register/Login to our site (see links in upper right corner) in order to Post questions. You do not have to be a licensed user of our product.

Please read Rules for forum posts before reporting your issue or asking a question. OPC Labs team is actively monitoring the forums, and replies as soon as possible. Various technical information can also be found in our Knowledge Base. For your convenience, we have also assembled a Frequently Asked Questions page.

Do not use the Contact page for technical issues.

Slow read speed with C/COM client

More
03 Aug 2015 08:54 #3475 by support
If you indicate the file extension of the picture you inteded to attach, I can enable it on the forums; the usual extensions such as BMP, PNG, JPG etc. should already be allowed. You can also ZIP it or RAR it.

We can have a closer look at the "speed per byte payload" at some future point, however it is likely that the main contributing factor is our use of the .NET stack, which we cannot change.

Best regards

Please Log in or Create an account to join the conversation.

More
31 Jul 2015 10:19 #3471 by pfp.meijers
I have done some more testing with larger array sizes but it looks that the raw speed per byte payload is much lower than other implementations.
These are measurements from one PC using indicated client stacks to a another PC that runs an open62541 server.

(I tried to attach a picture with a graph showing the measurement results, but then I get an error page when submitting)

Please Log in or Create an account to join the conversation.

More
15 Jul 2015 17:50 #3441 by support
Thank you.

Yes, it is quite realistic that you get such results. There are two main reasons:

1) QuickOPC is actually based on the .NET stack, and has a COM layer on top. The other stacks you have tested are probably native C/C++. The .NET is slower.

2) We do some high-level stuff that others don't - the API is actually designed for implied correctness and usability, not for speed. To give you one typical example: You need to specify the full node id, including the namespace (as a string), in each call to EasyUAClient.ReadXXXX method. This makes it easy to write correct programs, because the namespace string is what is stable and should be persisted. Looking up the namespace index each time slows things down. However the other toolkits tend to work with namespace index, which is faster, however the index is internal to the low-level communication, and the server is free to change the namespace index of any namespace with each session. Consequently there are many incorrect clients out there that would misbehave if the server does this. And this is just one example.

The test you have used (with a single 4-byte variable) is actually the most harsh that can be made, and not very realistic. No matter which toolkit you use, you should combine as many nodes as possible into a single call. I believe that if you repeat the measurements with e.g. 1000 nodes in each call, the results will be better for QuickOPC (though probably still not as fast as native C/C++ toolkits).

Best regards

Please Log in or Create an account to join the conversation.

More
15 Jul 2015 10:02 - 15 Jul 2015 10:04 #3439 by pfp.meijers
These are some measurement results, using different OPC-UA stacks/APIs.
>open62541Client.exe
43092 transactions, 1.00002 seconds, 4 B/transaction, 0.172365 MB/s, transaction min/avg/max/stddev: 1.72427e-005/2.26865e-005/0.000447488/5.0568e-006 seconds
 
>uaClient.exe
20400 transactions, 1.00002 seconds, 4 B/transaction, 0.0815985 MB/s, transaction min/avg/max/stddev: 3.85907e-005/4.84989e-005/0.00148862/1.62604e-005 seconds
 
>qopcClient
2723 transactions, 1.00006 seconds, 4 B/transaction, 0.0108914 MB/s, transaction min/avg/max/stddev: 0.000229492/0.000366861/0.0061158/0.000284175 seconds

They all talk to the same server. Reading a single int32 variable value in a loop during 1 second. The transaction time statistics (durations of the do_ipc() call) are listed.
I added a first dummy transaction in the setup function to cancel out the initial overhead.

For a real application we indeed would use a monitoring approach (push, instead of pull). But for now I use this to measure communication speed of the stack.
Last edit: 15 Jul 2015 10:04 by pfp.meijers.

Please Log in or Create an account to join the conversation.

More
14 Jul 2015 15:58 #3436 by support
Fundamentally, there probably isn't anything wrong (except for note at the bottom about subscriptions, but you might be doing the reads this way for some good reason). Can you post the actual times that you have measured?

And, here are some factors that may influence the performance:

1. First time you read, a connection process must take places. Therefore the first operation can take considerably longer, and that would be normal.

2. How frequently do you call the operation (Read?). If you make a delay longer than a (configurable) "hold period" (default to couple of seconds), EasyUAClient would disconnect, and then the connection process (under #1 above) would have to take place again, making it slower.

3. The default read parameters (an object that can be passed to the ReadXXXX method) specify a max age of 1 second. This means that the server must obtain each value form the device if it does not have a "fresh enough" value, younger than 1 second. This, of course, can take time - this depends on the server, the protocol etc. The max age can be changed from your side.


Note that any periodic reads of the same tags over and over are not a good idea in OPC world, performance-wise. Such read loop is better replaced by a subscription.

Best regards

Please Log in or Create an account to join the conversation.

More
14 Jul 2015 07:55 #3434 by pfp.meijers
I have made a simple test application that measures performance of read transactions.
For some reason it is much slower than expected.
Maybe I am doing something fundamentally wrong?
See code below.

The setup_ipc() function is called once and next the do_ipc() function is called in a loop many times, measuring its duration. The ipc.h file contains global variables such as server, port, variable name, array size/count, etc. which are set by the application.
#include <stdio.h>
#include <tchar.h>
#include <sstream>
using namespace std;
 
#define _ATL_CSTRING_EXPLICIT_CONSTRUCTORS      // some CString constructors will be explicit
#include <atlbase.h>
#include <atlstr.h>
#include <atlsafe.h>
 
#import "libid:BED7F4EA-1A96-11D2-8F08-00A0C9A6186D"    // mscorlib
using namespace mscorlib;
 
#import "libid:ecf2e77d-3a90-4fb8-b0e2-f529f0cae9c9"    // OpcLabs.BaseLib
using namespace OpcLabs_BaseLib;
 
#import "libid:E900F89A-5ED5-4E71-8E2C-D006DA67975C"    // OpcLabs.BaseLibExtensions
using namespace OpcLabs_BaseLibExtensions;
 
#import "libid:3b7714a0-a5e9-4b81-b663-d6faa9030ba8"    // OpcLabs.EasyOpcUAInternal
using namespace OpcLabs_EasyOpcUAInternal;
 
#import "libid:E15CAAE0-617E-49C6-BB42-B521F9DF3983"    // OpcLabs.EasyOpcUA
using namespace OpcLabs_EasyOpcUA;
 
extern "C"
{
  #include "ipc.h"
}
 
 
static CComSafeArray<VARIANT> arguments(array_count);
static _EasyUAClientPtr       client;
 
extern void setup_ipc()
{
  CoInitializeEx(NULL, COINIT_MULTITHREADED);
  client = _EasyUAClientPtr(__uuidof(EasyUAClient));
 
  stringstream s;
  s << "opc.tcp://" << server_address << ":" << port_nr;
  string server_url(s.str());
 
  s.str("");
  s << "ns=" << namespace_index << ";s=";
  if(variable_name == NULL || variable_name[0] == '\0')
  { s << "var" << array_size;
  }
  else
  { s << variable_name;
  }
  string node_id(s.str());
 
  for (int i = 0; i < array_count; i++)
  { _UAReadArgumentsPtr readArg(_uuidof(UAReadArguments));
    readArg->EndpointDescriptor->UrlString = server_url.c_str();
    readArg->NodeDescriptor->NodeId->ExpandedText = node_id.c_str();
    arguments.SetAt(i, _variant_t((IDispatch*)readArg));
  }
}
 
extern void cleanup_ipc()
{
  CoUninitialize();
}
 
extern int do_ipc(int measuring)
{
  LPSAFEARRAY pArguments = arguments.Detach();
  CComSafeArray<VARIANT> results = client->ReadMultipleValues(&pArguments);
  arguments.Attach(pArguments);
 
  if(verbose)
  { for(int i = results.GetLowerBound(); i <= results.GetUpperBound(); i++)
    { _ValueResultPtr valueResult = results[i];
      _variant_t      variant     = valueResult->value;
      VARIANT*        pVariant    = &variant;
      if(array_size == 1)
      { int val = pVariant->lVal;
        printf("%d\n", val);
      }
      else
      { SAFEARRAY* pArray = (SAFEARRAY*)(pVariant->parray);
        int*       val    = (int*)(pArray->pvData);
        for(int j = 0; j < array_size; j++)
        { printf("%d ", val[j]);
        }
        printf("\n");
      }
    }
  }  
 
  return 1;
}

Please Log in or Create an account to join the conversation.

More
14 Jul 2015 07:50 - 15 Jul 2015 09:42 #3433 by pfp.meijers
<Content removed because it was duplicate of original message.>
Last edit: 15 Jul 2015 09:42 by pfp.meijers. Reason: Duplicate message

Please Log in or Create an account to join the conversation.

Moderators: support
Time to create page: 0.054 seconds