转载请注明出处为KlayGE游戏引擎,本文的永久链接为http://www.klayge.org/?p=647

老早就想写这篇了,但一直没空,现在抽个空写写吧。

Motivation

编译期字符串Hash的想法源于一个常见做法,字符串比较。比如在KlayGE的effect系统中,有很多if (“cull_mode” == state_name)这样的比较。加速这个操作的方法之一就是hash。但是如果改成if (0x8F1E5E5F == state_name_hash)又非常不直观。如果能有一个编译期的hash方法,先称之为CT_HASH,就可以if(CT_HASH(“cull_mode”) == state_name_hash),既保证直观,又保证开发效率。

尝试

要在编译期做事情首先都会想到template,但是C++的template没法提供字符串的操作,就算boost::mpl::string,也必须把一个一个字符分开塞给它才行,就像这样:

boost::mpl::string<‘c’, ‘u’, ‘l’, ‘l’, ‘_’, ‘m’, ‘o’, ‘d’, ‘e’>

仍然不直观,所以template这条路走不通。

剩下的就是宏了。

inline size_t HASH_FUNCTION(size_t seed, char ch)
{
return seed ^ (ch + 0x9e3779b9 + (seed << 6) + (seed >> 2));
}

#define HASH_RECURSE_00(seed, str) (*(str + 1) == 0 ? HASH_FUNCTION((seed), *(str)) : HASH_RECURSE_01(HASH_FUNCTION((seed), *(str)), (str + 1)))
#define HASH_RECURSE_01(seed, str) (*(str + 1) == 0 ? HASH_FUNCTION((seed), *(str)) : HASH_RECURSE_02(HASH_FUNCTION((seed), *(str)), (str + 1)))
#define HASH_RECURSE_02(seed, str) (*(str + 1) == 0 ? HASH_FUNCTION((seed), *(str)) : HASH_RECURSE_03(HASH_FUNCTION((seed), *(str)), (str + 1)))
#define HASH_RECURSE_03(seed, str) (*(str + 1) == 0 ? HASH_FUNCTION((seed), *(str)) : HASH_RECURSE_04(HASH_FUNCTION((seed), *(str)), (str + 1)))
#define HASH_RECURSE_04(seed, str) (*(str + 1) == 0 ? HASH_FUNCTION((seed), *(str)) : HASH_RECURSE_05(HASH_FUNCTION((seed), *(str)), (str + 1)))
#define HASH_RECURSE_05(seed, str) (*(str + 1) == 0 ? HASH_FUNCTION((seed), *(str)) : HASH_RECURSE_06(HASH_FUNCTION((seed), *(str)), (str + 1)))
#define HASH_RECURSE_06(seed, str) (*(str + 1) == 0 ? HASH_FUNCTION((seed), *(str)) : HASH_RECURSE_07(HASH_FUNCTION((seed), *(str)), (str + 1)))
#define HASH_RECURSE_07(seed, str) (*(str + 1) == 0 ? HASH_FUNCTION((seed), *(str)) : HASH_RECURSE_08(HASH_FUNCTION((seed), *(str)), (str + 1)))
#define HASH_RECURSE_08(seed, str) (*(str + 1) == 0 ? HASH_FUNCTION((seed), *(str)) : HASH_RECURSE_09(HASH_FUNCTION((seed), *(str)), (str + 1)))
#define HASH_RECURSE_09(seed, str) (*(str + 1) == 0 ? HASH_FUNCTION((seed), *(str)) : HASH_RECURSE_10(HASH_FUNCTION((seed), *(str)), (str + 1)))
#define HASH_RECURSE_10(seed, str) (*(str + 1) == 0 ? HASH_FUNCTION((seed), *(str)) : HASH_RECURSE_11(HASH_FUNCTION((seed), *(str)), (str + 1)))
#define HASH_RECURSE_11(seed, str) (*(str + 1) == 0 ? HASH_FUNCTION((seed), *(str)) : HASH_RECURSE_12(HASH_FUNCTION((seed), *(str)), (str + 1)))
#define HASH_RECURSE_12(seed, str) (*(str + 1) == 0 ? HASH_FUNCTION((seed), *(str)) : HASH_RECURSE_13(HASH_FUNCTION((seed), *(str)), (str + 1)))
#define HASH_RECURSE_13(seed, str) (*(str + 1) == 0 ? HASH_FUNCTION((seed), *(str)) : HASH_RECURSE_14(HASH_FUNCTION((seed), *(str)), (str + 1)))
#define HASH_RECURSE_14(seed, str) (*(str + 1) == 0 ? HASH_FUNCTION((seed), *(str)) : HASH_RECURSE_15(HASH_FUNCTION((seed), *(str)), (str + 1)))
#define HASH_RECURSE_15(seed, str) (*(str + 1) == 0 ? HASH_FUNCTION((seed), *(str)) : HASH_RECURSE_16(HASH_FUNCTION((seed), *(str)), (str + 1)))
#define HASH_RECURSE_16(seed, str) (*(str + 1) == 0 ? HASH_FUNCTION((seed), *(str)) : HASH_RECURSE_17(HASH_FUNCTION((seed), *(str)), (str + 1)))
#define HASH_RECURSE_17(seed, str) (*(str + 1) == 0 ? HASH_FUNCTION((seed), *(str)) : HASH_RECURSE_18(HASH_FUNCTION((seed), *(str)), (str + 1)))
#define HASH_RECURSE_18(seed, str) (*(str + 1) == 0 ? HASH_FUNCTION((seed), *(str)) : HASH_RECURSE_19(HASH_FUNCTION((seed), *(str)), (str + 1)))
#define HASH_RECURSE_19(seed, str) HASH_FUNCTION((seed), *(str))

#define CT_HASH(str) HASH_RECURSE_00(0, (str))

这里的HASH_FUNCTION来自于boost,其本身是否出现冲突的情况不属于本片讨论的范围。值得注意的是,如果HASH_FUNCTION用宏的方式来写,那么HASH_RECURSE到9个之后就会造成编译器heap溢出,必须写成函数的形式。

编译期?

这样的CT_HASH是编译期的吗?是也不是。在Release下,看汇编的结果可以发现

cout << CT_HASH(“cull_mode”) << endl;

被编译成了

00DC1103  mov         eax,dword ptr [__imp_std::endl (0DC2054h)]
00DC1108  mov         ecx,dword ptr [__imp_std::cout (0DC2058h)]
00DC110E  push        eax
00DC110F  push        8F1E5E5Fh
00DC1114  mov         dword ptr [ebp-18h],0Fh
00DC111B  mov         dword ptr [ebp-1Ch],ebx
00DC111E  mov         byte ptr [ebp-2Ch],bl
00DC1121  call        dword ptr [__imp_std::basic_ostream<char,std::char_traits<char> >::operator<< (0DC2048h)]
00DC1127  mov         ecx,eax
00DC1129  call        dword ptr [__imp_std::basic_ostream<char,std::char_traits<char> >::operator<< (0DC2044h)]

也就是说CT_HASH(“cull_mode”)被编译成了常量8F1E5E5Fh。看似成功了,但是对于另一个更严格的常量测试

switch (state_name_hash)
{
case CT_HASH(“cull_mode”):

}

却失败了。也就是说CT_HASH(“cull_mode”)其实并不是编译期常量,而是优化器产生的。

结论

想要做一个编译期字符串Hash的尝试HLL地失败了:(,只能做到“优化期”常量的效果。如果各位看客有何高见的话不妨讨论讨论!