How to check if []byte is all zeros in go

时间:2017-08-04 12:06:00

标签: go slice

Is there a way to check if a byte slice is empty or 0 without checking each element or using reflect?

theByteVar := make([]byte, 128)

if "theByteVar is empty or zeroes" {
   doSomething()
}

One solution which seems weird that I found was to keep an empty byte array for comparison.

theByteVar := make([]byte, 128)
emptyByteVar := make([]byte, 128)

// fill with anything
theByteVar[1] = 2

if reflect.DeepEqual(theByteVar,empty) == false {
   doSomething(theByteVar)
}

For sure there must be a better/quicker solution.

Thanks

UPDATE did some comparison for 1000 loops and the reflect way is the worst by far...

Equal Loops: 1000 in true in 19.197µs
Contains Loops: 1000 in true in 34.507µs
AllZero Loops: 1000 in true in 117.275µs
Reflect Loops: 1000 in true in 14.616277ms

3 个答案:

答案 0 :(得分:3)

您可以使用bytes.Equal或bytes.Contains与零初始化字节切片进行比较,请参阅https://play.golang.org/p/mvUXaTwKjP,我没有检查性能,但希望它已经过优化。如果需要,您可能需要尝试其他解决方案并比较性能数据。

答案 1 :(得分:3)

另一个解决方案借鉴了C的想法。可以通过使用Go中的unsafe包来实现。

这个想法很简单,我们无需检查[] byte中的每个字节,而是可以在每个步骤中检查byte[i:i+8]的值,该值是uint64的值。这样,我们可以检查8个字节,而不必在每次迭代中仅检查一个字节。

下面的代码不是最佳做法,而只能说明问题。

const (
    len8 int = 0xFFFFFFF8
)

func IsAllBytesZero(data []byte) bool {
    n := len(data)

    // Magic to get largest length which could be divided by 8. 
    nlen8 := n & len8
    i := 0

    for ; i < nlen8; i += 8 {
        b := *(*uint64)(unsafe.Pointer(uintptr(unsafe.Pointer(&data[0])) + 8*uintptr(i)))
        if b != 0 {
            return false
        }
    }

    for ; i < n; i++ {
        if data[i] != 0 {
            return false
        }
    }

    return true
}

基准

测试用例:

仅测试最坏情况(所有元素均为零)

方法:

  • IsAllBytesZero:unsafe包解决方案
  • NaiveCheckAllBytesAreZero:循环遍历整个字节数组并对其进行检查的循环。
  • CompareAllBytesWithFixedEmptyArray:将bytes.Compare解决方案与预先分配的固定大小的空字节数组一起使用。
  • CompareAllBytesWithDynamicEmptyArray:使用bytes.Compare解决方案,无需预先分配固定大小的空字节数组。

结果

BenchmarkIsAllBytesZero10-8                                 254072224            4.68 ns/op
BenchmarkIsAllBytesZero100-8                                132266841            9.09 ns/op
BenchmarkIsAllBytesZero1000-8                               19989015            55.6 ns/op
BenchmarkIsAllBytesZero10000-8                               2344436           507 ns/op
BenchmarkIsAllBytesZero100000-8                              1727826           679 ns/op
BenchmarkNaiveCheckAllBytesAreZero10-8                      234153582            5.15 ns/op
BenchmarkNaiveCheckAllBytesAreZero100-8                     30038720            38.2 ns/op
BenchmarkNaiveCheckAllBytesAreZero1000-8                     4300405           291 ns/op
BenchmarkNaiveCheckAllBytesAreZero10000-8                     407547          2666 ns/op
BenchmarkNaiveCheckAllBytesAreZero100000-8                     43382         27265 ns/op
BenchmarkCompareAllBytesWithFixedEmptyArray10-8             415171356            2.71 ns/op
BenchmarkCompareAllBytesWithFixedEmptyArray100-8            218871330            5.51 ns/op
BenchmarkCompareAllBytesWithFixedEmptyArray1000-8           56569351            21.0 ns/op
BenchmarkCompareAllBytesWithFixedEmptyArray10000-8           6592575           177 ns/op
BenchmarkCompareAllBytesWithFixedEmptyArray100000-8           567784          2104 ns/op
BenchmarkCompareAllBytesWithDynamicEmptyArray10-8           64215448            19.8 ns/op
BenchmarkCompareAllBytesWithDynamicEmptyArray100-8          32875428            35.4 ns/op
BenchmarkCompareAllBytesWithDynamicEmptyArray1000-8          8580890           140 ns/op
BenchmarkCompareAllBytesWithDynamicEmptyArray10000-8         1277070           938 ns/op
BenchmarkCompareAllBytesWithDynamicEmptyArray100000-8         121256         10355 ns/op

摘要

假设我们正在谈论稀疏零字节数组中的条件。根据基准测试,如果性能成为问题,那么幼稚的检查解决方案将不是一个好主意。而且,如果您不想在项目中使用unsafe包,请考虑使用带有预先分配的空数组的bytes.Compare解决方案。

可能要指出的一个有趣点是,unsafe软件包的性能变化很大,但它的性能基本上优于上述所有其他解决方案。我认为这与CPU缓存机制有关。

答案 2 :(得分:2)

Comparing it with another slice containing only zeros, that requires reading (and comparing) 2 slices.

Using a single for loop will be more efficient here:

for _, v := range theByteVar {
    if v != 0 {
        doSomething(theByteVar)
        break
    }
}

If you do need to use it in multiple places, wrap it in a utility function:

func allZero(s []byte) bool {
    for _, v := range s {
        if v != 0 {
            return false
        }
    }
    return true
}

And then using it:

if !allZero(theByteVar) {
    doSomething(theByteVar)
}