Lambda 数值流实例优化
我正在阅读《Java 8在行动》(由Raoul Gabriel Urma、Mario Fusco和Alan Mycroft撰写),第5.6.3节,第116和117页。给出的代码处理所谓的“勾股三元组”的计算。第116页显示了第一次尝试,第117页显示了生成这些三元组的改进尝试,两者都使用“.rangeClosed()”方法 我发现了一些超出本书范围的优化,我想在这里与大家分享。我做了一些简单的“System.currentTimeMillis()”计算,看看我的修改是否是改进,它们似乎比书中发现的稍微好一些。您能为该代码提供更好的改进、解释或度量吗Lambda 数值流实例优化,lambda,functional-programming,java-8,java-stream,pythagorean,Lambda,Functional Programming,Java 8,Java Stream,Pythagorean,我正在阅读《Java 8在行动》(由Raoul Gabriel Urma、Mario Fusco和Alan Mycroft撰写),第5.6.3节,第116和117页。给出的代码处理所谓的“勾股三元组”的计算。第116页显示了第一次尝试,第117页显示了生成这些三元组的改进尝试,两者都使用“.rangeClosed()”方法 我发现了一些超出本书范围的优化,我想在这里与大家分享。我做了一些简单的“System.currentTimeMillis()”计算,看看我的修改是否是改进,它们似乎比书中发现
public void test() {
long time1 = System.currentTimeMillis();
/*
* From text, page 116
*/
IntStream.rangeClosed(1, 100).boxed()
.flatMap(a -> IntStream.rangeClosed(a, 100)
.filter(b -> Math.sqrt(a*a + b*b) % 1 == 0)
.mapToObj(b -> new int[]{a, b, (int)Math.sqrt(a*a + b*b)})
)
.forEach(c -> System.out.println("["+c[0]+" "+c[1]+" "+c[2]+"]"));
long time2 = System.currentTimeMillis();
System.out.println();
long time3 = System.currentTimeMillis();
/*
* From text, page 117, I added "map(...)" so that end result are ints, not doubles
*/
IntStream.rangeClosed(1, 100).boxed()
.flatMap(a -> IntStream.rangeClosed(a, 100)
.mapToObj(b -> new double[]{a, b, Math.sqrt(a*a + b*b)})
.filter(t -> t[2] % 1 == 0)
.map(b -> new int[]{(int)b[0], (int)b[1], (int)b[2]})
)
.forEach(c -> System.out.println("["+c[0]+" "+c[1]+" "+c[2]+"]"));
long time4 = System.currentTimeMillis();
System.out.println();
long time5 = System.currentTimeMillis();
/*
* My optimization attempt #1: now mapToObj(...) has c%1!=0 conditional, filter checks array element not null
*/
IntStream.rangeClosed(1, 100).boxed()
.flatMap(a -> IntStream.rangeClosed(a, 100)
.mapToObj(b -> {
double c = Math.sqrt(a*a + b*b);
return new Object[]{a, b, c % 1 == 0 ? (int)c : null};
})
.filter(d -> d[2] != null)
.map(e -> new int[]{(int)e[0], (int)e[1], (int)e[2]})
)
.forEach(f -> System.out.println("["+f[0]+" "+f[1]+" "+f[2]));
long time6 = System.currentTimeMillis();
System.out.println();
long time7 = System.currentTimeMillis();
/*
* My optimization attempt #2: now mapToObj(...) has c%1!=0 conditional, filter checks "array element" not 0
*/
IntStream.rangeClosed(1, 100).boxed()
.flatMap(a -> IntStream.rangeClosed(a, 100)
.mapToObj(b -> {
double c = Math.sqrt(a*a + b*b);
return new int[]{a, b, c % 1 == 0 ? (int)c : 0 };
})
.filter(t -> t[2] != 0)
)
.forEach(d -> System.out.println("["+d[0]+" "+d[1]+" "+d[2]+"]"));
long time8 = System.currentTimeMillis();
System.out.println();
long time9 = System.currentTimeMillis();
/*
* My optimization attempt #3: now mapToObj(...) has c%1!=0 conditional, filter checks "collection element" not null
*/
IntStream.rangeClosed(1, 100).boxed()
.flatMap(a -> IntStream.rangeClosed(a, 100)
.mapToObj(b -> {
double c = Math.sqrt(a*a + b*b);
return (c % 1 != 0) ? null : new int[]{a, b, (int)c};
})
.filter(t -> t != null)
)
.forEach(d -> System.out.println("["+d[0]+" "+d[1]+" "+d[2]+"]"));
long time10 = System.currentTimeMillis();
System.out.println();
long timeDelta1 = time2 - time1;
long timeDelta2 = time4 - time3;
long timeDelta3 = time6 - time5;
long timeDelta4 = time8 - time7;
long timeDelta5 = time10 - time9;
System.out.println("timeDelta1: " + timeDelta1 + ", timeDelta2: " + timeDelta2 + ", timeDelta3: " + timeDelta3 + ", timeDelta4: " + timeDelta4 + ", timeDelta5: " + timeDelta5);
}
public static void main(String[] args){
ReduceTest reduceTest = new ReduceTest();
reduceTest.test();
}
注意:似乎可以在“.forEach()”方法中使用“return;”,但不能在“.mapToInt()方法中使用。在传递到“.mapToInt()”方法的lambda中使用“return;”将消除使用“.filter()”方法的需要。这似乎是对streams api的改进。首先,您的“基准测试”存在严重缺陷。最有可能出现的情况是,最后的变体速度更快,因为在执行时,更多的共享代码(例如流API方法)已经编译/优化。看 此外,当您想测试一个数字是否为整数时,不需要“
%1
”。您可以强制转换为int
,这将删除分数部分,并将其与原始的double
进行比较。此外,当您已经知道要打印的内容将是一个字符串时,您不需要将映射到int[]
数组:
IntStream.rangeClosed(1, 100).boxed()
.flatMap(a -> IntStream.rangeClosed(a, 100)
.mapToObj(b -> new double[]{a, b, Math.sqrt(a*a + b*b)})
.filter(t -> ((int)t[2]) == t[2])
.map(arr -> String.format("[%.0f %.0f %.0f]", arr[0], arr[1], arr[2]))
)
.forEach(System.out::println);
但是,当然,如果您多次知道需要一个昂贵的函数,如sqrt
,那么预先计算它可能是有益的,特别是当有可能在不使用昂贵函数甚至浮点算术的情况下进行准备时:
int[] square = new int[20001];
IntStream.rangeClosed(1, 141).forEach(i -> square[i*i]=i);
IntStream.rangeClosed(1, 100).boxed()
.flatMap(a -> IntStream.rangeClosed(a, 100)
.filter(b -> square[a*a+b*b]!=0)
.mapToObj(b -> String.format("[%d %d %d]", a, b, square[a*a+b*b]))
)
.forEach(System.out::println);
请注意,这是为数不多的几种情况之一,其中嵌套的forEach
可以替代flatMap
:
int[] square=new int[20001];
IntStream.rangeClosed(1, 141).forEach(i -> square[i*i]=i);
IntStream.rangeClosed(1, 100)
.forEach(a -> IntStream.rangeClosed(a, 100)
.filter(b -> square[a*a+b*b]!=0)
.forEach(b -> System.out.printf("[%d %d %d]%n", a, b, square[a*a+b*b]))
);
首先,您的“基准”存在严重缺陷。最有可能出现的情况是,最后的变体速度更快,因为在执行时,更多的共享代码(例如流API方法)已经编译/优化。看
此外,当您想测试一个数字是否为整数时,不需要“%1
”。您可以强制转换为int
,这将删除分数部分,并将其与原始的double
进行比较。此外,当您已经知道要打印的内容将是一个字符串时,您不需要将映射到int[]
数组:
IntStream.rangeClosed(1, 100).boxed()
.flatMap(a -> IntStream.rangeClosed(a, 100)
.mapToObj(b -> new double[]{a, b, Math.sqrt(a*a + b*b)})
.filter(t -> ((int)t[2]) == t[2])
.map(arr -> String.format("[%.0f %.0f %.0f]", arr[0], arr[1], arr[2]))
)
.forEach(System.out::println);
但是,当然,如果您多次知道需要一个昂贵的函数,如sqrt
,那么预先计算它可能是有益的,特别是当有可能在不使用昂贵函数甚至浮点算术的情况下进行准备时:
int[] square = new int[20001];
IntStream.rangeClosed(1, 141).forEach(i -> square[i*i]=i);
IntStream.rangeClosed(1, 100).boxed()
.flatMap(a -> IntStream.rangeClosed(a, 100)
.filter(b -> square[a*a+b*b]!=0)
.mapToObj(b -> String.format("[%d %d %d]", a, b, square[a*a+b*b]))
)
.forEach(System.out::println);
请注意,这是为数不多的几种情况之一,其中嵌套的forEach
可以替代flatMap
:
int[] square=new int[20001];
IntStream.rangeClosed(1, 141).forEach(i -> square[i*i]=i);
IntStream.rangeClosed(1, 100)
.forEach(a -> IntStream.rangeClosed(a, 100)
.filter(b -> square[a*a+b*b]!=0)
.forEach(b -> System.out.printf("[%d %d %d]%n", a, b, square[a*a+b*b]))
);
我认为这个问题最好在:@Flown,好的,我已经这样做了。@Alexis,谢谢,stream标记应该是java stream,你当然是对的。你可以在每个lambda表达式中使用return
,但是,当然`如果函数应该返回一个值,您的return
语句必须返回一个值。@Holger,明白了,但我的意思是,如果在条件表达式在某些迭代中没有返回值的情况下,使用不带值的return语句会很好。我想这个问题最好在:@Flown,good point,我已经这样做了。@Alexis,谢谢,stream标记应该是java stream,你当然是对的。你可以在每个lambda表达式中使用return
,但是,当然`如果函数应该返回一个值,你的return
语句必须返回一个值。@Holger,明白了,但我的意思是,如果在条件表达式在某些迭代中没有返回值的情况下使用不带值的return语句会更好。@Holger,非常感谢您的评论。我欣赏你的洞察力。我不熟悉基准测试,因此在这一点上,我不得不与你们有所不同。我想知道filter()方法中从double到int的转换是否比%1比较更密集,而使用String.format()的最后一次map()调用虽然是一种很好的方法,但并不比使用数组值的方法更好。实际上,map()调用是一个额外的步骤。当然,从double
到int
的转换要比模运算便宜得多,但你仍然可以希望JVM的优化器能够将特定的…%1
转换为更有效的转换,也许正是转换为int
。我不明白,与原始代码将映射到数组并将该数组转换为字符串相比,映射到字符串
是一个“额外的步骤”。然而,最后一个例子没有任何映射步骤…@Holger,仔细想想,你的方法非常好。谢谢你的评论。@Holger,非常感谢你的评论。我欣赏你的洞察力。我不熟悉基准测试,因此在这一点上,我不得不与你们有所不同。我想知道filter()方法中从double到int的转换是否比%1比较更密集,而使用String.format()的最后一次map()调用虽然是一种很好的方法,但并不比使用数组值的方法更好。实际上,map()调用是一个额外的步骤。当然,从double
到int
的转换比