3
\$\begingroup\$

I am writing a CAN bus logger application and need to format messages as quick as possible in order to prevent buffer overflows on our device. In some cases I am quick enough, but under heavy loads it is still pretty slow. I believe the bulk of the problem happens in my formatting block (I am using TPL to separate the tasks as much as possible). Here is my consumer block which takes a PassThruMsg[] blocking collection, formats those messages, and passes them into a new blocking collection where they are later printed to a file:

private void FormatMessages(BlockingCollection<PassThruMsg[]> messages, BlockingCollection<string[]> formattedMessages)
{
    try
    {
        PassThruMsg msg;
        String[] formatted;
        foreach (var item in messages.GetConsumingEnumerable(_cancellationTokenSource.Token))
        {
            formatted = new string[item.Length];
            for (uint i = 0; i < item.Length; i++)
            {
                msg = item[i];

                if (_firstTime == 0) _firstTime = msg.timestamp;

                formatted[i] = ((msg.timestamp - _firstTime) / 1000000.0).ToString("0.000000 ");

                // Assumed CAN Protocol
                //TODO:
                string canID = BitConverter.ToString(msg.data, 0, 4).Replace("-", "").TrimStart('0');
                string data = msg.dataSize > 4 ? BitConverter.ToString(msg.data, 4, (int)msg.dataSize - 4).Replace("-", " ") : String.Empty;
                formatted[i] += canID + " " + data;
            }
            formattedMessages.Add(formatted);
        }
    }
    catch (OperationCanceledException)
    {
        //TODO:
    }
}

If it is relevant, here is the snippet where I pass into the BlockingCollection

uint numMsgs;
while (!_cancellationTokenSource.IsCancellationRequested)
{
    numMsgs = (uint)msgs.Length;
    status = j2534.PassThruReadMsgs(channelID, msgs, ref numMsgs, 1000);
    PassThruMsg[] copiedMessages = new PassThruMsg[numMsgs];
    Array.Copy(msgs, copiedMessages, numMsgs);
    if (status == Status.ERR_BUFFER_OVERFLOW)
    {
        _fileWriter.WriteLine("DEVICE INDICATED BUFFER OVERFLOW - MESSAGES LOST!");
        logMessages.Add(copiedMessages);
    }
    else if (status == Status.STATUS_NOERROR || status == Status.ERR_TIMEOUT)
    {
        logMessages.Add(copiedMessages);
    }
    else if (status == Status.ERR_BUFFER_EMPTY)
    {
        throw new Exception(String.Format("PassThruReadMsgs Failed(0x{0:X})", status));
    }
}

And the last node:

private void LogMessages(BlockingCollection<string[]> messages)
{
    try
    {
        foreach (var item in messages.GetConsumingEnumerable(_cancellationTokenSource.Token))
        {
            if (item.Length > 0)
            {
                _fileWriter.WriteLine(String.Join(Environment.NewLine, item));
                _fileWriter.Flush();
            }
        }
    }
    catch (OperationCanceledException)
    {
        //TODO:
    }
}

I have looked this over several times and have been working on it for quite a while, but cannot find any places where this is bottle necking in a way that I can fix. I am just hoping a second set of eyes to review this will see performance issues that I don't.

\$\endgroup\$
1

1 Answer 1

2
\$\begingroup\$

I would start with extracting the message formatting logic to a separate method. It will make profiling and performance testing easier.

foreach (var item in messages.GetConsumingEnumerable(_cancellationTokenSource.Token))
{
    formatted = FormatSingleMessage(item);
    formattedMessages.Add(formatted);
}

Then you can check the FormatSingleMessage method independently to see if the problem is really there.

When you format the messages, you perform several operations on strings. Strings are immutable, so each operation creates a new string on the heap. All these strings have to be garbage collected, which is another expensive operation.

Try to use a StringBuilder instead and compare performance of both approaches. Depending on the size of the messages, you might see a big improvement.

\$\endgroup\$
3
  • \$\begingroup\$ I'll try this out, as well as string builder. Any tips on how I can run a preformance benchmark on this? Because I'm plugging into an ECU and running it, I don't have a list of test messages to format, just whatever I read from the vehicle at the time. :/ Maybe I will have to pull a list of messages from one run and use them in the future for bench marks if I still have no luck. \$\endgroup\$
    – AdamMc331
    Commented Oct 2, 2015 at 19:06
  • \$\begingroup\$ @McAdam331 Definitely try to get some real data for profiling. And identify the bottlenecks before optimizing. The solution I proposed isn't really a performance optimization - it's just a .Net best practice when working with strings. Real performance optimization will probably make your code less readable and harder to maintain. Make sure it's really needed before you do it. \$\endgroup\$ Commented Oct 2, 2015 at 19:42
  • \$\begingroup\$ It is really needed. I will try to get real data, I'm just not sure how to determine at which node in the TPL dataflow it's bottlenecking at. \$\endgroup\$
    – AdamMc331
    Commented Oct 2, 2015 at 19:46

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.