dsiv4041 2015-08-06 16:14
浏览 47

此整数池代码如何工作

I've been trying to understand how this integer pool works. It is a lot of bit fiddling stuff I can't wrap my head around. I'm assuming there is a concept I'm missing with the m2id array and how it is or'ed with index 'n' that I don't know and would clear up a lot of my confusion. Are there are any general concepts/CS-theory that explain this seemingly-looking-simple code. I've put comments in the code to try and state my current understanding and where I am totally confused.

// Copyright 2009 The Go9p Authors.  All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
//Original source: https://github.com/rminnich/go9p/blob/master/clnt_pool.go
package go9p

import "sync" 

var m2id = [...]uint8{  // I think this is where the magic is.
    0, 1, 0, 2, 0, 1, 0, 3,
    0, 1, 0, 2, 0, 1, 0, 4,
    0, 1, 0, 2, 0, 1, 0, 3,
    0, 1, 0, 2, 0, 1, 0, 5,
    0, 1, 0, 2, 0, 1, 0, 3,
    0, 1, 0, 2, 0, 1, 0, 4,
    0, 1, 0, 2, 0, 1, 0, 3,
    0, 1, 0, 2, 0, 1, 0, 6,
    0, 1, 0, 2, 0, 1, 0, 3,
    0, 1, 0, 2, 0, 1, 0, 4,
    0, 1, 0, 2, 0, 1, 0, 3,
    0, 1, 0, 2, 0, 1, 0, 5,
    0, 1, 0, 2, 0, 1, 0, 3,
    0, 1, 0, 2, 0, 1, 0, 4,
    0, 1, 0, 2, 0, 1, 0, 3,
    0, 1, 0, 2, 0, 1, 0, 7,
    0, 1, 0, 2, 0, 1, 0, 3,
    0, 1, 0, 2, 0, 1, 0, 4,
    0, 1, 0, 2, 0, 1, 0, 3,
    0, 1, 0, 2, 0, 1, 0, 5,
    0, 1, 0, 2, 0, 1, 0, 3,
    0, 1, 0, 2, 0, 1, 0, 4,
    0, 1, 0, 2, 0, 1, 0, 3,
    0, 1, 0, 2, 0, 1, 0, 6,
    0, 1, 0, 2, 0, 1, 0, 3,
    0, 1, 0, 2, 0, 1, 0, 4,
    0, 1, 0, 2, 0, 1, 0, 3,
    0, 1, 0, 2, 0, 1, 0, 5,
    0, 1, 0, 2, 0, 1, 0, 3,
    0, 1, 0, 2, 0, 1, 0, 4,
    0, 1, 0, 2, 0, 1, 0, 3,
    0, 1, 0, 2, 0, 1, 0, 0,
}

type pool struct {
    sync.Mutex
    need  int
    nchan chan uint32
    maxid uint32
    imap  []byte
}

func newPool(maxid uint32) *pool {
    p := new(pool)
    p.maxid = maxid
    p.nchan = make(chan uint32)

    return p
}

func (p *pool) getId() uint32 {
    var n uint32 = 0
    var ret uint32

    p.Lock()
    for n = 0; n < uint32(len(p.imap)); n++ {
        // it looks like every 0...n position of imap will be incremented to 255.
        if p.imap[n] != 0xFF {
            break
        }
    }

    if int(n) >= len(p.imap) {
        // This seems to be just growing the imap slice as needed.
        // I don't quite understand the constant of '8' here.
        m := uint32(len(p.imap) + 32)
        if uint32(m*8) > p.maxid {
            m = p.maxid/8 + 1
        }

        b := make([]byte, m)
        copy(b, p.imap)
        p.imap = b
    }

    if n >= uint32(len(p.imap)) {
        // If you get here the I'm assuming all the ID's are used up and putId will return you the next released ID.
        p.need++
        p.Unlock()
        ret = <-p.nchan
    } else {
        // This part I'm having a hard time grasping.  
        // It seems that each index of imap is incremented
        // from 0 to 255 and magically or'd with ret to increment to the next number?
        ret = uint32(m2id[p.imap[n]])
        p.imap[n] |= 1 << ret
        ret += n * 8
        p.Unlock()
    }

    return ret
}

