Go 位掩码int64中的多个值

Go 位掩码int64中的多个值,go,bit-shift,Go,Bit Shift,我正在使用缓存数据库结果,但目前我需要在每次删除时转储更大的数据块,与目标删除相比,这会额外花费几微秒fmt.Sprintf(“%d_%d_%d”)对于像#SUBJECT_35;ID1_35;ID2这样的模式,也需要花费几微秒的时间。即使这样听起来也不算太多,在缓存响应时间的当前比率中,大量缓存的响应速度也比当前慢 我正在考虑使用库的SetInt/GetInt,它使用int64键而不是字符串 假设我存储在#SUBJECT#ID1#ID2模式中。主题是我代码中的表或查询段范围(例如,与ACL或Pr

我正在使用缓存数据库结果,但目前我需要在每次删除时转储更大的数据块,与目标删除相比,这会额外花费几微秒<代码>fmt.Sprintf(“%d_%d_%d”)对于像
#SUBJECT_35;ID1_35;ID2
这样的模式,也需要花费几微秒的时间。即使这样听起来也不算太多,在缓存响应时间的当前比率中,大量缓存的响应速度也比当前慢

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

假设我存储在
#SUBJECT#ID1#ID2
模式中。主题是我代码中的表或查询段范围(例如,与ACL或Productfiltering有关的所有内容)

让我们以
Userright.id
#ID1
User.id
#ID2
Subject
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)
id2, id1, subj := 0x04, 0x02, 0x01
key := fmt.Sprint(id2, "_", id1, "_", subj)
fmt.Println(key)
如何将
CACHE\u SUBJECT\u ACL
/
userrightID
/
userID
编译到
storeKey

我知道我可以调用
userrightID
0x100000001
,但它是一个动态值,因此我不确定在不造成比将字符串格式化为键更大开销的情况下编译它的最佳方法是什么

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


我正在考虑通过位移位将它们相互添加,就像
userID似乎已经找到了答案:

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
构建
int64
屏蔽。另一方面,使用新移位的掩蔽再次从
int64
中提取旧值

因为
storeKey&0x1fffff00000000>>36
无论如何都会一直运行到最后,
storeKey>>36
也就足够了,因为再左边没有位。

包装编号到
int64
如果我们能确保要打包的数字不是负数,并且它们符合我们为它们保留的位范围,那么是的,这是一种安全有效的打包方法

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

例如,如果我们为
主题
保留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|
请注意,在编码时,建议同时使用位掩码和按位掩码,以确保我们打包的数字不会重叠(这是有争议的,因为如果组件较大,我们无论如何都会出错…)

还要注意,如果我们也使用符号位(第63位),则在解码时必须在位移位后应用掩蔽,因为右移“引入”符号位,而不是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
}
输出(在上尝试):

坚持使用
字符串
最初,您研究了打包到
int64
中,因为它的速度很慢。请注意,
Sprintf()
使用格式
字符串
,解析格式字符串并根据格式字符串中列出的“规则”格式化参数需要时间

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

// 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)
id2, id1, subj := 0x04, 0x02, 0x01
key := fmt.Sprint(id2, "_", id1, "_", subj)
fmt.Println(key)
输出:

4_2_1
4 2 1
这一个将大大加快,因为它不必处理格式字符串,它只会连接参数

我们甚至可以做得更好;如果相邻的两个参数都不是
string
值,则会自动插入一个空格,因此只需列出数字即可:

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

4_2_1
4 2 1
试穿这些衣服

使用
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)
}
测试它:

key := encode(0x01, 0x02, 0x04)
fmt.Printf("%016x\n", key)
fmt.Println(decode(key))
id2, id1, subj := int64(0x04), int64(0x02), int64(0x01)
key := encode(subj, id1, id2)
fmt.Println(key)
输出(在上尝试):


哇,非常感谢你的详细解释!我自己的回答结果是
~175ns/op
。您的
fmt.Sprint
解决方案是
~360ns/op
,但是
AppendInt
要高得多!只有
~89ns/op
哇!例如,我现在的一个权限检查查询是
710787ns/op
每页加载。现在只需89ns/op
,速度快了7986倍!这远远优于使用以前的Redis/Memcache解决方案层。手动缓存映射区域内空间的ftw:DAs。正如你所说,我知道这一点。这两个28位区域都有空间容纳高达268.435.456的ID。幸运的是,在总计数中,我的数据永远不会发生这种情况(如果发生这种情况,我可能永远不会再工作了:P)。我有一个整洁的清理脚本,每周清理大多数主键,并推后自动增量,因此我不担心在大多数普通表上会达到这一点:)而且
256
主题的空间对于更大的系统来说似乎已经足够了。