【问题标题】:How to check if []byte is all zeros in go如何检查 []byte 在 go 中是否全为零
【发布时间】:2017-08-04 12:05:59
【问题描述】:

有没有办法在不检查每个元素或使用反射的情况下检查字节切片是空还是 0?

theByteVar := make([]byte, 128)

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

我发现一个看起来很奇怪的解决方案是保留一个空字节数组进行比较。

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

// fill with anything
theByteVar[1] = 2

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

肯定有更好/更快的解决方案。

谢谢

UPDATE 对 1000 个循环进行了比较,反射方式是迄今为止最差的......

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

【问题讨论】:

  • 最初,theByteVar 的 len() 为 0,cap() 为 128。len() 为 0 表示它为空。如果不为空,则可以使用 for/range 来测试非零值,并在它不为空时在第一次出现时中断。
  • 但是,您似乎对切片和数组感到困惑。确保了解其中的区别。对于数组,您需要遍历所有元素以测试非零。
  • 看来 len 也返回 128,所以我无法检查是否为空。 play.golang.org/p/plx-vt0XjC

标签: go slice


【解决方案1】:

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

这个想法很简单,我们可以在每个步骤中检查byte[i:i+8]的值,也就是uint64的值,而不是检查[]byte中的每个字节。通过这样做,我们可以检查 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缓存机制有关。

【讨论】:

  • 这一行好像打错了,i不需要乘8:b := *(*uint64)(unsafe.Pointer(uintptr(unsafe.Pointer(&amp;data[0])) + 8*uintptr(i)))。只需*(*uint64)(unsafe.Pointer(&amp;data[i])) 即可。
【解决方案2】:

将其与仅包含零的另一个切片进行比较,这需要读取(并比较)2 个切片。

在这里使用单个 for 循环会更有效:

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

如果您确实需要在多个地方使用它,请将其包装在实用函数中:

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

然后使用它:

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

【讨论】:

  • 这样做的一个好处是,如果处理得当,您将利用 CPU L1 缓存优化,例如 Intel 的静态读取预测。这就是为什么 Go 语言让你只迭代集合中的项目,而不是使用一些花哨的内置抽象扩展。只要您保持结构对齐,并且您可以巧妙地使用 L1。例如,如果经常交换字节,您可能希望将这些位存储在 int64 中:这样您就可以减少 L1 缓存失效。
【解决方案3】:

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

【讨论】:

    猜你喜欢
    • 2017-01-27
    • 2011-04-13
    • 2014-06-24
    • 1970-01-01
    • 2012-04-27
    • 2021-03-29
    • 2014-09-23
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多