func (p *pool) putId(id uint32) {
    p.Lock()
    if p.need > 0 {
        p.nchan <- id
        p.need--
        p.Unlock()
        return
    }

    // This doesn't play well with what I thought was going on.  I though that.  
    // I was thinking that imap[0] would always somehow magically return all the 
    // values from 0 to 255 and imap[1] would return 256 += 255 and so on.  
    // How does this work?
    p.imap[id/8] &= ^(1 << (id % 8))
    p.Unlock()
}
  • 写回答

1条回答 默认 最新

  • dongyi2159 2015-08-07 06:07
    关注

    Optimization often leads to obscurity. Start with the basic concept. The pool of available Ids is represented by the underlying bit array of a slice of bytes. Id 19 is represented by left-to-right byte 2 (19 / 8) and right-to-left bit 3 (19 % 8).

    Here's a simple implementation, ignoring details like locking and growing the bit array.

    package main
    
    import "fmt"
    
    // The Id pool is represented by the underlying bit array of a slice of bytes.
    var idPool = make([]byte, 4)
    
    // Get the next available Id from the pool.
    func getId() int {
        // Get next available byte
        for i := 0; i < len(idPool); i++ {
            b := idPool[i]
            if b != 0xFF {
                // Get next available bit in the byte
                for j := 0; j < 8; j++ {
                    if b&(1<<uint(j)) == 0 {
                        // Mark Id bit as unavailable.
                        idPool[i] |= 1 << uint(j)
                        // Return Id.
                        return 8*i + j
                    }
                }
            }
        }
        panic("Insufficient Ids")
    }
    
    // Put the Id back in the pool.
    func putId(id int) {
        if 0 > id || id >= 8*len(idPool) {
            panic("Invalid Id")
        }
        i := id / 8
        j := id % 8
        // Mark Id bit as available.
        idPool[i] &^= 1 << uint(j)
    }
    
    func main() {
        for i := 0; i < 16; i++ {
            getId()
        }
        fmt.Printf("%x
    ", idPool)
        for i := 10; i < 12; i++ {
            putId(i)
        }
        fmt.Printf("%x
    ", idPool)
        fmt.Println(getId())
        fmt.Printf("%x
    ", idPool)
    }
    

    Output:

    ffff0000
    fff30000
    10
    fff70000
    

    We can optimize this loop

    // Get next available bit in the byte
    for j := 0; j < 8; j++ {
        if b&(1<<uint(j)) == 0 {
            // Mark Id bit as unavailable.
            idPool[i] |= 1 << uint(j)
            // Return Id.
            return 8*i + j
        }
    }
    

    by replacing it with a table (m2id) lookup for the bit shift value.

    // Get next available bit in the byte
    j := int(m2id[idPool[i]])
    // Mark Id bit as unavailable.
    idPool[i] |= 1 << uint(j)
    // Return Id.
    return 8*i + j
    

    The m2idInit() function shows how the m2id table bit shift values are calculated.

    func m2idInit() (m2id [256]uint8) {
        // For all byte values.
        for i := uint(0); i < 256; i++ {
            // Find an unused id
            for j := uint(0); j < 8; j++ {
                if i&(1<<j) == 0 {
                    // Bit shift value
                    m2id[i] = uint8(j)
                    break
                }
            }
        }
        return m2id
    }
    

    For example,

    package main
    
    import "fmt"
    
    // The Id pool is represented by the underlying bit array of a slice of bytes.
    var idPool = make([]byte, 4)
    
    // Get the next available Id from the pool.
    func getId() int {
        // Get next available byte
        for i := 0; i < len(idPool); i++ {
            b := idPool[i]
            if b != 0xFF {
                // Get next available bit in the byte
                j := int(m2id[idPool[i]])
                // Mark Id bit as unavailable.
                idPool[i] |= 1 << uint(j)
                // Return Id.
                return 8*i + j
            }
        }
        panic("Insufficient Ids")
    }
    
    // Put the Id back in the pool.
    func putId(id int) {
        if 0 > id || id >= 8*len(idPool) {
            panic("Invalid Id")
        }
        i := id / 8
        j := id % 8
        // Mark Id bit as available.
        idPool[i] &^= 1 << uint(j)
    }
    
    var m2id = m2idInit()
    
    func m2idInit() (m2id [256]uint8) {
        // For all byte values.
        for i := uint(0); i < 256; i++ {
            // Find an unused id
            for j := uint(0); j < 8; j++ {
                if i&(1<<j) == 0 {
                    // Bit shift value
                    m2id[i] = uint8(j)
                    break
                }
            }
        }
        return m2id
    }
    
    func main() {
        for i := 0; i < 16; i++ {
            getId()
        }
        fmt.Printf("%x
    ", idPool)
        for i := 10; i < 12; i++ {
            putId(i)
        }
        fmt.Printf("%x
    ", idPool)
        fmt.Println(getId())
        fmt.Printf("%x
    ", idPool)
    
        fmt.Println()
        fmt.Println(m2id)
    }
    

    Output:

    ffff0000
    fff30000
    10
    fff70000
    
    [0 1 0 2 0 1 0 3
     0 1 0 2 0 1 0 4
     0 1 0 2 0 1 0 3
     0 1 0 2 0 1 0 5
     0 1 0 2 0 1 0 3
     0 1 0 2 0 1 0 4
     0 1 0 2 0 1 0 3
     0 1 0 2 0 1 0 6
     0 1 0 2 0 1 0 3
     0 1 0 2 0 1 0 4
     0 1 0 2 0 1 0 3
     0 1 0 2 0 1 0 5
     0 1 0 2 0 1 0 3
     0 1 0 2 0 1 0 4
     0 1 0 2 0 1 0 3
     0 1 0 2 0 1 0 7
     0 1 0 2 0 1 0 3
     0 1 0 2 0 1 0 4
     0 1 0 2 0 1 0 3
     0 1 0 2 0 1 0 5
     0 1 0 2 0 1 0 3
     0 1 0 2 0 1 0 4
     0 1 0 2 0 1 0 3
     0 1 0 2 0 1 0 6
     0 1 0 2 0 1 0 3
     0 1 0 2 0 1 0 4
     0 1 0 2 0 1 0 3
     0 1 0 2 0 1 0 5
     0 1 0 2 0 1 0 3
     0 1 0 2 0 1 0 4
     0 1 0 2 0 1 0 3
     0 1 0 2 0 1 0 0]
    

    There is no magic.

    References:

    Bit manipulation

    The Go Programming Language Specification, Arithmetic operators

    评论

报告相同问题?

悬赏问题

  • ¥15 用windows做服务的同志有吗
  • ¥60 求一个简单的网页(标签-安全|关键词-上传)
  • ¥35 lstm时间序列共享单车预测,loss值优化,参数优化算法
  • ¥15 Python中的request,如何使用ssr节点,通过代理requests网页。本人在泰国,需要用大陆ip才能玩网页游戏,合法合规。
  • ¥100 为什么这个恒流源电路不能恒流?
  • ¥15 有偿求跨组件数据流路径图
  • ¥15 写一个方法checkPerson,入参实体类Person,出参布尔值
  • ¥15 我想咨询一下路面纹理三维点云数据处理的一些问题,上传的坐标文件里是怎么对无序点进行编号的,以及xy坐标在处理的时候是进行整体模型分片处理的吗
  • ¥15 一直显示正在等待HID—ISP
  • ¥15 Python turtle 画图