Limitations on the STL std::map and Finding Keys

September 28th, 2010

I've been working with the STL std::map for quite a while, but recently I use it as the core of a data structure where I wasn't finding an exact match - I was looking for a "lower bound" on the key to see where I needed to start looking for a match to the data. It's probably easier to start from the beginning. The data I'm dealing with is a std::map where the key is a uint32_t and the value is a boost::tuple of a uint32_t, another uint32_t, and a std::string. Like this:

  typedef boost::tuple<uint32_t, uint32_t, std::string> Channel;
  typedef std::map<uint32_t, Channel> ChannelMap;

Where the data looks something like this:

Key Value
0x00000000 0x00000000, 0x01ffffff, "first"
0x02000000 0x02000000, 0x02ffffff, "second"
0x03000000 0x03000000, 0x03ffffff, "third"
0x04000000 0x04000000, 0x04ffffff, "fourth"

where the 'key' is the first value of the tuple, and the two numeric values in the tuple form an arithmetic range for a uint32_t. What I need to do is to take an arbitrary uint32_t value and find the string that it fits with. If it's less then the least range there's nothing, and if it's greater than the last, it's nothing.

The problem was that I was using STL's lower_bound() and assuming that it was going to give me what I thought was the "lower bound" of the value in the keyspace. But it doesn't. What lower_bound() returns is:

Finds the first element whose key is not less than the argument

Simply put, it find the key whose value is greater than or equal to the argument. So this is almost the "upper bound" in my book. But there's more.

The upper_bound() method returns:

Finds the first element whose key greater than the argument

which is no better. What I wanted was something like: the largest key less than or equal to the argument. Put that way, I'm not really surprised that I didn't find it. So how to I make it out of the methods I have?

What I needed to do was to look at values of both of these functions and try to make some sense of it. So I made the following test code:

  #include <iostream>
  #include <string>
  #include <map>
  #include <stdint.h>
 
  int main(int argc, char *argv[]) {
    std::map<uint32_t, uint32_t>   m;
    for (uint32_t i = 10; i <= 100; i += 10) {
      m[i] = i + 1;
    }
    std::map<uint32_t, uint32_t>::iterator    it;
    // print out the entire map
    for (it = m.begin(); it != m.end(); ++it) {
      std::cout << "m[" << it->first << "] = " << it->second << std::endl;
    }
    // now check the lower_bound and upper_bound methods
    it = m.lower_bound(45);
    std::cout << "lower_bound(45): " << it->first << " == " << it->second << std::endl;
    it = m.upper_bound(45);
    std::cout << "upper_bound(45): " << it->first << " == " << it->second << std::endl;
 
    it = m.lower_bound(50);
    std::cout << "lower_bound(50): " << it->first << " == " << it->second << std::endl;
    it = m.upper_bound(50);
    std::cout << "upper_bound(50): " << it->first << " == " << it->second << std::endl;
 
    it = m.upper_bound(45);
    --it;
    std::cout << "--upper_bound(45): " << it->first << " == " << it->second << std::endl;
    it = m.upper_bound(50);
    --it;
    std::cout << "--upper_bound(50): " << it->first << " == " << it->second << std::endl;
 
    return 0;
  }

which returns:

  m[10] = 11
  m[20] = 21
  m[30] = 31
  m[40] = 41
  m[50] = 51
  m[60] = 61
  m[70] = 71
  m[80] = 81
  m[90] = 91
  m[100] = 101
  lower_bound(45): 50 == 51
  upper_bound(45): 50 == 51
  lower_bound(50): 50 == 51
  upper_bound(50): 60 == 61

