Skip to main content
gamedev.net
🔒 Locked

Fast string comparison

Started by cpp forever 10 replies 1.8k views
Original Post
cpp forever
cpp forever
I have two 4-byte strings (char[]). Is it sufficient to compare them like this:

*(__int32 *)str1 == *(__int32 *)str2

ai-blog.org: AI is discussed here.
twanvl
twanvl
Yes, but only if they are exactly four bytes (or three bytes and a terminating \0). Also note that __int32 is not portable.
This seems like something that is bound to introduce bugs in your code, the speed up is probably not worth it. Don't use this unless you really have to; and the profiler indicates that it indeed speeds your program up significantly.
MaulingMonkey
MaulingMonkey
That'll die on some 64 bit processors if the char arrays are not aligned to a 4 byte boundry AFAIK, not to mention the fact that it's really really brittle. Just use strcmp. The difference between this ugly hack and strcmp will be minimal in terms of preformance, or if it isn't, you'll get far greater benifit from upgrading your compiler to one that WILL have minimal difference between the two.

If they're supposed to be numbers, store them as numbers in the first place. If they're supposed to be strings, then this micro-micro-micro optimization of yours is at best pointless, at worst going to cause you hours on end of debugging (more time than will ever be saved total by the micro-micro-micro optimization) as you try and figure out why your code crashes, having long frogotten you limited your code to 4 byte strings.
Tramboi
Tramboi
It is not portable (you should write it with reinterpret_cast's if you're doing C++).

But it IS a valuable optimization, and if it proves efficient, no reason not to use it. Don't listen to correctness integrists! Listen to us performance integrists! ;)

Of course you should do this :
bool result = *reinterpret_cast<__int32*>(str1) == *reinterpret_cast<__int32*>(str2);assert (result == !strcmp (str1, str2));
iMalc
iMalc
Quote:
Original post by Tramboi
It is not portable (you should write it with reinterpret_cast's if you're doing C++).

But it IS a valuable optimization, and if it proves efficient, no reason not to use it. Don't listen to correctness integrists! Listen to us performance integrists! ;)

Of course you should do this :
*** Source Snippet Removed ***
If he really wants to get away with that then he should add four more asserts on top of that...
assert (strlen(str1) == 3);assert (strlen(str2) == 3);assert (sizeof(str1[0]) == 1);assert (sizeof(str2[0]) == 1);*reinterpret_cast<__int32*>(str1) == *reinterpret_cast<__int32*>(str2);assert (result == !strcmp (str1, str2));
The first two ensure that he never does try and use it for strings that are not of length 3, and the other two ensures that they are not unicode strings.
Anon Mike
Anon Mike
The asserts do exactly diddly in a release build of his program. If either string comes from user input, file io, etc, etc then he's asking for trouble. That is of course in addition to the trouble of getting possible crashes on non-x86 cpus, possible crashes on x86 if one of the strings happens to lie at the end of a committed memory region and the string is shorter than 3 characters, etc, etc. Basically unless you tightly control every facet of how these strings are allocated then you shouldn't do it and you probably shouldn't do it anyway.

There are a couple ways to speed up strings comparisons assuming you've used a profiler to show that the perf of strings comparisons actually means anything. The first is to change your algorithm to not do so many comparisons in the first place. Another is to generate hashs for your strings and use the hash to quickly eliminate non-matches. Another is to google for "efficient string comparison" and check out the tons of articles.
-Mike
MaulingMonkey
MaulingMonkey
If your program is completely algorithmically optimized (it's not) and this string comparison really is a bottleneck (it won't be), then my advice would be to create an entire wrapper class around the whole shebang:

