0

Я читаю «Java 8 in Action» (Рауль-Габриэль Урма, Марио Фуско и Алан Майкрофт), раздел 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()». Использование «return;» в lambda, прошедшем в метод .mapToInt(), будет устранена необходимость использования метода «.filter()». Похоже, это будет улучшение потоков api.

+1

Я думаю, этот вопрос лучше обсудить на: http://codereview.stackexchange.com/ – Flown

+0

@Flown, хороший момент, я это сделал. –

+0

@Alexis, спасибо, что тег потока должен быть java-потоком, вы правы, конечно. –

ответ

3

Во-первых, вы «бенчмарк» сильно испорчены. Скорее всего, всегда будет казаться, что последние варианты быстрее, потому что больше общего кода (например, методы Stream API) было скомпилировано/оптимизировано, когда они выполнены. См. “How do I write a correct micro-benchmark in Java?”

Кроме того, «%1» не требуется, если вы хотите проверить, является ли число целым числом. Вы можете наложить на int, что позволит удалить часть дроби и сравнить ее с оригиналом double. Кроме того, вам не нужно map к int[] массива, когда вы уже знаете, что то, что вы собираетесь печатать, будет String:

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])) 
    ); 
+1

1+ просто потратить время на анализ этого кода. – Eugene

+0

@ Хольгер, большое спасибо за комментарии. Я ценю ваше понимание. Я не знаком с бенчмарками, поэтому я буду вам по-другому. Интересно, что ваш листинг от double до int в методе filter() более интенсивный, чем сравнение% 1, и ваш окончательный вызов map() с помощью String.format(), в то время как хороший способ сделать это, не лучше чем использование моего метода со значениями массива. На самом деле вызов map() является дополнительным шагом.Тем не менее, я по-прежнему ценю ваш подход. –

+0

Разумеется, листинг с 'double' на' int' намного * дешевле, чем операция modulo, но вы все еще можете надеяться, что оптимизатор JVM способен преобразовать ваш конкретный '...% 1' в нечто более эффективное, возможно, именно к актерскому составу 'int'. Я не вижу, как 'map' to' String' является «дополнительным шагом», по сравнению с вашим исходным кодом, имеющим «карту» в массив * и * преобразование этого массива в 'String'. Тем не менее, последний пример приходит без какого-либо шага сопоставления ... – Holger

 Смежные вопросы

  • Нет связанных вопросов^_^