From this, it seems like the two really aren't all that different - and they aren't. What's important to see, though, is that really the definition of greatest less than or equal to is something like one less than the one just greater. With that, I tried using the upper_bound() and then backing off one:

  #include <iostream>
  #include <string>
  #include <map>
  #include <stdint.h>
 
  int main(int argc, char *argv[]) {
    std::map<uint32_t, uint32_t>   m;
    for (uint32_t i = 10; i <= 100; i += 10) {
      m[i] = i + 1;
    }
    std::map<uint32_t, uint32_t>::iterator    it;
    // print out the entire map
    for (it = m.begin(); it != m.end(); ++it) {
      std::cout << "m[" << it->first << "] = " << it->second << std::endl;
    }
    // now check the lower_bound and upper_bound methods
    it = m.lower_bound(45);
    std::cout << "lower_bound(45): " << it->first << " == " << it->second << std::endl;
    it = m.upper_bound(45);
    std::cout << "upper_bound(45): " << it->first << " == " << it->second << std::endl;
 
    it = m.lower_bound(50);
    std::cout << "lower_bound(50): " << it->first << " == " << it->second << std::endl;
    it = m.upper_bound(50);
    std::cout << "upper_bound(50): " << it->first << " == " << it->second << std::endl;
 
    it = m.upper_bound(45);
    --it;
    std::cout << "--upper_bound(45): " << it->first << " == " << it->second << std::endl;
    it = m.upper_bound(50);
    --it;
    std::cout << "--upper_bound(50): " << it->first << " == " << it->second << std::endl;
 
    return 0;
  }

which returns:

  m[10] = 11
  m[20] = 21
  m[30] = 31
  m[40] = 41
  m[50] = 51
  m[60] = 61
  m[70] = 71
  m[80] = 81
  m[90] = 91
  m[100] = 101
  lower_bound(45): 50 == 51
  upper_bound(45): 50 == 51
  lower_bound(50): 50 == 51
  upper_bound(50): 60 == 61
  --upper_bound(45): 40 == 41
  --upper_bound(50): 50 == 51

If I was careful and checked for the limits, I think I'd have something. So that's exactly what I did.

My final code for finding the string in the tuple looks something like this:

  const std::string ZMQChannelMapper::getURL( const MessageMapCode aCode )
  {
    std::string     url;
 
    if (!mChannelMap.empty()) {
      ChannelMap::iterator  itr;
      if (mChannelMap.size() == 1) {
        // if there's only one... try it - we might get lucky
        itr = mChannelMap.begin();
      } else {
        // find the high-end of the enclosing range in the table
        itr = mChannelMap.upper_bound(aCode);
        // if it's not at the ends, back off one to the start
        if ((itr != mChannelMap.begin()) && (itr != mChannelMap.end())) {
          --itr;
        }
      }
      // from this starting point, check the range for a match
      if (itr != mChannelMap.end()) {
        Channel & tupleInfo = (*itr).second;
        if ((tupleInfo.get<0>() <= aCode) &&
            (aCode <= tupleInfo.get<1>())) {
          url.append(getBaseURL());
          url.append(tupleInfo.get<2>());
        }
      }
    }
 
    // all done - return what we have
    return url;
  }

It works, and it's OK, but it's clear why they didn't make something like this in STL - far too specialized. But I have it now.

Fantastic Advice for All Developers – Probably Everyone

September 28th, 2010

I found this on a tweet I got this morning and I love this guy's approach to using standards. In fact, I think it can be applied to a far greater range of things than standards. It's probably really good advice for life:

  1. If it hurts when you do it, stop doing it.
  2. Shut up and eat your vegetables.
  3. Assume people have common sense.

Golden Advice, people. Can't get much better than this.

If it hurts, then stop doing it. Figure out a better way. Realize that the pain is telling you something. Don't be a lemming. Find a better solution, a better way. This is the beginning line of virtually every great success story. Just do it.

Shut up and eat your vegetables. Realize there's no free lunch, and you're going to have to do the work. You're going to have to pound the keys. Practice. Effort. That's the only thing that's really going to pay off in the end. Just pipe down and get to it.

Assume people have common sense. If not, don't worry about them - they have far bigger problems than you're going to be able to solve.