class four_byte_string {    char data[4]; //optionally replace with a forcibly aligned typepublic:    four_byte_string() {       std::fill( data , data + 4 , '\0' );    }    four_byte_string( const four_byte_string & other ) {       std::copy( other.data , other.data + 4 , data );    }    four_byte_string( const char * string ) {       size_t string_length = std::strlen( string );       if ( string_length > 4 ) throw std::runtime_error( "four_byte_string: more than 4 bytes foo!!!" );       std::copy( string , string + string_length , data );       std::fill( data + string_length , data + 4 , '\0' );    }    four_byte_string( const std::string & string ) {        size_t string_length = string.size();        if ( string_length > 4 ) throw std::runtime_error( "four_byte_string: more than 4 bytes foo!!!" );        std::copy( string.begin() , string.end() , data );        std::fill( data + string_length , data + 4 , '\0' );    }    four_byte_string & operator=( const four_byte_string & other ) {        std::copy( other.data , other.data + 4 , data );        return *this;    }    friend bool operator==( const four_byte_string & lhs , const four_byte_string & rhs ) {        return std::equal( lhs.data , lhs.data + 4 , rhs.data );    }    friend bool operator!=( const four_byte_string & lhs , const four_byte_string & rhs ) {        return !(lhs == rhs);    }    friend bool operator<( const four_byte_string & lhs , const four_byte_string & rhs ) {        return std::lexicographical_compare( lhs.data , lhs.data + 4 , rhs.data , rhs.data + 4 );    }};

Tramboi
Tramboi
Quote:
Original post by Anon Mike
The asserts do exactly diddly in a release build of his program. If either string comes from user input, file io, etc, etc then he's asking for trouble. That is of course in addition to the trouble of getting possible crashes on non-x86 cpus, possible crashes on x86 if one of the strings happens to lie at the end of a committed memory region and the string is shorter than 3 characters, etc, etc. Basically unless you tightly control every facet of how these strings are allocated then you shouldn't do it and you probably shouldn't do it anyway.

I did specify it wasn't portable, didn't I?
If you don't have guarantees about your data, just test and throw an exception, and be a bit slower! Or just demote the test to an upper level where you can make a formal proof...
Do you check your inputs in every routine in release? I don't!
I don't send rockets to the moon, I just program videogames :)


Quote:

There are a couple ways to speed up strings comparisons assuming you've used a profiler to show that the perf of strings comparisons actually means anything. The first is to change your algorithm to not do so many comparisons in the first place. Another is to generate hashs for your strings and use the hash to quickly eliminate non-matches. Another is to google for "efficient string comparison" and check out the tons of articles.


Why do you *by default* assume people have not profiled and are not optimizing the good stuff in their code?
I get a little tired about seeing always the same stuff about profiling, code safety, algorithms before micro-optimizations and so on...
Yes, it is nice to remind what is obvious, but...
...do you really want to use a standard string hash function for zero-terminated 3 chars strings (or const-4 bytes array) in a performance intensive part of your code?
(This kind of strings are often used to generate readable 32 bits RTTI ids)
I wouldn't if I had correct guarantee about the input data. Which you *can* get if you take proper care.

So let's avoid the generic "Profile first, optimize if they point a gun at you", and let's discuss the specifics of the post :)

(Btw, this is not intended to be a flame!)
Tramboi
Tramboi
Quote:
If he really wants to get away with that then he should add four more asserts on top of that...
The first two ensure that he never does try and use it for strings that are not of length 3, and the other two ensures that they are not unicode strings.


You're totally right!
Extrarius
Extrarius
Quote:
Original post by Tramboi
[...]Why do you *by default* assume people have not profiled and are not optimizing the good stuff in their code?[...]
It's the default assumption because it's the common case. People that are educated about optimization seem to just do it rather than ask here about possibilities. Also, most of the optimization books that get around seem to be older ones (ie mostly 486 or prior, rarely pentium 1) that cover micro-optimizations because at the time the book was written they weren't a bad idea.
"Walk not the trodden path, for it has borne it's burden." -John, Flying Monk
MaulingMonkey
MaulingMonkey
Quote:
Original post by Tramboi
Why do you *by default* assume people have not profiled and are not optimizing the good stuff in their code?


Because 99.9% of posters here on GameDev have not.
That, and the 0.1% that have explicitly state this in their post because they know they're posting in a forum frequented by the optimizingly-challanged.
Not to put down the optimizingly-challanged - we all are or were at one point.

Quote:
So let's avoid the generic "Profile first, optimize if they point a gun at you",


No, let's not avoid them. People don't read FAQs, and as such these generic answers are very important in educating those who are new to this aspect of programming about how to do optimizing right.

I don't think these answers are even in the GD FAQ, although I could be wrong.
Tramboi
Tramboi
Ok, got your (valid) point!

(Just hope you got a macro to paste this disclaimer ;) )

Topic Locked

This topic has been locked by a moderator. New replies are not allowed.

Sign in to reply to this topic.