有没有办法在Go中快速订购类型?

时间:2017-09-02 16:48:46

标签: go types

我有一个包必须订购Go类型,快速

目前我使用reflect.Type s,用Name()获取他们的名字,并将名称命名为字符串:

if type1.Name() < type2.Name() { ...

但是,它使用字符串比较。它有效,但我正在寻找更快的解决方案。

这个比较的确切方式对我来说并不重要 - 只有我需要的,

  1. 比较结果在过程的生命中应该是相同的。
  2. 对于不同的类型,它应该是不相等的,但对于相同的类型应该是相等的。
  3. 直接将reflect.Type变量与<进行比较并不起作用,因为此操作未在Go中为reflect.Type定义。

    可以生成类型名称的散列为64位或128位整数,然后比较这些整数。这是一种可能性,但我正在寻找一种更快的解决方案。

    另一种可能性是将reflect.Type变量与它们的不安全指针进行比较(转换为int64),但据我所知,Go中无法保证不安全指针地址在流程的生命周期中不会发生变化。因此,我将失去我的(1)要求。

1 个答案:

答案 0 :(得分:2)

使用Type.Name()不好

使用Type.Name()进行比较是一个非常糟糕的主意,因为有许多匿名类型Type.Name()返回空字符串,例如[]int*int等。由Type.Name()返回的不包含包名称,因此例如time.Timefoo.Time的名称将为"Time",因此被视为相等。不再需要说了。查看Identify non builtin-types using reflect了解详情。

使用内部注册表

一种简单快捷的方法是为所有需要比较的类型分配一个整数(例如int值),然后您不需要进行任何string比较或散列,只需比较分配的int值。

唯一的问题是如何分配和记住这些int值。指定的值可能只是连续值,从1开始,然后是2,3等。此分配和查找可能是自动的(隐藏在我们眼前)。

基本上我们想要一个像这样的函数:

// Compare compares 2 types, returns a negative number
// if t1 < t2, a positive number if t1 > t2, and 0 otherwise.
func Compare(t1, t2 reflect.Type) int {
}

实施非常简单/紧凑:

var (
    registry = map[reflect.Type]int{}
    max      int
)

func value(t reflect.Type) int {
    v := registry[t]
    if v == 0 {
        max++
        v, registry[t] = max, max
    }
    return v
}

// Compare compares 2 types, returns a negative number
// if t1 < t2, a positive number if t1 > t2, and 0 otherwise.
func Compare(t1, t2 reflect.Type) int {
    v1, v2 := value(t1), value(t2)
    return v1 - v2
}

测试它:

tstring := reflect.TypeOf("")
tint := reflect.TypeOf(int(1))
ttime := reflect.TypeOf(time.Time{})

fmt.Println(Compare(tstring, tstring)) // 0
fmt.Println(Compare(tint, ttime))      // -1, tint gets registered first
fmt.Println(Compare(ttime, tint))      // 1
fmt.Println(Compare(tint, tint))       // 0
fmt.Println(Compare(tstring, ttime))   // -2
fmt.Println(Compare(ttime, tstring))   // 2

输出(在Go Playground上尝试):

0
-1
1
0
-2
2

使并发使用安全

但有一个缺点:实施对于并发使用是不安全的(通过多个goroutines)。如果我们需要安全性来同时使用,则必须同步对registry地图(以及max变量)的访问:

var mux sync.RWMutex

func value(t reflect.Type) int {
    mux.RLock()
    v := registry[t]
    mux.RUnlock()
    if v == 0 {
        mux.Lock()
        max++
        v, registry[t] = max, max
        mux.Unlock()
    }
    return v
}

其余的没有变化。输出是一样的。在Go Playground上试试这个。

引擎盖下(看得更近)

现在这个Compare()意味着在引擎盖下使用锁。另外,为了获得指定的类型的整数值,我们必须索引地图。此映射中的键的类型是reflect.Type,它是一种接口类型。 reflect.Type的当前实现是*reflect.rtype这是一个指针,所以它仍然足够快,因为只有一个指针被哈希,然后在一个桶中查找(内置映射是一个hashmap实现)。 p>

最快的解决方案(摆脱锁定和地图索引)

现在这个Compare()意味着在引擎盖下使用锁定(以及索引地图),因此如果您真的寻求最快的解决方案,这仍然是一个不可接受的“负担”。如果获取为该类型分配的内部int值,并且使用它,甚至可以直接与另一个类型的值进行比较,则可以轻松地在需要比较时始终使用这些值。

为此,您需要做的就是“发布”(导出)value()功能:

func Value(t reflect.Type) int {
    // ...
}

甚至可以删除Compare()。因此,需要这种类型比较的库可以查询分配给类型的整数值,并且它们可以“缓存”该值以避免再次调用它(因为它在进程的生命周期内不会改变),并且它可以与此Value()函数获得的其他值进行比较。

例如:

vstring := Value(reflect.TypeOf(""))
vint := Value(reflect.TypeOf(int(1)))
vtime := Value(reflect.TypeOf(time.Time{}))

fmt.Println(vstring - vstring) // 0
fmt.Println(vint - vtime)      // -1, tint gets registered first
fmt.Println(vtime - vint)      // 1
fmt.Println(vint - vint)       // 0
fmt.Println(vstring - vtime)   // -2
fmt.Println(vtime - vstring)   // 2

Go Playground上试试这个。