如果使用java 8流功能,为什么迭代列表需要更多时间?

时间:2017-08-05 14:54:46

标签: java foreach parallel-processing java-8 parallel.foreach

public static void main(String[] args) {
        List<String> data = new ArrayList<>();
        for (int i = 0; i < 10000000; i++) {
            data.add("data" + i);
        }
        System.out.println("parallel stream start time" + System.currentTimeMillis());
        data.parallelStream().forEach(x -> {
            System.out.println("data -->" + x);
        });
        System.out.println("parallel stream end time" + System.currentTimeMillis());

        System.out.println("simple stream start time" + System.currentTimeMillis());
        data.stream().forEach(x -> {
            System.out.println("data -->" + x);
        });
        System.out.println("simple stream end time" + System.currentTimeMillis());

        System.out.println("normal foreach start time" + System.currentTimeMillis());
        for (int i = 0; i < data.size(); i++) {
            System.out.println("data -->" + data.get(i));
        }
        System.out.println("normal foreach end time" + System.currentTimeMillis());
    }

输出

并行流开始时间1501944014854

并行流结束时间1501944014970

简单流开始时间1501944014970

简单流结束时间1501944015036

正常的foreach开始时间1501944015036

正常的foreach结束时间1501944015040

总时间

简单流 - &gt; 66

Parellem流 - &gt; 116

简单的foreach - &gt; 4

在许多博客中写道,parallelStream是由线程内部管理的分布式任务并行执行并自动收集..

但是按照上面的实验,显然注意到并行流比简单的流和正常的foreach花费更多的时间。

如果并行执行,为什么花费更多时间?在项目中使用是否很好,因为此功能降低了性能?

先谢谢

1 个答案:

答案 0 :(得分:1)

  1. 您的测试基于 I / O操作(最昂贵的操作)
  2. 如果要使用并行流,则必须考虑线程创建时间开销。因此,只有当您的操作从中获益时才能使用它(这是重型操作的情况)。如果没有,那么只需使用普通流或常规for循环。
  3. 衡量的基本规则:

    1. 不要使用I / O操作。
    2. 重复相同的测试多一次
    3. 因此,如果我们必须再次重新制定测试场景,那么我们可能会有一个测试助手类定义如下:

      import java.util.HashMap;
      import java.util.Map;
      import java.util.UUID;
      
      public class Benchmark {
      
          public static <T> T performTest(Callable<T> callable, int iteration, String name) throws Exception {
              Map<String, Iteraion> map = new HashMap<>();
              T last = null;
              for (int i = 0; i < iteration; i++) {
                  long s = System.nanoTime();
                  T temp = callable.call();
                  long f = System.nanoTime();
                  map.put(UUID.randomUUID().toString(), new Iteraion(s, f));
                  if (i == iteration - 1) {
                      last = temp;
                  }
              }
              System.out.print("TEST :\t" + name + "\t\t\t");
              System.out.print("ITERATION: " + map.size());
              long sum = 0l;
              for (String i : map.keySet()) {
                  sum += (map.get(i).finish - map.get(i).start);
              }
              long avg = (sum / map.size()) / 1000000;
              System.out.println("\t\t\tAVERAGE: " + avg + " ms");
              return last;
      
          }
      
          public interface Callable<T> {
              T call() throws Exception;
          }
      
          static class Iteraion {
              Long start;
              Long finish;
      
              public Iteraion(Long s, Long f) {
                  start = s;
                  finish = f;
              }
          }
      }
      

      现在我们可以使用不同的操作执行相同的测试。以下代码显示了使用两种不同方案执行的测试。

      import java.util.ArrayList;
      import java.util.List;
      import static java.lang.Math.*;
      
      @SuppressWarnings("unused")
      public class Test {
      
          public static void main(String[] args) {
              try {
                  final int iteration = 100;
                  final List<String> data = new ArrayList<>();
      
                  for (int i = 0; i < 10000000; i++) {
                      data.add("data" + i);
                  }
      
                  /**
                   * Scenario 1
                   */
                  Benchmark.performTest(new Callable<Void>() {
      
                      @Override
                      public Void call() throws Exception {
                          data.parallelStream().forEach(x -> {
                              x.trim();
                          });
                          return (Void) null;
                      }
      
                  }, iteration, "PARALEL_STREAM_ASSIGN_VAL");
      
                  Benchmark.performTest(new Callable<Void>() {
      
                      @Override
                      public Void call() throws Exception {
                          data.stream().forEach(x -> {
                              x.trim();
                          });
                          return (Void) null;
                      }
                  }, iteration, "NORMAL_STREAM_ASSIGN_VAL");
      
                  Benchmark.performTest(new Callable<Void>() {
      
                      @Override
                      public Void call() throws Exception {
                          for (int i = 0; i < data.size(); i++) {
                              data.get(i).trim();
                          }
                          return (Void) null;
                      }
                  }, iteration, "NORMAL_FOREACH_ASSIGN_VAL");
      
                  /**
                   * Scenario 2
                   */
                  Benchmark.performTest(new Callable<Void>() {
      
                      @Override
                      public Void call() throws Exception {
                          data.parallelStream().forEach(x -> {
                              Integer i = Integer.parseInt(x.substring(4, x.length()));
                              double d = tan(atan(tan(atan(i))));
                          });
                          return (Void) null;
                      }
      
                  }, iteration, "PARALEL_STREAM_COMPUTATION");
      
                  Benchmark.performTest(new Callable<Void>() {
      
                      @Override
                      public Void call() throws Exception {
                          data.stream().forEach(x -> {
                              Integer i = Integer.parseInt(x.substring(4, x.length()));
                              double d = tan(atan(tan(atan(i))));
                          });
                          return (Void) null;
                      }
                  }, iteration, "NORMAL_STREAM_COMPUTATION");
      
                  Benchmark.performTest(new Callable<Void>() {
      
                      @Override
                      public Void call() throws Exception {
                          for (int i = 0; i < data.size(); i++) {
                              Integer x = Integer.parseInt(data.get(i).substring(4, data.get(i).length()));
                              double d = tan(atan(tan(atan(x))));
                          }
                          return (Void) null;
                      }
                  }, iteration, "NORMAL_FOREACH_COMPUTATION");
      
              } catch (Exception e) {
                  e.printStackTrace();
              }
      
          }
      }
      
      1. 对于包含 10_000_000个元素的列表,第一个方案使用trim()方法执行相同的测试100次,因此它使用并行流,然后一个普通流并持续旧学校 for loop
      2. 第二种方案使用与第一种方案相同的技术对同一列表执行一些相对繁重的操作,如tan(atan(tan(atan(i))))
      3. 结果是:

        // First scenario, average times
        Parallel stream:  78 ms
        Regular stream:  113 ms
        For-loop:        110 ms
        
        // Second scenario, average times
        Parallel stream:  1397 ms
        Regular stream:   3866 ms
        For-loop:         3826 ms
        

        请注意,您可以调试上面的代码,然后您会注意到,对于并行流程,程序会在名称[ForkJoinPool-1][ForkJoinPool-2]和{{1}下创建三个额外的线程 }。

        修改 顺序流和 for-loop 使用来电者的话题