【问题标题】:Numerical Streams Example Optimization数值流示例优化
【发布时间】:2017-01-08 18:44:32
【问题描述】:

我正在阅读“Java 8 In Action”(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();
}

注意:您似乎可以使用“return;”在“.forEach()”方法中,但不在“.mapToInt()”方法中。使用“返回;”在传递给“.mapToInt()”方法的 lambda 中,将不再需要使用“.filter()”方法。这似乎是对流 api 的改进。

【问题讨论】:

  • 我认为这个问题最好在以下位置讨论:codereview.stackexchange.com
  • @Flown,很好,我已经做到了。
  • @Alexis,谢谢,那个流标签应该是 java-stream,你当然是对的。
  • 你可以在每个 lambda 表达式中使用return,但是,当然`如果函数应该返回一个值,你的return 语句必须返回一个值。
  • @Holger,理解,但我的意思是,如果在条件表达式没有要返回的值的情况下使用没有值的 return 语句会很好某些迭代。

标签: lambda functional-programming java-8 java-stream pythagorean


【解决方案1】:

首先,您的“基准”存在严重缺陷。最后一个变体很可能总是看起来更快,因为更多的共享代码(例如 Stream API 方法)在执行时已经编译/优化。见“How do I write a correct micro-benchmark in Java?”

除此之外,当您想测试一个数字是否为整数时,“%1”是不必要的。您可以转换为int,这将删除小数部分,并将其与原始double 进行比较。此外,当您已经知道要打印的内容将是 String 时,您不需要将 map 转换为 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]))
    );

【讨论】:

  • @Holger,非常感谢 cmets。我很欣赏你的洞察力。我对基准不熟悉,所以我必须与你有所不同。我想知道您在 filter() 方法中从 double 到 int 的转换是否比 %1 比较更密集,并且您使用 String.format() 进行的最终 map() 调用虽然是一种好方法,但并不是更好而不是将我的方法与数组值一起使用。事实上, map() 调用是一个额外的步骤。也就是说,我仍然很欣赏你的方法。
  • 当然,从doubleint 的转换比模运算便宜很多,但您仍然可以希望JVM 的优化器能够转换您将特定的…%1 转换为更有效的方法,也许正是转换为int。我不明白,mapString 是一个“额外的步骤”,与您的原始代码具有 map 到数组 将该数组转换为String。但是,最后一个示例没有任何映射步骤……
  • @Holger,再想想,你的方法非常好。谢谢他们。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2011-04-12
  • 2018-11-17
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多