Golang 最佳化之路——Cantor pair

來源:互聯網
上載者:User
這是一個建立於 的文章,其中的資訊可能已經有所發展或是發生改變。

寫在前面

某一種對象是通過兩個ID唯一確定的,如何處理這種資料結構以便快速尋找以及節約記憶體?先說一種笨方法——用字串來處理。這是比較容易想到的(我覺得一般最容易想到的也是最簡單粗暴的方法都是用字串來搞搞搞)。

fmt.Sprintf("%d_%d", id1, id2)

這樣就成了。儲存的時候用字串來儲存,查詢比較的時候用字串的方法來計算。當然,把數字當作字串來儲存和計算本身就是極其浪費記憶體和CPU的。

Cantor pairing function 簡介

康托爾配對 - Cantor pairing function,是一種將兩個自然數轉成唯一一個自然數的方法。具體原理我就不說了,我也看不懂。。。簡單地說:

  • 只支援自然數。自然數是整數(自然數包括正整數和零);
  • 支援反解;
  • f(k1, k2)f(k2, k1)得到的結果是不同的。當然,如果你想得到相同的也是可以的,計算支援把兩個數字排個序;
  • 計算結果有可能比k1k2都大很多,需要注意溢出的問題。

Example

這個演算法只有一個公式,實現起來很容易。我索性自己造了一個輪子,pairing。公式的推導請移步維基百科。

import "github.com/mnhkahn/pairing"pair := pairing.Encode(k1, k2)k3, k4 := pairing.Decode(pair2)

支援正向編碼以及反向解碼。

實現

import "math"func Encode(k1, k2 uint64) uint64 {pair := k1 + k2pair = pair * (pair + 1)pair = pair / 2pair = pair + k2return pair}
  • func Decode(pair uint64) (uint64, uint64) { w := math.Floor((math.Sqrt(float64(8pair+1)) - 1) / 2) t := (ww + w) / 2

      k2 := pair - uint64(t)  k1 := uint64(w) - k2  return k1, k2   }

與其它資料結構的對比

其中一個要對比的就是和前面說的字串的比較。還有一種代替方案:兩個整數int32,這64位元字拼接一個int64裡面,第一個數字占前32位,後一個數字佔後32位,也是一個可行的方案。我們可以把這個方法叫做bit方法。

func EncodeBit(k1, k2 uint32) uint64 {pair := uint64(k1)<<32 | uint64(k2)return pair}func DecodeBit(pair uint64) (uint32, uint32) {k1 := uint32(pair >> 32)k2 := uint32(pair) & 0xFFFFFFFFreturn k1, k2}

實現起來更簡單。那和Cantor pair相比效能怎麼樣呢?

import ("fmt""testing")var TEST_PAIRS = [][]uint64{[]uint64{0, 0},[]uint64{0, 1},[]uint64{1, 0},}var TEST_RESs = []uint64{0,2,1,}var TEST_RESBits = []uint64{0,1,4294967296,}func TestPair(t *testing.T) {for i, p := range TEST_PAIRS {a, b := p[0], p[1]if pair := Encode(a, b); pair != TEST_RESs[i] {t.Error(a, b, pair)}}for i, p := range TEST_PAIRS {a, b := p[0], p[1]pair := TEST_RESs[i]if x, y := Decode(pair); x != a || y != b {t.Error(a, b, pair)}}fmt.Println(Encode(559, 83792))for i, p := range TEST_PAIRS {a, b := uint32(p[0]), uint32(p[1])if pair := EncodeBit(a, b); pair != TEST_RESBits[i] {t.Error(a, b, pair)}}for i, p := range TEST_PAIRS {a, b := uint32(p[0]), uint32(p[1])pair := TEST_RESBits[i]if x, y := DecodeBit(pair); x != a || y != b {t.Error(a, b, pair)}}fmt.Println(EncodeBit(559, 83792))}func BenchmarkEncode(b *testing.B) {for i := 0; i < b.N; i++ {Encode(559, 83792)}}func BenchmarkDecode(b *testing.B) {for i := 0; i < b.N; i++ {Decode(3557671568)}}func BenchmarkEncodeBit(b *testing.B) {for i := 0; i < b.N; i++ {EncodeBit(559, 83792)}}func BenchmarkDecodeBit(b *testing.B) {for i := 0; i < b.N; i++ {DecodeBit(2400886802256)}}func BenchmarkEncodeStr(b *testing.B) {for i := 0; i < b.N; i++ {_ = fmt.Sprintf("%d_%d", 559, 83792)}}/*go test -bench=. -benchmemBenchmarkEncode-2       2000000000               0.37 ns/op            0 B/op          0 allocs/opBenchmarkDecode-2       50000000                26.9 ns/op             0 B/op          0 allocs/opBenchmarkEncodeBit-2    2000000000               0.37 ns/op            0 B/op          0 allocs/opBenchmarkDecodeBit-2    2000000000               0.37 ns/op            0 B/op          0 allocs/opBenchmarkEncodeStr-2     5000000               271 ns/op              32 B/op          3 allocs/op*/

結論

  • 用字串儲存的效能最差,剩餘兩個是這個效能的400倍;
  • Cantor pair和bit方法的效能相當,反解的時候效能還查一些;
  • 既然效能相當,雖然本文著重介紹的是Cantor pair演算法,但我還是建議,如果bit方法能滿足你的需求,尤其是數字範圍較小的時候,還是用這個比較好。簡單的方法在維護方面會讓你更加得心應手,即使你懂那些複雜的公式是如何推匯出來的^ ^。

本文所涉及到的完整源碼請參考。

原文連結:Golang 最佳化之路——Cantor pair,轉載請註明來源!

相關文章

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.