sen*_*iwu 4 java biginteger java-8
我正在玩Java 8,并使用程序比较Java 6中的一些东西来计算大型列表中偶数的总和
Java 8
public class ListOperationsJava8 {
static List<BigInteger> list = new LinkedList<>();
public static void main(String[] args) {
createList();
long start = System.currentTimeMillis();
/*System.out.println(list.parallelStream().
filter(n -> n.mod(new BigInteger("2")).equals(BigInteger.ZERO)).
mapToInt(BigInteger::intValue).sum()); --> gives result -1795017296 */
System.out.println(list.parallelStream().
filter(n -> n.mod(new BigInteger("2")).equals(BigInteger.ZERO)).
mapToLong(BigInteger::longValue).sum());
long end = System.currentTimeMillis();
System.out.println("Time taken using Java 8: " + (end - start) + " ms");
}
private static void createList() {
for (int i = 0; i < 100000; i++) {
list.add(new BigInteger(String.valueOf(i)));
}
}
}
Run Code Online (Sandbox Code Playgroud)
Java 6
public class ListOperationsClassic {
static List<BigInteger> list = new LinkedList<BigInteger>();
public static void main(String[] args) {
createList();
long start = System.currentTimeMillis();
BigInteger sum = BigInteger.ZERO;
for(BigInteger n : list) {
if(n.mod(new BigInteger("2")).equals(BigInteger.ZERO))
sum = sum.add(n);
}
System.out.println(sum);
long end = System.currentTimeMillis();
System.out.println("Time taken using Java 6: " + (end - start) + " ms");
}
private static void createList() {
for (int i = 0; i < 100000; i++) {
list.add(new BigInteger(String.valueOf(i)));
}
}
}
Run Code Online (Sandbox Code Playgroud)
我有两个问题
mapToInt(BigInteger::intValue).sum())减少值,但得到了负面结果-1795017296!为什么?根据我的理解,预期结果2499950000
本身在可以表达的范围内int.parallelStream和/或reduce操作和普通的旧循环更好?以下是其中一个结果:
2499950000
Time taken using Java 6: 52 ms
2499950000
Time taken using Java 8: 249 ms
Run Code Online (Sandbox Code Playgroud)
这是我快速而肮脏的基准测试,允许在每次测试之间进行JIT热身和GC测试.结果:
请注意,我已修改您的代码以使三个测试尽可能相等 - 特别是对于lambdas,我使用缩减来添加BigIntegers而不是转换为long.
我还删除了new BigInteger("2")每次迭代中不必要的创建.
public class Test1 {
static List<BigInteger> list = new LinkedList<>();
static BigInteger TWO = new BigInteger("2");
public static void main(String[] args) {
createList();
long sum = 0;
//warm-up
for (int i = 0; i < 100; i++) {
sum += forLoop().longValue();
sum += lambda().longValue();
sum += parallelLambda().longValue();
}
{
System.gc();
long start = System.currentTimeMillis();
for (int i = 0; i < 100; i++) sum += forLoop().longValue();
long end = System.currentTimeMillis();
System.out.println("Time taken using for loop: " + (end - start) + " ms");
}
{
System.gc();
long start = System.currentTimeMillis();
for (int i = 0; i < 100; i++) sum += lambda().longValue();
long end = System.currentTimeMillis();
System.out.println("Time taken using lambda: " + (end - start) + " ms");
}
{
System.gc();
long start = System.currentTimeMillis();
for (int i = 0; i < 100; i++) sum += parallelLambda().longValue();
long end = System.currentTimeMillis();
System.out.println("Time taken using parallelLambda: " + (end - start) + " ms");
}
}
private static void createList() {
for (int i = 0; i < 100000; i++) {
list.add(new BigInteger(String.valueOf(i)));
}
}
private static BigInteger forLoop() {
BigInteger sum = BigInteger.ZERO;
for(BigInteger n : list) {
if(n.mod(TWO).equals(BigInteger.ZERO))
sum = sum.add(n);
}
return sum;
}
private static BigInteger lambda() {
return list.stream().
filter(n -> n.mod(TWO).equals(ZERO)).
reduce(ZERO, BigInteger::add);
}
private static BigInteger parallelLambda() {
return list.parallelStream().
filter(n -> n.mod(TWO).equals(ZERO)).
reduce(ZERO, BigInteger::add);
}
}
Run Code Online (Sandbox Code Playgroud)
对于你的第一个问题:你的价值可能大于Integer.MAX_VALUE,因此溢出,这意味着它将从外部表示为开始Integer.MIN_VALUE.溢出是这里的关键字.
对于第二部分,我不确切地知道它为什么不同,最终可能在字节码中找到,但这真的是一个问题,还是一个过早优化的情况?如果它是第一个,那么你应该担心,如果是第二种情况,那么你不应该注意它变慢.
我从你的代码中观察到的一个区别是:
BigInteger sum,并.add()在其上调用方法.long变量,并在内部添加变量.此外,您使用的是parallelStream()这里是非常快的计算,这可能会导致问题,创造新的底层线程实际成本比只使用一个普通的线性更多的时间stream().
此外,如果您真的在测量速度,那么您应该进行更多的测试,而不是每个测试用例运行一次,时间也可能取决于其他因素 - JVM,CPU的时钟速度等.
作为上一次编辑,您的Java 8代码实际上并不代表您的Java 6代码,它应该是:
final BigInteger sum = BigInteger.ZERO;
list.stream()
.filter(n -> n.mod(new BigInteger("2")).equals(BigInteger.ZERO))
.forEach(n -> { sum = sum.add(n); });
Run Code Online (Sandbox Code Playgroud)
要完全代表您的Java 6代码,请注意,这里的介绍sum并不是一件好事,如果您将其用于并行计算,则此代码将无法正常工作.
最后一次编辑,正确显示它应该如何在Java 8中完成,看起来是正确的,适用于并行版本,甚至可以在线性版本中获得额外的性能:
Optional<BigInteger> sum = list.stream()
.filter(n -> n.mod(new BigInteger("2")).equals(BigInteger.ZERO))
.reduce((n1, n2) -> n1.add(n2));
System.out.println(sum.get());
Run Code Online (Sandbox Code Playgroud)
这里我使用的是reduce()运算符,它使用BinaryOperator<T>as参数,我用它来将BigIntegers加在一起.
需要注意的是,它带有一个事实,即流可能是空的,所以它不知道是否有值,所以它返回一个Optional<T>.
请仔细注意,通常你需要调用sum.isPresent(),以检查其是否实际上有一个值,但在这一点,我们知道,必须有一个价值!list.isEmpty(),因此,我们继续呼吁sum.get()直接.
最后,我在PC上测试了不同版本的100万个数字:
因此,由于这不是真正适当的微基准测试,我认为结论是你用于这种目的并不重要.