位掩码 int64 中的多个值

Bitmask multiple values in int64

我正在使用 https://github.com/coocood/freecache 来缓存数据库结果,但目前我需要在每次删除时转储更大的块,与目标删除相比,这会额外花费数微秒。 fmt.Sprintf("%d_%d_%d") 对于像 #SUBJECT_#ID1_#ID2 这样的模式也会花费数微秒。尽管这听起来并不多,但以缓存响应时间的当前比率来看,它比现在慢很多。

我正在考虑使用库的 SetInt/GetInt,它使用 int64 键而不是字符串。

假设我正在以 #SUBJECT_#ID1_#ID2 模式存储。 Subject 是我代码中的 table 或查询段范围(e.a。一切都与 ACL 或 Productfiltering 有关)。

举个例子,Userright.id#ID1User.id#ID2主题ACL .我会像这样构建它:

// const CACHE_SUBJECT_ACL = 0x1
// var userrightID int64 = 0x1
// var userID int64 = 0x1
var storeKey int64 = 0x1000000101

fmt.Println("Range: ", storeKey&0xff)
fmt.Println("ID1  : ", storeKey&0xfffffff00-0xff)
fmt.Println("ID2  : ", storeKey&0x1fffffff00000000-0xfffffffff)

如何将 CACHE_SUBJECT_ACL/userrightID/userID 编译成 storeKey

我知道我可以调用 userrightID 0x100000001,但它是一个动态值,所以我不确定编译它的最佳方法是什么而不导致比将字符串格式化为键更多的开销.

我的想法是,在稍后的状态下,当我需要刷新缓存时,我可以调用一小部分 int64 调用,而不是仅仅转储整个分区(可能有数千个条目)。

我正在考虑通过移位将它们彼此相加,例如 userID<<8,但我不确定这是否是安全的途径。

如果我没有提供足够的信息,请询问。

好像想通了:

const CacheSubjectACL = 1
var userrightID int64 = 8
var userID int64 = 2
storeKey := CacheSubjectACL + (userrightID << 8) + (userID << 36)

fmt.Println("storeKey: ", storeKey)
fmt.Println("Range   : ", storeKey&0xff)
fmt.Println("ID1     : ", storeKey&0xfffffff00>>8)
fmt.Println("ID2     : ", storeKey&0x1ffffff000000000>>36)

给出:

 storeKey:  137438955521
 Range   :  1
 ID1     :  8
 ID2     :  2

storeKey 构建 int64 掩码。并且以另一种方式进行新转变的掩蔽再次将旧值从 int64 中剔除。

因为storeKey&0x1ffffff000000000>>36无论如何都会跑到最后,storeKey>>36也足够了,因为更左边没有位。

将数字打包到 int64

如果我们可以确保我们要打包的数字不是负数并且它们适合我们为它们保留的位范围,那么是的,这是一种安全有效的打包方式。

一个int64有64位,这就是我们可以分配给我们想要打包的部分的位数。通常不使用符号位以避免混淆,或者使用无符号版本 uint64

例如,如果我们为 subject 保留 8 位,则剩余 64-8=56 位,每个 ID 28 位。

                   | ID2         | ID1         |SUB|
Encoded key bits:  |f f f f f f f|f f f f f f f|f f|

请注意,在编码时,建议还使用带按位 AND 的位掩码,以确保我们打包的数字不重叠(有争议,因为如果组件更大,我们无论如何都会搞砸......)。

另请注意,如果我们还使用符号位 (63th),我们必须在解码时在移位后应用掩码,如右移 "brings in" 符号位而不是 0(在负数的情况下符号位为 1)。

由于我们为 ID1 和 ID2 使用了 28 位,我们可以为两个 ID 使用相同的掩码:

使用这些简短的实用函数来完成工作:

const (
    maskSubj = 0xff
    maskId   = 0xfffffff
)

func encode(subj, id1, id2 int64) int64 {
    return subj&maskSubj | (id1&maskId)<<8 | (id2&maskId)<<36
}

func decode(key int64) (sub, id1, id2 int64) {
    return key & maskSubj, (key >> 8) & maskId, (key >> 36) & maskId
}

正在测试:

key := encode(0x01, 0x02, 0x04)
fmt.Printf("%016x\n", key)
fmt.Println(decode(key))

输出(在 Go Playground 上尝试):

0000004000000201
1 2 4

坚持string

最初您尝试打包到 int64 中,因为 fmt.Sprintf() 很慢。请注意,Sprintf() 使用格式 string,并且根据格式字符串中布置的 "rules" 解析格式字符串和格式化参数需要时间。

但对于您的情况,我们不需要这个。我们可以像这样简单地得到你最初想要的:

id2, id1, subj := 0x04, 0x02, 0x01
key := fmt.Sprint(id2, "_", id1, "_", subj)
fmt.Println(key)

输出:

4_2_1

这个速度会快很多,因为它不需要处理格式字符串,它只会连接参数。

我们甚至可以做得更好;如果彼此相邻的 2 个参数中的 none 是 string 值,则会自动插入 space,因此仅列出数字就足够了:

key = fmt.Sprint(id2, id1, subj)
fmt.Println(key)

输出:

4 2 1

Go Playground 上试试这些。

利用fmt.AppendInt()

我们可以使用 fmt.AppendInt() 进一步改进它。此函数将整数的文本表示附加到字节切片。我们可以使用 16 进制,这样我们会有更紧凑的表示,而且因为将数字转换为 16 进制的算法比转换为 10 进制更快:

func encode(subj, id1, id2 int64) string {
    b := make([]byte, 0, 20)

    b = strconv.AppendInt(b, id2, 16)
    b = append(b, '_')
    b = strconv.AppendInt(b, id1, 16)
    b = append(b, '_')
    b = strconv.AppendInt(b, subj, 16)

    return string(b)
}

正在测试:

id2, id1, subj := int64(0x04), int64(0x02), int64(0x01)
key := encode(subj, id1, id2)
fmt.Println(key)

输出(在 Go Playground 上尝试):

4_2_1