I had an argument with an employee about implementing a simple line parser. One of them is “small”, 10 lines of code, using C ++ and threads, the other 70 lines of code, using switch enclosures and an iteration string char to char. We tested it for more than 1 million iterations and measured the speed using a time command. It seems like a long and ugly approach is on average 1 second faster.
Problem: Input: string
"v=spf1 mx include:_spf-a.microsoft.com include:_spf-b.microsoft.com include:_spf-c.microsoft.com include:_spf-ssg-a.microsoft.com ip4:131.107.115.212 ip4:131.107.115.215 ip4:131.107.115.214 ip4:205.248.106.64 ip4:205.248.106.30 ip4:205.248.106.32 ~all a:1.2.3.4"
Output: map<string, list<string>> with all values for each key, for example: ip4, include,
An example of the output of one iteration on the input line above:
key:
1.2.3.4,
key: enable
_spf-a.microsoft.com, _spf-b.microsoft.com, _spf-c.microsoft.com, _spf-ssg-a.microsoft.com,
key: ip4
131.107.115.212, 131.107.115.215, 131.107.115.214, 205.248.106.64, 205.248.106.30, 205.248.106.32,
The guy is "little handsome":
istringstream iss(input); map<string, list<string> > data; string item; string key; string value; size_t pos; while (iss.good()) { iss >> item; pos = item.find(":"); key = item.substr(0,pos); data[key].push_back(item.substr(pos+1)); }
Second faster approach:
typedef enum {I,Include,IP,A,Other} State; State state = Other; string line = input; string value; map<string, list<string> > data; bool end = false; size_t pos = 0; while (pos < line.length()) { switch (state) { case Other: value.clear(); switch (line[pos]) { case 'i': state = I; break; case 'a': state = A; break; default: while(line[pos]!=' ' && pos < line.length()) pos++; } pos++; break; case I: switch (line[pos]) { case 'p': state = IP; break; case 'n': state = Include; break; } pos++; break; case IP: pos+=2; for (;line[pos]!=' ' && pos<line.length(); pos++) { value+=line[pos]; } data["ip4"].push_back(value); state = Other; pos++; break; case Include: pos+=6; for (;line[pos]!=' ' && pos<line.length(); pos++) { value+=line[pos]; } data["include"].push_back(value); state = Other; pos++; break; case A: if (line[pos]==' ') data["a"].push_back("a"); else { pos++; for (;line[pos]!=' ' && pos<line.length(); pos++) { value+=line[pos]; } } data["a"].push_back(value); state = Other; pos++; break; } }
I really believe that “little is beautiful” is the way to go and I don't like the longer code presented here, but it's hard for me to argue about it when the code runs faster.
Can you suggest ways to optimize or completely rewrite a small approach, in a sense where it remains small and beautiful, but also works faster?
Update: Added state determination and initialization. Context: a longer approach completes 1 million iterations on the same line in 15.2 seconds, the less code does this in an average of 16.5 seconds.
both versions compiled with g ++ -O3, g ++ - 4.4, worked on Intel (R) Core (TM) 2 Duo CPU E8200 @ 2.66 GHz, Linux Mint 10
The good side won this battle :) I found a small mistake in a small program, it added even invalid values to the map, those that did not have a colon :: in the line. After adding an if statement to check for a colon, the smaller code is faster, much faster. Now the timings: “small and beautiful”: 12.3 and long and ugly: 15.2.
Little beautiful :)