int64中的位掩码多个值

时间:2017-01-22 11:56:04

标签: go bit-shift

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

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

所以,让我们说我以#SUBJECT_#ID1_#ID2模式存储。 主题是我的代码中的表或查询段范围(例如,所有内容都涉及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,但我不确定这是否是安全路线。

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

2 个答案:

答案 0 :(得分:2)

将数字打包到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的位掩码,以确保我们打包的数字不重叠(可论证,因为如果组件更大,我们仍然会被搞砸。 ..)。

另请注意,如果我们还使用符号位(63 th ),我们必须在解码后的bitshift之后应用屏蔽,因为右移&#34;引入&# 34;符号位而不是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,并且根据&#34;规则&#34;解析格式字符串并格式化参数需要时间。以格式字符串布局。

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

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

输出:

4_2_1

这个会快得多,因为它不必处理格式字符串,它只会连接参数。

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

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

答案 1 :(得分:0)

似乎已经弄明白了:

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也足够了,因为左边没有位。