Sage advice indeed. Love it.

Finished up the Cache Data Service for Ticker Plant

September 27th, 2010

Ringmaster

Today was a great day for making progress on publishing the cache through the data service. In fact, I got it all done. It's pretty slick and should fit into the other data services of the broker nicely.

I create a subclass of the MMDServiceHandler which is spawned off of the MMDService for each call to bind() that the MMDService receives. It's a classic controller/worker breakdown where I've put a lot of the smarts of the workers in the abstract base class - MMDServiceHandler, and then create subclasses for the 'basic' data handler and the 'cache' handler. I'd tackled the first one earlier, so today it was time to hit the latter.

The cache is a lockless single-producer, multiple-consumer cache of the last tick message the feed has produced for that message type and a particular conflation key to make sure we keep "unique" values, but not duplicates of those "unique" values. It's standard stuff for a ticker plant, the point is that we need to scan the cache - no two ways about it.

So I created the different query schemes and then implemented them - realizing that we don't have to worry as much about performance here as this is going through the Broker, and because of that it's meant for the slower data consumers. In that our big concern is not to slow down the feed by locking anything. Good enough.

I found out that I needed to return the data in two ways: objects and a map of ivar names/values for the cross-platform crowd. To accomplish this, I had to add a getMapData() method to all the messages and build it up from the bottom. It just took a little time, but in the end it's a solid way of allowing easier access to the Java and Python clients as they all have maps and I don't have to worry about making real objects of the messages. (Note: I am making them in Java, but it's nice not to have to mess with the Python client)

The last thing I had to do was to glue this service into the Ticker Plant such that the feed exposed it's cache so the service could bind() it to the Broker. Not bad at all, and very slick. Really nice day today.

Running Some Initial Tests on Ticker Plant

September 27th, 2010

Today I needed to run some tests on my ticker plant code to get a decent scale for ordering hardware and network taps. The new machines are going to need double 10Gb ethernet cards - one for the incoming feeds from the exchanges and the other for the outgoing packets. Right now I don't split it up like that, but I know I'll need to in the real UAT testing. But today I just wanted to get as close to "real" as possible - given that I don't have a lot of the supporting data sources I'm going to need before UAT.

The big missing data source is the mapping of the exchange symbol to "security ID" - an internal unsigned integer that is generated in the database and used for all references to an instrument. I'm expecting that it'll be a simple data service where I'll open up a subscription channel to the data service and issue calls to map the symbols to security IDs. I have the code to map these (in both directions) so it's only necessary to get this data once, but I need a source of this data.

Well... not really. For these tests all I need is a unique ID for these guys. So let's make them up. Easy. I'll implement the "lookup" method on the class to generate a uuid_t, which is a random 128-bit number, and use the first 64-bits as the "security ID". I'll pass this back to the mapping method and it'll think it's real. For the sake of these tests, it's good enough as we just need to have these in order to check on the conflation of the data stream.

When I fired it up running on 1/24th of the OPRA feed it used under 10% of one CPU. A full-tilt feed using less than 10%! You gotta be kidding! I watched it for a while and if you toss out the CPU usage of the terminal that is streaming the log data, it's well under 10%. From time to time it spikes to 40% - not quite sure what that's all about, but it's not even 1% of the time.

If we assume we can get 4 channels on one CPU, and factor up the memory, it looks like we can get the complete OPRA feed on one 8 CPU box with 32 GB RAM. If we add another for all the remaining feeds, which is a good estimate, we're fitting the complete ticker plant into two small boxes. Pretty wild.

Past wild... that's better than I'd have ever believed. Sweet.

Acorn 2.5.1 is Out

September 27th, 2010

I got a tweet today saying that Acorn 2.5.1 was out with a nice list of fixes and additions. Very nice. Still my preference over Elements and Gimp. Simple, powerful, clean. Great Mac software.

Lots of Progress on Conversational Data Service

September 24th, 2010

Ringmaster

Today I got a lot of good work done on the conversational data service. I decided to go with an instance variable approach to integration. Basically, I looked at the inheritance scheme but in the end I wasn't really happy with the way that was going to work. It wasn't easy to handle multiple services... it was going to require more code to be written by the user... it was going to be difficult to work it into the exchange feed's cache... lots of little things. But if I think of it as an instance variable to a class, then it's almost a client to the MMD that allows me to "publish" data to it. It might even be possible to make a single, unified client for the MMD - that which can request and provide. Might be interesting.

Anyway... today was a lot of good progress on the design and getting the headers written. Lots to do, but at least I have a nice, clear path now.

Google Chrome dev 7.0.517.13 is Out

September 24th, 2010

I was updating the configuration of WP Super Cache on my WordPress installs at HostMonster this afternoon and noticed that Google Chrome was saying there was an update - I said 'yes', and saw that it's up to 7.0.517.13. OK... nothing in the release notes on this, maybe I'm just a little early.

As for the "Don't be Evil"... the problem is that Chrome is a really good browser. It's a few in the management that I have issues with. So that's the rationalization I'm going with. Yup. It'll work.

Adding Ant, JUnit to Ticker Plant Project

September 24th, 2010

I had to take a little time-out this afternoon to install Ant and JUnit on the development machines we're using so that the Team can start working on the Java side of things. I have to say that while I'm not a huge fan of Ant, it's adequate, and the rules that people are building are pretty impressive. Of course, there's very little that you can't do in GNU make, as I've learned, but I can see that the Java/XML generation doesn't want to have to learn what I've learned in order to have a decent, stable build system for Java. So be it. It's not horrible.

JUnit is in the same boat, in my book. It's adequate, and yet everything it does can be done by your own test frameworks, it's just that they already have this one, and in that it makes sense to use it.

It took me a little time to get things all set up and configured properly - about 30 mins. Yeah, that's about as fast as I've ever done it. It helped to have used both a lot, and to know how to lay out the project to get it all working easily. Once I had it all in I just needed to run a few tests and check it all in.

Now they can get started on the Java client.

Working on the Simple Data Service

September 23rd, 2010

Ringmaster

The first data service I wanted to create was the simplest as well - something that could take a simple C++ variant and expose it to the Broker under a provided service name for query and update. There are two basic ways the Broker's clients interact with the data services: 'call' and 'subscribe'. The first is a "give it to me" idea, and the second is "give it to me and keep it up to date" plan. At least as far as the simple data service goes.

What I wanted to build was a very simple API for the service so that a user would be able to simply provide the variable and a service name and the rest of the "magic" would be done by the base service class. It's a nice idea - one application could then "publish" a bunch of values and services by simply gathering the data and publishing it with one of these "one-line" calls. Sweet.

Well... today was that day. It's the easiest form of the service as it's a single variant and I just need to be able to send it back to the caller and then track updates. Thankfully, I've talked a lot with the designer of the broker and know just what I want to do for tracking the changes. It's going to be a very simple transaction system where the user will have to start a transaction on the object, make changes, and then commit the changes and when that happens, the changes will be sent to all the registered listeners for that variant.

One of these registered listeners will, of course, be the service so that when the changes are sent, it can package them up and send them to all the subscribers. Pretty simple.

There were a lot of little things that needed to be worked out - especially with the transactions. But in the end, I have something that's working just fine for the simple case.

Tomorrow: Start on the conversational mode.

Flash Player 10.1.85.3 is Out

September 23rd, 2010

I'm not a big fan of Flash, but I've had to develop in it, and I know that every little bit helps, so when I saw this morning that they had an upgrade, and it's even the hardware accelerated version that should decode H.264 in hardware, but that's not a lot of what I do, so maybe it's not all that great to get excited about it. Still... it's at least fixing security holes... So I got it.