快速字符串对象比较
2013 年 2 月 16 日
我首先想的是把 lua 字符串对象 TString 从一个连续结构体改造成包含一个指针的间接引用。也就是说 TString 里不再直接包含字符串数据,而引用另一块数据块。如果首次比较两个 TString ,我们可以先比较内部的数据指针,不同的话再进一步比较数据内容。如果数据内容相同,就把两个内部指针合并成同一个。下次比较就可以通过比较指针完成。
但是,在多线程环境下,简单的引用计数却很难管理好这个内部数据块指针的生命期。我想了很久的无锁算法,感觉无解。可如果加锁,损失的性能或许更大,甚至大于直接比较短字符串的内存(毕竟最多才 40 字节)。
今天,我突然想到另一个巧妙地方法,非常值得一提。
我们可以给每个 TString 对象增加一个 64bit id 。一般情况下,这个 id 都是 0 。当我们需要把一个字符串共享出去时,则用简单的原子自增的方式分配一个唯一 id 给它。由于 id 有 64bit 所以不需要考虑 id 用完的情况。
当我们比较两个 TString 时,先比较 id 是否相同。如果 id 不同,再用 memcmp 做全量比较,如果最终结果相同,我们就把 id 较小的那个 TString 的 id 修改成较大的那个。
如果 id 相同,则可以认为两个 TString 的值相同,不需要进一步做 memcmp。
比如,在 A B 两个虚拟机中,都有字符串 “foo” ,它们都把自己共享出去。一开始 A 中的 foo id 为 1 ,B 中的 foo id 为 2 。
在 C 这个虚拟机中,一开始也有字符串 foo ,id 为 0 。当我们第一次将 C 本地的 foo 和 A 引入的 foo 比较后,C 的 foo 的 id 会修改为 1 。再次比较就不再需要 memcmp 。如果其后和 B 引入的 foo 比较,本地的 foo 的 id 会变成 2 ,再次遇到 A 的 foo ,则会把 A 的 id 也提升为 2 。
即使 A B 共享的 foo 在多线程下,被不同的虚拟机共享,也不需要加锁,因为 id 永远是向上提升,最终都会稳定下来。而且这个 id 仅仅是作为一个比较的参考而已。
通常这个方法,值相同的对象,在经过第一次比较后,以后的每次比较都可以通过一个 id 比较得到结果;值不同的对象,它们的 hash 值也几乎不可能相同,所以再后续的 hash 值比较中也能得到结果。
但是,在多线程环境下,简单的引用计数却很难管理好这个内部数据块指针的生命期。我想了很久的无锁算法,感觉无解。可如果加锁,损失的性能或许更大,甚至大于直接比较短字符串的内存(毕竟最多才 40 字节)。
今天,我突然想到另一个巧妙地方法,非常值得一提。
我们可以给每个 TString 对象增加一个 64bit id 。一般情况下,这个 id 都是 0 。当我们需要把一个字符串共享出去时,则用简单的原子自增的方式分配一个唯一 id 给它。由于 id 有 64bit 所以不需要考虑 id 用完的情况。
当我们比较两个 TString 时,先比较 id 是否相同。如果 id 不同,再用 memcmp 做全量比较,如果最终结果相同,我们就把 id 较小的那个 TString 的 id 修改成较大的那个。
如果 id 相同,则可以认为两个 TString 的值相同,不需要进一步做 memcmp。
比如,在 A B 两个虚拟机中,都有字符串 “foo” ,它们都把自己共享出去。一开始 A 中的 foo id 为 1 ,B 中的 foo id 为 2 。
在 C 这个虚拟机中,一开始也有字符串 foo ,id 为 0 。当我们第一次将 C 本地的 foo 和 A 引入的 foo 比较后,C 的 foo 的 id 会修改为 1 。再次比较就不再需要 memcmp 。如果其后和 B 引入的 foo 比较,本地的 foo 的 id 会变成 2 ,再次遇到 A 的 foo ,则会把 A 的 id 也提升为 2 。
即使 A B 共享的 foo 在多线程下,被不同的虚拟机共享,也不需要加锁,因为 id 永远是向上提升,最终都会稳定下来。而且这个 id 仅仅是作为一个比较的参考而已。
通常这个方法,值相同的对象,在经过第一次比较后,以后的每次比较都可以通过一个 id 比较得到结果;值不同的对象,它们的 hash 值也几乎不可能相同,所以再后续的 hash 值比较中也能得到结果。