I need help regarding a code I want to write in c++.

I want to develop a program that receives and visualizes CAN messages on a GUI. The messages in question are about 100 distinct ID's, so we're not talking about a large CAN network. I already have "Message" class that encapsulates the message data into signals.

I want to create a data structure (like an array) which contains all the message classes, and when I receive a new message from the CAN network I want to access the class with the matching ID to parse the content of the payload into it.

I currently have two options regarding the data structure containing the messages:

  • unordered map
  • a normal ordered array

On a normal ordered array I would need to search each message with a logarithmic search which would take about 6/7 comparisons, given the fact that I have around 100 distinct ID's. On the other hand, the unordered map needs to calculate the IDs hash function in order to return the correct Message index.

The question is: is it faster to calculate the hash function or to perform a logarithmic search on the array?

NOTE: data structure is constant and message id's are uint32_t

14 Replies 14

For something as small as a few hundred elements I doubt it will make any actual difference one way or the other. But benchmark both options with an optimized build if you want to know for sure.

Your question is too general. I'd try to implement both versions and write some benchmarks. 100 elements is quite small, so a cache should speed up simple array solution. Also hash maps have branches which make things slower. So I would not be surprised if the array solution with binary search is faster.

If performance is critical, you can implement your own hash map with a constant size with a hash function which will give you uniqueness for message ids, so you can jump directly to desired element just based on the hash.

Both choices are typically way (orders of magnitude) quicker than CAN network transmits the messages so it probably has no effect to overall performance.

Otherwise hash is usually noticeable quicker compared to several comparisons of IDs. For example if ID is integral number then hash calculation is typically like ID % bucket_count (library typically keeps latter as prime number). On the other hand sorted array is usually smaller than unordered_set so memory performance of it is better. I personally would take sorted array or vector.

I'd use whatever method is simplest to write. Peronally I'd use a map, because the code is straigthforward. Keep your code simple.

If performance is an issue you can still optimize your code later. But if your code is running on a desktop computer, it certainly doesn't matter.

Just generate a giant switch from all your classes ordered by most likely frequent messages, or a combination of that and a map based design. How many messages are you expecting to parse?

This data structure is constant? Then we don't need to worry about the cost of inserting into std::vector. Lookup time is small in either case, and the array uses less memory and less indirection, so I'd probably go with that. But as Jabberwocky (almost) says, the most readable choice is best unless there's a proven need to do something more complex. You'll be thanked by the maintainer (especially if that's Future You).

Using switch is a good option if the IDs are integers - give the problem to the compiler to solve, instead of worrying it yourself.

100 elements probably won't start making a dent in the responsiveness of your program even if you use dumb linear search. I would recommend starting with just that. Only if you see unacceptably slow response, go ahead and replace it with a binary search. OTOH if you only have standard (not extended) message IDs, you can use them directly as indices in your array since there are only 512 standard IDs. This will be faster than literally anything else.

There are 2048 possible standard IDs (11 bit).

Do you actually have a proven performance issue with map (which would be easier to use).

Myself, I likely wouldn't bother with the map-overhead, and just go with a sorted constexpr std::array and a binary search.

Both are quite inefficient. Binary search is good for large array, but not small ones: branch miss-prediction is expensive (AFAIK typically ~7 cycles on a modern x86-64 CPU in average for a binary search searching random values -- so 40-50 cycles here overall).
std::unordered_map is often not very efficient because of its common separate chaining implementation. Open addressing is generally faster for this use case, especially if the cache is cold. The hash function can also be optimised to be trivial if IDs are rather randomly distributed. Highly-optimized hash maps should be at least 2-3 times faster than mainstream std::unordered_map implementations. This option is the most maintainable solution while being pretty fast (only few ns on a recent mainstream x86-64 CPU when the cache is hot). If you store the IDs in a separate array, you can iterate very quickly in it. This index array will take only about 8 x 64-bytes cache lines. Iterating over the array is super fast with SIMD instructions. Modern x86-64 CPU can compare a full 64-byte cache line in about 1 cycle. In average, only 4 cache lines should be checked. You can very quickly get the index from a comparison mask. The biggest overhead is the final branch miss-prediction of the loop. The overall check should not take <20 cycles on such CPUs. A branchless code should be faster (especially on CPUs supporting wide SIMD instruction set like Zen5 or Intel P-core server-side CPUs). At this scale, the question is then what matters the most: fetching latency or throughput? I strongly advise you to start with std::unordered_map, profile your code, and replace it with a highly-optimized hash-map implementation if it is too slow. If it is still not enough, then try other solutions and benchmark them directly in your application.

If you only have 2048 message IDs then you can build a table of 2048 function pointers and use direct lookup by index. Can't get any faster than that.

Rule of thumb: do the simplest thing that will get the job done. The array is easy to build, easy to use, and easy to prove correct. So do that first. At least then you have a working system. If it turns out to be too slow, then worry about how to optimize it.

Your Reply

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

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.