4

I'm working on a high performance application where all calls must be justified. I have a map that is used once in the beginning of each transaction to do a lookup that I would like to improve upon. The map is loaded at startup and does not change after that.

The key in the map below is an std::string but it can it changed to a char array if needed. C or C++ as a solution is fine.

  typedef stdext::hash_map<std:string, int> symbols_t;

Does anyone know of any other solutions that would eliminate the lookup or be faster?

Thanks ahead of time for your help.

Additional info from edits:
1. The hash_map currently has 350,000 elements.
2. Each key value is typically between 4 and 10 characters in length.
3. Information is received on a callback from a third party API. The callback is given a symbol that is used as the key value when doing a the map lookup. The rest of the software is driven off of the int returned from the map lookup.

THANKS: Thanks all for your input. You've given me a few avenues to explore. I will definitely try these out. I appreciate the help.

skimobear
  • 1,188
  • 10
  • 12
  • 2
    I highly doubt that the overall performance will be drastically different if you replace `std::string` with `char*`. However, this would for sure make the code much less maintainable. – ereOn Sep 22 '10 at 11:59
  • 3
    A hash map is O(1), so the lookup time depends only on the time needed to calculate the hash. Have you looked into that? – sbi Sep 22 '10 at 12:19
  • 1
    I wonder, is this the biggest bottleneck in your code? Smells premature optimization. – Yakov Galka Sep 22 '10 at 12:29
  • @ybungalobill - I'm reviewing the rest of the code as well prior to presenting the solution to the team I'm part of. thx – skimobear Sep 22 '10 at 12:32
  • @skimobear: is part of your presentation going to mention how much CPU time is actually being spent on the map *right now*? – jalf Sep 22 '10 at 14:15
  • @jalf - cpu time is typically looked as a whole and then we go to the next level and look for low hanging fruit (i.e. container usage, object constructor/destruction, temp objects, stuff to simply/streamline, etc). – skimobear Sep 22 '10 at 14:45
  • 3
    @skimobear: How do you propose to find the low-hanging fruit *if you don't know where your CPU time is being spent*? This is optimization 101. Don't just guess, and don't just go through your entire codebase trying to optimize everything blindly: **Find out where optimization is needed and beneficial**, and then optimize that. If the map only ever takes 0.01% of your application's combined execution time, then optimizing it is a complete and utter waste of time. – jalf Sep 22 '10 at 15:14
  • @jalf - Thx for the suggestion. I'm certainly familiar with your suggestion, the 80-20 rule, quantifying, purifying, etc. I follow these guidelines as well but I also feel that it cant hurt to look to improve all areas. It's not an unusual for a developer to test each line of code. Why not apply the same principle to performance. The caveat being the size of the project. I may be working on smaller projects then others are which may be the reason why I can afford to take the time to do this. Thank you for the suggestion. – skimobear Sep 22 '10 at 16:04
  • 2
    @skimobear: you feel wrong. ;) Unless you've got infinite time for optimizing, then every second you spend optimizing code that isn't performance-critical is a second *less* that you're able to spend where it matters. So the net effect is that you're slowing your code *down* by optimizing where it has no measurable effect. – jalf Sep 23 '10 at 08:59

7 Answers7

2

Is this map completely constant or changes between program invocations? For constant hashes (known at compile time) there is gperf program, which generates fast and guaranteed O(1) lookup table.

Also, it could help to understand you problem if you tell us why and how exactly map lookups slow down your code.

blaze
  • 4,326
  • 18
  • 23
  • The contents of the hash_map changes on a daily basis. It is pulled out of a database each morning. That sounds interesting, I'll take a look :) – skimobear Sep 22 '10 at 13:06
  • gperf generates C++ source files that are hardcoded with your data. Use gperf to create a dynamic library from your database that you unload and load each morning. – Lou Franco Sep 22 '10 at 14:51
2

A hash table is usually fast enough O(1) and we cannot tell you if you can get rid of the hash table without knowing the whole structure of your application. It may not be possible.

