blog:2018:1227_string_id

Action disabled: register

String IDs in C++

Lately, while reading the book Game Engine Architecture (2018) from Jason Gregory, I discovered the concept of String IDs which could be used when we need very high performances (for instance when building games!):

The idea is simple: comparing and creating strings in C++ is expensive, so, this should be avoided at runtime, whereas comparing integers is really efficient. But for human readability, we cannot just get rid of strings obviously :-). So we keep using them while writing code, but using the recent user-defined string literals and constexpr features from C++, we convert those strings into integer at compile time. Nice, isn't it ?!

In the book from Jason Gregory, they used CRC32 and then CRC64 to build string hashes. But for my part I'm just going to experiment with another hashing algorithm: FNV-1a, which seems pretty famous for this kind of usage. I found an simple implementation of that algorithm at this link: https://notes.underscorediscovery.com/constexpr-fnv1a/

So, I've written a simple code snippet to add support for string IDs generation:

constexpr U32 val_32_const = 0x811c9dc5;
constexpr U32 prime_32_const = 0x1000193;
constexpr U64 val_64_const = 0xcbf29ce484222325;
constexpr U64 prime_64_const = 0x100000001b3;

inline constexpr U32 hash_32_fnv1a_const(const char* const str, const U32 value = val_32_const) noexcept {
	return (str[0] == '\0') ? value : hash_32_fnv1a_const(&str[1], (value ^ U32(str[0])) * prime_32_const);
}

inline constexpr U64 hash_64_fnv1a_const(const char* const str, const U64 value = val_64_const) noexcept {
	return (str[0] == '\0') ? value : hash_64_fnv1a_const(&str[1], (value ^ U64(str[0])) * prime_64_const);
}

// Implementation of StringID mechanism:

// cf. https://en.cppreference.com/w/cpp/language/user_literal
inline constexpr StringID operator "" _sid(const char* str, std::size_t n) noexcept {
    return hash_64_fnv1a_const(str);
}

inline StringID strId(const char* str, std::size_t n) noexcept {
	return hash_64_fnv1a(str, n);
}

inline StringID strId(const char* str) noexcept {
	return hash_64_fnv1a(str, strlen(str));
}

inline StringID strId(const std::string& str) noexcept {
	return hash_64_fnv1a(str.data(), str.size());
}

inline constexpr StringID strId_const(const char* str) noexcept {
	return hash_64_fnv1a_const(str);
}

And from there we can just define a simple macro to generate our string IDs when required:

#define SID(str) nv::strId_const(str)

And that's it! I also added a unit test to ensure the behavior is correct, and there was no surprise there, so we are all good.

  • blog/2018/1227_string_id.txt
  • Last modified: 2020/07/10 12:11
  • by 127.0.0.1