当前位置: 首页 > 面试题库 >

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

寿子轩
2023-03-14
问题内容

我有一个,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已经对字节进行了签名,因此我需要or。 另外,我已将JVM预热更改为正确。

最好的选择实际上是简单地遍历所有值。

我想您有三种主要选择:

  1. 或所有元素并检查总和。
  2. 进行无分支比较。
  3. 与分支进行比较。

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

因此,我希望发生以下情况:

byte[] array = new byte[4096];
for (byte b : array) {
    if (b != 0) {
        return false;
    }
}
  1. 当分支预测变量仍在播种自身时,在前几次迭代中比较缓慢。
  2. 由于分支预测,分支比较非常快,因为每个值无论如何都应为零。

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

我进一步 认为for (byte b : array)应该允许这样做,因为据我所知,它应该直接编译成索引数组迭代,在没有PrimitiveArrayIterator内联代码的情况下,内联代码会导致一些额外的方法调用(遍历列表)。

更新资料

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

我还决定将选项1和2组合在一起,因为我认为它们实际上与无分支的您通常使用的所有内容(或减去条件)相同,然后检查最终结果。并且这里的条件是x > 0,因此a或0可能是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();
    }
}

令人惊讶的结果:

基准:byteArrayCheck12 /迭代:700000 /每次迭代时间:50.18817142857143ns
基准:byteArrayCheck3 /迭代:700000 /每次迭代时间:767.7371985714286ns
基准:byteArrayCheck4 /迭代:700000 /每次迭代时间:21145.03219857143ns
基准:byteArrayCheck5 /迭代:700000 /每次迭代时间:10376.119144285714ns


这表明orring比分支预测器快很多,这非常令人惊讶,因此我假设正在执行一些底层优化。

另外,我包括了流变种,但我没想到它会这么快。

运行有时钟的Intel 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)未在使用,因此优化了整个方法。

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

基准:byteArrayCheck12 /迭代:700000 /每次迭代时间:1370.6987942857143ns
基准:byteArrayCheck3 /迭代:700000 /每次迭代时间:736.1096242857143ns
基准:byteArrayCheck4 /迭代:700000 /每次迭代时间:20671.230327142857ns
基准:byteArrayCheck5 /迭代:700000 /每次迭代时间:9845.388841428572ns

因此,我们认为可以最终得出分支预测获胜的结论。但是,由于提前返回,它也可能发生,因为平均而言,有问题的字节将位于字节数组的中间,因此该是另一个不早返回的方法了:

private boolean byteArrayCheck3b(final byte[] array) {
    int hits = 0;
    for (byte b : array) {
        if (b != 0) {
            hits++;
        }
    }
    return (hits == 0);
}

这样,我们仍然可以从分支预测中受益,但是请确保不能早日返回。

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

基准:byteArrayCheck12 /迭代:700000 /每次迭代时间:1327.2817714285713ns
基准:byteArrayCheck3 /迭代:700000 /每次迭代时间:753.31376ns
基准:byteArrayCheck3b /迭代:700000 /每次迭代时间:1506.6772842857142ns
基准:byteArrayCheck4 /迭代:700000 /每次迭代时间:21655.950115714284ns
基准测试:byteArrayCheck5 /迭代次数:700000 /每次迭代时间:10608.70917857143ns

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

更新 ,使用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[]分别 之前 的基准,也有人neccessary到最大堆大小设置为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;
}

得到了以下结果:

基准:byteArrayCheck8 /迭代:700000 /每次迭代时间:259.8157614285714ns
基准:byteArrayCheck9 /迭代:700000 /每次迭代时间:266.38013714285717ns

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



 类似资料:
  • 问题内容: Pandigital数字是包含数字1..number长度的数字。 例如123、4312和967412385。 我已经解决了许多Euler项目问题​​,但是Pandigital问题总是超过一分钟法则。 这是我的泛指功能: 创建自己的函数并使用此方法对其进行测试 使用此循环,您应该获得720个pandigital号码。我的平均时间是500毫秒。 我正在使用Java,但问题是所有语言都可以使

  • 问题内容: 我有一个的条目: 目前,我正在检查它是否包含真像这样: 这是检查布尔数组的 最快 方法吗?如果不是,执行此检查的最快方法是什么? 编辑: 通过在Android 4.03 Samsung S2设备上将其作为应用程序运行,我对您的答案中的方法进行了计时,如下所示: 在五次跑步中的时间排名最高,排名第一: 在5334和11584 ns之间: } return false; 在160542和1

  • 问题内容: 如何检查数字是否为小数? 使用Objective-C: 问题答案: 如果将数字四舍五入(可以使用下限功能来完成),然后从原始数字中减去该数字,则会得到两者之间的差。 编辑- 我的原始答案建议计算数字与其下限等值之间的差,以查看小数点后是否有任何单位。但是,如后面所述,可能存在舍入错误,这会导致内存中值的表示与实际含义略有不同。 例如,3.0可以表示为3.00000000000001,因

  • 问题内容: 检查字符串是否仅包含字母数字字符的最快方法是什么。 我有一些代码会占用大量CPU,我想知道是否有比使用预编译正则表达式更快的方法。 问题答案: 我已经编写了使用正则表达式(根据其他答案)与不使用正则表达式进行比较的测试。在运行Java 1.6的四核OSX10.8计算机上进行的测试 有趣的是,使用正则表达式比手动迭代字符串要慢5到10倍。此外,该功能比的速度略快。一种支持允许扩展Unic

  • 问题内容: 我有一个游标,带有来自选择的值,我想根据我是否发现任何行来做点什么。 这似乎不起作用,有帮助吗? 问题答案: 您需要在使用%FOUND属性之前对游标执行FETCH。将您的代码更改为类似 请注意,您可能需要将变量添加到FETCH语句的INTO子句中,在TABLE1和TABLE2中的每一列都需要添加一个变量。还要注意,编写此游标的方式可能会获得比预期更多的返回行。因为没有指定连接条件,所以

  • 问题内容: 有没有比这更好,更优雅(和/或更快)的方式 …? 编辑 :因为我不能选择两个答案,所以我要使用正则表达式,因为a)很优雅,并且b)说“ Jon Skeet解决了问题”是一种重言式,因为Jon Skeet自己就是所有问题的解决方案。 问题答案: 我不认为Java有任何内置功能可以更快,更可靠地完成此操作,但前提是您稍后希望使用Double.valueOf(或类似功能)对其进行解析。 我会