I don't know how is implemented stdext::hash_map<std::string,T> , but a prefix tree is a possibly better solution. It is equivalent to a hash table with a perfect hash function.

      s
      |
      t
    /   \
   o     a
   |     |
(p,42)   r
         |
       (t,69)

It will give you the value corresponding to your string in O(1) maximum 10 iterations (max length of the string) and will minimize the space cost of storing keys.

log0
  • 10,489
  • 4
  • 28
  • 62
1

I would say we lack of information here to reliably tell you what to do.

You may want to be more specific about what the lookup is for and the overall algorithmic cost of your functions.

If you clutter the code with ugly hacks to win 1 constant microsecond in a function whose algorithmic cost is O(n²) where it could be O(n), you're wasting your time on the wrong problem.

Without additional details, we can't really tell.

ereOn
  • 53,676
  • 39
  • 161
  • 238
1

Hand-code a hash-map that is more tuned to your data.

  1. simplistic hash function that is good enough
  2. use a sparse C-array that is big enough to not have collisions for your data
  3. make sure all calls are inlined
  4. Make sure you never copy or convert strings
  5. Write code to generate the C-source for this C array. It's going to look like (using 0 for no entry):

    int symbols[] = { 0,0,0,0,0,0,5,0,0,0,0,0,3,0,0,0,0,0,0,2 /* etc */ };
    

    The code you write can search for a hash function where there are no collisions for your data. Perhaps it's something as simple as the first two-chars of the symbol (or first 4) as an int. If you don't care about space, you don't need to make a perfect hash for all possible data, just a fast one that's perfect for the data you have.

The array index is simple_hash(string& s)

Remember that if you change the symbols, you may have to rewrite the hash and certainly need to regenerate the table.

EDIT: based on @blaze's answer -- the code in #5 is written for you and is called gperf

Lou Franco
  • 87,846
  • 14
  • 132
  • 192
1

If you really do need a hash_map keyed on strings, then you could try customizing the hash function. If your strings are mostly unique in (say) the first four characters, then write a custom hash function that only looks at up to the first four characters in a string, and make the hash_map use that. Here's an example:

struct CustomStringHash: std::unary_function<std::string, size_t>
{
    size_t operator()(const std::string & s) const
    {
         switch (s.size())
         {
              case 0:
                   return 0;
              case 1:
                   return s[0] + 1;
              case 2:
                   return (s[0] << 8) + s[1];
              default: //3 or more chars long, plus a terminating null
                   return *reinterpret_cast<const uint32_t *>(s.c_str());
         }
    }

If your strings are 8-12 characters on average, and mostly unique in the first four characters, then customizing the hash function could speed up lookups quite significantly.

Doug
  • 8,780
  • 2
  • 27
  • 37
1

How can we advise you how to eliminate your lookup since you don't tell us what you look up or why? We'd need far more algorithmic detail.

As for performance, whether or not to use a hash_map depends on some of the complexity. Hashmaps have (if you have a good implementation, realistically) O(1) lookup, insertion. But the constant overhead can be quite high. If you have low numbers of entries you could suffer here and might benefit from a std::map. You could also suffer from cache coherence problems if many different elements of the map are accessed frequently and could consider some sort of sorted array instead.

Puppy
  • 144,682
  • 38
  • 256
  • 465
1

Here is an article about performance of the hash_map, where a drop-in replacement is presented that should perform much better:

http://www.codeproject.com/KB/cross-platform/BenchmarkCppVsDotNet.aspx

Here is a list of more performance tests:

http://attractivechaos.wordpress.com/2008/10/07/another-look-at-my-old-benchmark/ http://attractivechaos.wordpress.com/2008/08/28/comparison-of-hash-table-libraries/
http://tinodidriksen.com/2009/10/04/cpp-map-speeds-msvc-edition/

Experienced that std_ext::hash_map performed poorly when more than 25.000 elements, where lookups became slower as the number of elements increased. Changing to boost::unordered_map fixed the problem.

Rolf Kristensen
  • 17,785
  • 1
  • 51
  • 70