Search code examples
stringgobinarybinary-operators

Bitmasking conversion of CPU ids with Go


I have a mask that contains a binary counting of cpu_ids (0xA00000800000 for 3 CPUs) which I want to convert into a string of comma separated cpu_ids: "0,2,24".

I did the following Go implementation (I am a Go starter). Is it the best way to do it? Especially the handling of byte buffers seems to be inefficient!

package main

import (
    "fmt"
    "os"
    "os/exec"
)

func main(){
    cpuMap     := "0xA00000800000"
    cpuIds     = getCpuIds(cpuMap)
    fmt.Println(cpuIds)
}

func getCpuIds(cpuMap string) string {
    // getting the cpu ids
    cpu_ids_i, _ := strconv.ParseInt(cpuMap, 0, 64) // int from string
    cpu_ids_b := strconv.FormatInt(cpu_ids_i, 2)    // binary as string

    var buff bytes.Buffer
    for i, runeValue := range cpu_ids_b {
        // take care! go returns code points and not the string    
        if runeValue == '1' {
            //fmt.Println(bitString, i)
            buff.WriteString(fmt.Sprintf("%d", i))
        }
        if (i+1 < len(cpu_ids_b)) && (runeValue == '1') {
            //fmt.Println(bitString)
            buff.WriteString(string(","))
        }

    }
    cpuIds := buff.String()
    // remove last comma
    cpuIds = cpuIds[:len(cpuIds)-1]
    //fmt.Println(cpuIds)
    return cpuIds
}

Returns:

"0,2,24"


Solution

  • What you're doing is essentially outputting the indices of the "1"'s in the binary representation from left-to-right, and starting index counting from the left (unusal).

    You can achieve the same using bitmasks and bitwise operators, without converting it to a binary string. And I would return a slice of indices instead of its formatted string, easier to work with.

    To test if the lowest (rightmost) bit is 1, you can do it like x&0x01 == 1, and to shift a whole number bitwise to the right: x >>= 1. After a shift, the rightmost bit "disappears", and the previously 2nd bit becomes the 1st, so you can test again with the same logic. You may loop until the number is greater than 0 (which means it sill has 1-bits).

    See this question for more examples of bitwise operations: Difference between some operators "|", "^", "&", "&^". Golang

    Of course if we test the rightmost bit and shift right, we get the bits (indices) in reverse order (compared to what you want), and the indices are counted from right, so we have to correct this before returning the result.

    So the solution looks like this:

    func getCpuIds(cpuMap string) (r []int) {
        ci, err := strconv.ParseInt(cpuMap, 0, 64)
        if err != nil {
            panic(err)
        }
    
        count := 0
        for ; ci > 0; count, ci = count+1, ci>>1 {
            if ci&0x01 == 1 {
                r = append(r, count)
            }
        }
    
        // Indices are from the right, correct it:
        for i, v := range r {
            r[i] = count - v - 1
        }
        // Result is in reverse order:
        for i, j := 0, len(r)-1; i < j; i, j = i+1, j-1 {
            r[i], r[j] = r[j], r[i]
        }
    
        return
    }
    

    Output (try it on the Go Playground):

    [0 2 24]
    

    If for some reason you need the result as a comma separated string, this is how you can obtain that:

    buf := &bytes.Buffer{}
    for i, v := range cpuIds {
        if i > 0 {
            buf.WriteString(",")
        }
        buf.WriteString(strconv.Itoa(v))
    }
    cpuIdsStr := buf.String()
    fmt.Println(cpuIdsStr)
    

    Output (try it on the Go Playground):

    0,2,24