gpt4 book ai didi

java - 检查字节数组是否全为零的最快方法

转载 作者:行者123 更新时间:2023-12-02 12:19:04 26 4
gpt4 key购买 nike

我有一个 byte[4096]并且想知道检查所有值是否为零的最快方法是什么?

有没有比做更快的方法:

byte[] b = new byte[4096];
b[4095] = 1;
for(int i=0;i<b.length;i++)
if(b[i] != 0)
return false; // Not Empty

最佳答案

我在第一次对所有字节求和时重写了这个答案,但是这是不正确的,因为 Java 已经对字节进行了签名,因此我需要或。此外,我现在已将 JVM 预热更改为正确。

最好的办法是简单地循环遍历所有值。

我想你有三个主要的选择:

  • 或所有元素并检查总和。
  • 进行无分支比较。
  • 与分支进行比较。

  • 我不知道使用 Java(低级性能)添加字节的性能有多好,我知道如果您进行分支比较,Java 会使用(低级)分支预测器。

    因此,我希望发生以下情况:
    byte[] array = new byte[4096];
    for (byte b : array) {
    if (b != 0) {
    return false;
    }
    }
  • 当分支预测器仍在播种时,前几次迭代中的比较相对较慢。
  • 由于分支预测,分支比较非常快,因为无论如何每个值都应该为零。

  • 如果它会达到非零值,则分支预测器将失败,导致比较变慢,但是您也处于计算的末尾,因为您想以任何一种方式返回 false。我认为一个失败的分支预测的成本比继续迭代数组的成本小一个数量级。

    我还相信 for (byte b : array)应该被允许,因为它应该被直接编译成索引数组迭代,据我所知没有这样的东西 PrimitiveArrayIterator这会导致一些额外的方法调用(如遍历列表),直到代码被内联。

    更新

    我编写了自己的基准测试,它们给出了一些有趣的结果......不幸的是,我无法使用任何现有的基准测试工具,因为它们很难正确安装。

    我还决定将选项 1 和 2 组合在一起,因为我认为它们实际上与无分支的你通常或所有东西(减去条件)相同,然后检查最终结果。这里的条件是 x > 0因此 a or of zero 大概是一个 noop 。

    编码:
    public class Benchmark {
    private void start() {
    //setup byte arrays
    List<byte[]> arrays = createByteArrays(700_000);

    //warmup and benchmark repeated
    arrays.forEach(this::byteArrayCheck12);
    benchmark(arrays, this::byteArrayCheck12, "byteArrayCheck12");

    arrays.forEach(this::byteArrayCheck3);
    benchmark(arrays, this::byteArrayCheck3, "byteArrayCheck3");

    arrays.forEach(this::byteArrayCheck4);
    benchmark(arrays, this::byteArrayCheck4, "byteArrayCheck4");

    arrays.forEach(this::byteArrayCheck5);
    benchmark(arrays, this::byteArrayCheck5, "byteArrayCheck5");
    }

    private void benchmark(final List<byte[]> arrays, final Consumer<byte[]> method, final String name) {
    long start = System.nanoTime();
    arrays.forEach(method);
    long end = System.nanoTime();
    double nanosecondsPerIteration = (end - start) * 1d / arrays.size();
    System.out.println("Benchmark: " + name + " / iterations: " + arrays.size() + " / time per iteration: " + nanosecondsPerIteration + "ns");
    }

    private List<byte[]> createByteArrays(final int amount) {
    Random random = new Random();
    List<byte[]> resultList = new ArrayList<>();
    for (int i = 0; i < amount; i++) {
    byte[] byteArray = new byte[4096];
    byteArray[random.nextInt(4096)] = 1;
    resultList.add(byteArray);
    }
    return resultList;
    }

    private boolean byteArrayCheck12(final byte[] array) {
    int sum = 0;
    for (byte b : array) {
    sum |= b;
    }
    return (sum == 0);
    }

    private boolean byteArrayCheck3(final byte[] array) {
    for (byte b : array) {
    if (b != 0) {
    return false;
    }
    }
    return true;
    }

    private boolean byteArrayCheck4(final byte[] array) {
    return (IntStream.range(0, array.length).map(i -> array[i]).reduce(0, (a, b) -> a | b) != 0);
    }

    private boolean byteArrayCheck5(final byte[] array) {
    return IntStream.range(0, array.length).map(i -> array[i]).anyMatch(i -> i != 0);
    }

    public static void main(String[] args) {
    new Benchmark().start();
    }
    }

    令人惊讶的结果:

    Benchmark: byteArrayCheck12 / iterations: 700000 / time per iteration: 50.18817142857143ns
    Benchmark: byteArrayCheck3 / iterations: 700000 / time per iteration: 767.7371985714286ns
    Benchmark: byteArrayCheck4 / iterations: 700000 / time per iteration: 21145.03219857143ns
    Benchmark: byteArrayCheck5 / iterations: 700000 / time per iteration: 10376.119144285714ns



    这表明 orring 比分支预测器快很多,这相当令人惊讶,所以我假设正在完成一些低级优化。

    作为额外的我已经包括了流变体,无论如何我没想到它会那么快。

    在原厂时钟英特尔 i7-3770、16GB 1600MHz RAM 上运行。

    所以我认为最终的答案是:视情况而定。这取决于您要连续检查数组的次数。 “byteArrayCheck3”方案始终稳定在700~800ns。

    跟进更新

    事情实际上采取了另一种有趣的方法,结果 JIT 由于根本没有使用结果变量而优化了几乎所有的计算。

    因此我有以下新 benchmark方法:
    private void benchmark(final List<byte[]> arrays, final Predicate<byte[]> method, final String name) {
    long start = System.nanoTime();
    boolean someUnrelatedResult = false;
    for (byte[] array : arrays) {
    someUnrelatedResult |= method.test(array);
    }
    long end = System.nanoTime();
    double nanosecondsPerIteration = (end - start) * 1d / arrays.size();
    System.out.println("Result: " + someUnrelatedResult);
    System.out.println("Benchmark: " + name + " / iterations: " + arrays.size() + " / time per iteration: " + nanosecondsPerIteration + "ns");
    }

    这确保了基准测试的结果不能被优化掉,因此主要问题是 byteArrayCheck12方法无效,因为它注意到 (sum == 0)没有被使用,因此它优化了整个方法。

    因此,我们有以下新结果(为清楚起见省略了结果打印):

    Benchmark: byteArrayCheck12 / iterations: 700000 / time per iteration: 1370.6987942857143ns
    Benchmark: byteArrayCheck3 / iterations: 700000 / time per iteration: 736.1096242857143ns
    Benchmark: byteArrayCheck4 / iterations: 700000 / time per iteration: 20671.230327142857ns
    Benchmark: byteArrayCheck5 / iterations: 700000 / time per iteration: 9845.388841428572ns



    因此我们认为我们最终可以得出分支预测获胜的结论。然而,它也可能由于提前返回而发生,因为平均而言,违规字节将位于字节数组的中间,因此是时候使用另一种不提前返回的方法了:
    private boolean byteArrayCheck3b(final byte[] array) {
    int hits = 0;
    for (byte b : array) {
    if (b != 0) {
    hits++;
    }
    }
    return (hits == 0);
    }

    通过这种方式,我们仍然受益于分支预测,但是我们确保我们不能提前返回。

    这反过来又给了我们更多有趣的结果!

    Benchmark: byteArrayCheck12 / iterations: 700000 / time per iteration: 1327.2817714285713ns
    Benchmark: byteArrayCheck3 / iterations: 700000 / time per iteration: 753.31376ns
    Benchmark: byteArrayCheck3b / iterations: 700000 / time per iteration: 1506.6772842857142ns
    Benchmark: byteArrayCheck4 / iterations: 700000 / time per iteration: 21655.950115714284ns
    Benchmark: byteArrayCheck5 / iterations: 700000 / time per iteration: 10608.70917857143ns



    我认为我们最终可以得出结论,最快的方法是使用早期返回和分支预测,然后是 orring,然后是纯粹的分支预测。我怀疑所有这些操作都在 native 代码中进行了高度优化。

    更新 ,使用 long 和 int 数组进行一些额外的基准测试。

    看到使用建议后 long[]int[]我认为这值得调查。然而,这些尝试可能不再完全符合原始答案,但仍然可能很有趣。

    首先,我改变了 benchmark使用泛型的方法:
    private <T> void benchmark(final List<T> arrays, final Predicate<T> method, final String name) {
    long start = System.nanoTime();
    boolean someUnrelatedResult = false;
    for (T array : arrays) {
    someUnrelatedResult |= method.test(array);
    }
    long end = System.nanoTime();
    double nanosecondsPerIteration = (end - start) * 1d / arrays.size();
    System.out.println("Result: " + someUnrelatedResult);
    System.out.println("Benchmark: " + name + " / iterations: " + arrays.size() + " / time per iteration: " + nanosecondsPerIteration + "ns");
    }

    然后我执行了来自 byte[] 的转换至 long[]int[]分别 之前 在基准测试中,还需要将最大堆大小设置为 10 GB。
    List<long[]> longArrays = arrays.stream().map(byteArray -> {
    long[] longArray = new long[4096 / 8];
    ByteBuffer.wrap(byteArray).asLongBuffer().get(longArray);
    return longArray;
    }).collect(Collectors.toList());
    longArrays.forEach(this::byteArrayCheck8);
    benchmark(longArrays, this::byteArrayCheck8, "byteArrayCheck8");

    List<int[]> intArrays = arrays.stream().map(byteArray -> {
    int[] intArray = new int[4096 / 4];
    ByteBuffer.wrap(byteArray).asIntBuffer().get(intArray);
    return intArray;
    }).collect(Collectors.toList());
    intArrays.forEach(this::byteArrayCheck9);
    benchmark(intArrays, this::byteArrayCheck9, "byteArrayCheck9");

    private boolean byteArrayCheck8(final long[] array) {
    for (long l : array) {
    if (l != 0) {
    return false;
    }
    }
    return true;
    }

    private boolean byteArrayCheck9(final int[] array) {
    for (int i : array) {
    if (i != 0) {
    return false;
    }
    }
    return true;
    }

    这给出了以下结果:

    Benchmark: byteArrayCheck8 / iterations: 700000 / time per iteration: 259.8157614285714ns
    Benchmark: byteArrayCheck9 / iterations: 700000 / time per iteration: 266.38013714285717ns



    如果可能以这种格式获取字节,则此路径可能值得探索。然而,在基准方法中进行转换时,每次迭代的时间约为 2000 纳秒,因此当您需要自己进行转换时,这是不值得的。

    关于java - 检查字节数组是否全为零的最快方法,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/23824364/

    26 4 0
    Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
    广告合作:1813099741@qq.com 6ren.com