Java 检查字节数组是否全为零的最快方法
我有一个Java 检查字节数组是否全为零的最快方法,java,arrays,performance,primitive,Java,Arrays,Performance,Primitive,我有一个字节[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 byte[]b=新字节[4096]; b[4095]=1; 对于(int i=0;i我在第一次对所有字节求和时重写了这个答案,但是这是不正确的,因为Java
字节[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
byte[]b=新字节[4096];
b[4095]=1;
对于(int i=0;i我在第一次对所有字节求和时重写了这个答案,但是这是不正确的,因为Java有签名的字节,因此我需要或。我还将JVM预热更改为现在正确。
你最好的选择就是简单地循环所有的值
我想你有三个主要的选择:
或所有元素,并检查总和
进行无分支比较
与分支进行比较
我不知道使用Java添加字节的性能有多好(低级别性能),我知道如果进行分支比较,Java使用(低级别)分支预测器
因此,我预计以下情况将发生:
byte[] array = new byte[4096];
for (byte b : array) {
if (b != 0) {
return false;
}
}
当分支预测器仍在播种时,在前几个迭代中比较相对缓慢
由于分支预测,非常快的分支比较,因为每个值无论如何都应该为零
如果它将达到非零值,那么分支预测器将失败,导致比较速度减慢,但是,如果您想返回false,那么计算也将结束。我认为一个分支预测失败的代价比继续在数组上迭代的代价小一个数量级
此外,我认为应该允许for(byte b:array)
,因为它应该直接编译到索引数组迭代中,据我所知,在代码内联之前,没有原语数组迭代器会导致一些额外的方法调用(如在列表上迭代)
更新
我编写了自己的基准测试,它给出了一些有趣的结果……不幸的是,我无法使用任何现有的基准测试工具,因为它们很难正确安装
我还决定将选项1和选项2组合在一起,因为我认为它们实际上与无分支的you通常或所有选项(减去条件)相同,然后检查最终结果。这里的条件是x>0
,因此or为零可能是一个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
方法无效,因为它注意到(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[]
的建议后,我认为这是值得研究的。然而,这些尝试可能不再与原始答案完全一致,但可能仍然很有趣
首先,我将基准方法更改为使用泛型:
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");
}
结果如下:
基准:byteArrayCheck8/迭代次数:700000/每次迭代次数:259.8157614285714ns
基准:byteArrayCheck9/迭代次数:700000/每次迭代次数:266.38013714285717ns
如果有可能以这种格式获取字节,那么这个路径可能值得探索。但是,在基准方法内进行转换时,每次迭代的时间大约为2000纳秒,因此,当您需要自己进行转换时,就不值得探索了。我认为理论上您的方法是最快的,实际上是最快的ice您可能能够使用其中一位评论员建议的更大的比较(1字节比较需要1条指令,但在64位系统上8字节比较也需要1条指令)
同样,在更接近硬件的语言(C和变体)中,您可以使用一种称为矢量化的方法,在这种方法中,您可以同时执行许多比较/添加操作。看起来Java仍然并没有对它的本机支持,但基于此,您可能能够使用它
与其他评论一样,我想说,对于4k缓冲区,可能不值得花时间尝试和优化它(除非经常调用它)
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");
}
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;
}
LongBuffer longBuffer = ByteBuffer.wrap(b).asLongBuffer();
while (longBuffer.hasRemaining()) {
if (longBuffer.get() != 0) {
return false;
}
}
return true;
byte[] arr = randomByteArray();
assert Arrays.equals(arr, new byte[arr.length]);
public static boolean isEmpty(final byte[] data){
return IntStream.range(0, data.length).parallel().allMatch(i -> data[i] == 0);
}