Micro Benchmark - pierwsze zwarcie - update listy bazujac na innej liscie

0

Siemka,
miałem do zrobienia aktualizację wartości w jednej liście bazując na innej, aby wykonać tą operację użyłem streamów. Następnie nadeszła optymalizacja i zrodziło się pytanie, o ile to jest szybsze, a może tylko ładniej wygląda. Kiedyś słyszałem gdzieś o micro benchmarkach to czemu nie spróbować i oto me dzieło:

import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.Setter;
import org.openjdk.jmh.annotations.*;
import org.openjdk.jmh.infra.Blackhole;

import java.math.BigDecimal;
import java.util.*;
import java.util.concurrent.TimeUnit;
import java.util.function.Function;
import java.util.stream.Collectors;

@Fork(1)
@Warmup(iterations = 10)
@Measurement(iterations = 10)
@BenchmarkMode(Mode.AverageTime)
@OutputTimeUnit(TimeUnit.NANOSECONDS)
public class UpdateObjectInOneListBasedOnSecondOne {

    @AllArgsConstructor
    @Getter
    @Setter
    public static class BookOverallData {
        private Long idOfBook;
        private String title;
        private String authour;
        private BigDecimal basePrice;
        private Integer discountRate;
    }

    @AllArgsConstructor
    @Getter
    public static class TimeDiscount {
        private Long idOfBook;
        private Integer discountRate;
    }

    private Set<BookOverallData> getListWithBooks(){

        return Set.of(new BookOverallData(1L, "1984", "George Orwell", BigDecimal.valueOf(25.6), 10),
                new BookOverallData(2L, "Sentimental Education", "Gustave Flaubert", BigDecimal.valueOf(12.3), 0),
                new BookOverallData(3L, "Absalom, Absalom!", "William Faulkner", BigDecimal.valueOf(32.62), 30),
                new BookOverallData(4L, "The Hunger Games", "Suzanne Collins", BigDecimal.valueOf(12.45), 40),
                new BookOverallData(5L, "To Kill a Mockingbird", "Harper Lee", BigDecimal.valueOf(10.6), 10),
                new BookOverallData(6L, "Pride and Prejudice", "Jane Austen", BigDecimal.valueOf(15.4), 0),
                new BookOverallData(7L, "The Book Thief", "Markus Zusak", BigDecimal.valueOf(9.99), 15),
                new BookOverallData(8L, "Animal Farm", "George Orwell", BigDecimal.valueOf(22.0), 24),
                new BookOverallData(9L, "Gone with the Wind", "Margaret Mitchell", BigDecimal.valueOf(12.4), 27),
                new BookOverallData(10L, "The Fault in Our Stars", "John Green", BigDecimal.valueOf(16.00), 17),
                new BookOverallData(11L, "The Giving Tree", "Shel Silverstein", BigDecimal.valueOf(19.99), 15),
                new BookOverallData(12L, "Wuthering Heights", "Emily Brontë", BigDecimal.valueOf(20.0), 11),
                new BookOverallData(13L, "The Da Vinci Code", "Dan Brown", BigDecimal.valueOf(10.0), 7),
                new BookOverallData(14L, "Memoirs of a Geisha", "Arthur Golden", BigDecimal.valueOf(45.0), 4),
                new BookOverallData(15L, "The Picture of Dorian Gray", "Oscar Wilde", BigDecimal.valueOf(23.99), 16),
                new BookOverallData(16L, "Les Misérables", "Victor Hugo", BigDecimal.valueOf(12.00), 24),
                new BookOverallData(17L, "Jane Eyre", "Charlotte Brontë", BigDecimal.valueOf(55.99), 12),
                new BookOverallData(18L, "Lord of the Flies", "William Golding", BigDecimal.valueOf(24.00), 6),
                new BookOverallData(19L, "Crime and Punishment", "Fyodor Dostoyevsky", BigDecimal.valueOf(17.55), 0),
                new BookOverallData(20L, "The Perks of Being a Wallflower", "Stephen Chbosky", BigDecimal.valueOf(15.99), 0));
    }

    private Set<TimeDiscount> getListWithDiscounts(){
        return Set.of(new TimeDiscount(1L, 20), new TimeDiscount(8L, 20), new TimeDiscount(19L, 20));
    }

    @Benchmark
    public void usedForLoop() {
        final Set<BookOverallData> listWithBooks = getListWithBooks();
        final Set<TimeDiscount> listWithDiscounts = getListWithDiscounts();
        for (BookOverallData bookOverallData : listWithBooks){
            for (TimeDiscount timeDiscount : listWithDiscounts){
                if (timeDiscount.getIdOfBook().equals(bookOverallData.getIdOfBook())){
                    bookOverallData.setDiscountRate(timeDiscount.getDiscountRate() + bookOverallData.getDiscountRate());
                }
            }
        }

        Blackhole.consumeCPU(10);
    }

    @Benchmark
    public void streamOfStream() {
        final Set<BookOverallData> listWithBooks = getListWithBooks();
        final Set<TimeDiscount> listWithDiscounts = getListWithDiscounts();

        listWithBooks.forEach(
                p -> {
                    final Optional<TimeDiscount> promotion = listWithDiscounts.stream().filter(ap -> Objects.equals(ap.getIdOfBook(), p.getIdOfBook())).findFirst();
                    promotion.ifPresent(ap -> p.setDiscountRate(ap.getDiscountRate() + p.getDiscountRate()));
                }
        );

        Blackhole.consumeCPU(10);
    }

    @Benchmark
    public void streamOptimized() {
        final Set<BookOverallData> listWithBooks = getListWithBooks();
        final Set<TimeDiscount> listWithDiscounts = getListWithDiscounts();

        Map<Long, TimeDiscount> accumulator =
                listWithDiscounts.stream()
                        .collect(Collectors.toMap(TimeDiscount::getIdOfBook, Function.identity()));

        listWithBooks.forEach(e ->
                Optional.ofNullable(accumulator.get(e.getIdOfBook()))
                        .ifPresent(p -> e.setDiscountRate(e.getDiscountRate() + p.getDiscountRate()))
        );

        Blackhole.consumeCPU(10);
    }

}

W benchmarku wyszło, że zoptyamlizowane streamy są szybsze od tych pierwotnych, biorąc pod uwagę , że nie tworzę streama stremow dla każdego elementu, to składa się to w jedną całość.
Do benchmarku używam JMH. Dorzucam logi testu w załączniku.
Moje pytanie jest następujące, co można zmienić/dodać aby test był jeszcze bardziej wiarygodny?

0

sprobuj dla roznych wartosci Blackhole tak jak tutaj: https://shipilev.net/blog/2014/nanotrusting-nanotime/

W sumie nie wiem w tym momencie ale sprawdzilbym czy jak Blackhole bedzie na poczatku a nie na koncu to czy wynik bedzie taki sam.

Nie podoba mi sie ze Twoje metody nic nie zwracaja -> to proszenie sie JVM o wyciecie czesci kodu jako nieuzywany i niewykonywanie go.

Nie mam niestety teraz czasu by zaglebiac sie co ten kod robi :(

0

Co u Ciebie robi consumeCPU? Po co?

0

Wrzucam kod po zmianach które radziliście, dzięki wielkie, wyniki jak dla mnie wyglądają lepiej niż w pierwotnej wersji.

@Fork(1)
@Warmup(iterations = 10)
@Measurement(iterations = 10)
@BenchmarkMode(Mode.AverageTime)
@OutputTimeUnit(TimeUnit.NANOSECONDS)
public class UpdateObjectInOneListBasedOnSecondOne {

    @AllArgsConstructor
    @Getter
    @Setter
    public static class BookOverallData {
        private Long idOfBook;
        private String title;
        private String authour;
        private BigDecimal basePrice;
        private Integer discountRate;
    }

    @AllArgsConstructor
    @Getter
    public static class TimeDiscount {
        private Long idOfBook;
        private Integer discountRate;
    }

    private Set<BookOverallData> getListWithBooks(){

        return Set.of(new BookOverallData(1L, "1984", "George Orwell", BigDecimal.valueOf(25.6), 10),
                new BookOverallData(2L, "Sentimental Education", "Gustave Flaubert", BigDecimal.valueOf(12.3), 0),
                new BookOverallData(3L, "Absalom, Absalom!", "William Faulkner", BigDecimal.valueOf(32.62), 30),
                new BookOverallData(4L, "The Hunger Games", "Suzanne Collins", BigDecimal.valueOf(12.45), 40),
                new BookOverallData(5L, "To Kill a Mockingbird", "Harper Lee", BigDecimal.valueOf(10.6), 10),
                new BookOverallData(6L, "Pride and Prejudice", "Jane Austen", BigDecimal.valueOf(15.4), 0),
                new BookOverallData(7L, "The Book Thief", "Markus Zusak", BigDecimal.valueOf(9.99), 15),
                new BookOverallData(8L, "Animal Farm", "George Orwell", BigDecimal.valueOf(22.0), 24),
                new BookOverallData(9L, "Gone with the Wind", "Margaret Mitchell", BigDecimal.valueOf(12.4), 27),
                new BookOverallData(10L, "The Fault in Our Stars", "John Green", BigDecimal.valueOf(16.00), 17),
                new BookOverallData(11L, "The Giving Tree", "Shel Silverstein", BigDecimal.valueOf(19.99), 15),
                new BookOverallData(12L, "Wuthering Heights", "Emily Brontë", BigDecimal.valueOf(20.0), 11),
                new BookOverallData(13L, "The Da Vinci Code", "Dan Brown", BigDecimal.valueOf(10.0), 7),
                new BookOverallData(14L, "Memoirs of a Geisha", "Arthur Golden", BigDecimal.valueOf(45.0), 4),
                new BookOverallData(15L, "The Picture of Dorian Gray", "Oscar Wilde", BigDecimal.valueOf(23.99), 16),
                new BookOverallData(16L, "Les Misérables", "Victor Hugo", BigDecimal.valueOf(12.00), 24),
                new BookOverallData(17L, "Jane Eyre", "Charlotte Brontë", BigDecimal.valueOf(55.99), 12),
                new BookOverallData(18L, "Lord of the Flies", "William Golding", BigDecimal.valueOf(24.00), 6),
                new BookOverallData(19L, "Crime and Punishment", "Fyodor Dostoyevsky", BigDecimal.valueOf(17.55), 0),
                new BookOverallData(20L, "The Perks of Being a Wallflower", "Stephen Chbosky", BigDecimal.valueOf(15.99), 0));
    }

    private Set<TimeDiscount> getListWithDiscounts(){
        return Set.of(new TimeDiscount(1L, 20), new TimeDiscount(8L, 20), new TimeDiscount(19L, 20));
    }

    @Benchmark
    public Set<BookOverallData> usedForLoop() {
        Blackhole.consumeCPU(10);

        final Set<BookOverallData> listWithBooks = getListWithBooks();
        final Set<TimeDiscount> listWithDiscounts = getListWithDiscounts();

        Blackhole.consumeCPU(10);

        for (BookOverallData bookOverallData : listWithBooks){
            for (TimeDiscount timeDiscount : listWithDiscounts){
                if (timeDiscount.getIdOfBook().equals(bookOverallData.getIdOfBook())){
                    bookOverallData.setDiscountRate(timeDiscount.getDiscountRate() + bookOverallData.getDiscountRate());
                }
            }
        }

        Blackhole.consumeCPU(10);

        return listWithBooks;
    }

    @Benchmark
    public Set<BookOverallData> streamOfStream() {
        Blackhole.consumeCPU(10);

        final Set<BookOverallData> listWithBooks = getListWithBooks();
        final Set<TimeDiscount> listWithDiscounts = getListWithDiscounts();

        Blackhole.consumeCPU(10);

        listWithBooks.forEach(
                p -> {
                    final Optional<TimeDiscount> promotion = listWithDiscounts.stream().filter(ap -> Objects.equals(ap.getIdOfBook(), p.getIdOfBook())).findFirst();
                    promotion.ifPresent(ap -> p.setDiscountRate(ap.getDiscountRate() + p.getDiscountRate()));
                }
        );

        Blackhole.consumeCPU(10);

        return listWithBooks;
    }

    @Benchmark
    public Set<BookOverallData> streamOptimized() {
        Blackhole.consumeCPU(10);

        final Set<BookOverallData> listWithBooks = getListWithBooks();
        final Set<TimeDiscount> listWithDiscounts = getListWithDiscounts();

        Blackhole.consumeCPU(10);

        Map<Long, TimeDiscount> accumulator =
                listWithDiscounts.stream()
                        .collect(Collectors.toMap(TimeDiscount::getIdOfBook, Function.identity()));

        listWithBooks.forEach(e ->
                Optional.ofNullable(accumulator.get(e.getIdOfBook()))
                        .ifPresent(p -> e.setDiscountRate(e.getDiscountRate() + p.getDiscountRate()))
        );

        Blackhole.consumeCPU(10);

        return listWithBooks;
    }
}

Jutro jeszcze pobawię się wartościami blackholi :)

0

Idea blackhole i consume.cpu jest taka jak w artykule ktory podlinkowalem:

  • jesli popatrzymy na sam koszt uzywania volatile wyjdzie ze drogo, bieda itp. Ale w realnym dzialaniu system robi tez inne operacje w tzw. miedzyczasie i takie wywolania potrafia sie zamortyzowac.
    I teraz w zaleznosci co przekazujesz do consumeCpu symuluje w bardzo prosty sposob mniejsze lub wieksze obciazenie. Stad warto przeiterowac uzywajac roznych parametrow (tez jest w artykule).

Warto obejrzec ten material, jest pare pulapek w microbenchmarkach omowionych:
https://www.youtube.com/watch[...]saeAegRfNHXwgKF_tvcLI0mmmyGe5

  • z rzeczy na szybko dodalbym jeszcze te same metody ktore testujesz ale z innymi nazwami (i dokladnie takim samym body). Zeby sprawdzic czy nie ma zaleznosci pod spodem.
2

Na początek wrzucę wynik zabawy ze zróżnicowanymi ilościami konsumowanych przez cpu tokenów

Kod:


@Fork(1)
@Warmup(iterations = 5, time = 1)
@Measurement(iterations = 5, time = 1)
@BenchmarkMode(Mode.AverageTime)
@OutputTimeUnit(TimeUnit.NANOSECONDS)
@State(Scope.Thread)
public class UpdateObjectInOneListBasedOnSecondOne {

    @Param({"0", "1", "2", "3", "4", "5", "6", "7", "8", "9",
            "10", "11", "12", "13", "14", "15", "16", "17", "18", "19",
            "20", "21", "22", "23", "24", "25", "26", "27", "28", "29",
            "30", "31", "32", "33", "34", "35", "36", "37", "38", "39","40"})
    private int blackholeTokens;

    @AllArgsConstructor
    @Getter
    @Setter
    public static class BookOverallData {
        private Long idOfBook;
        private String title;
        private String authour;
        private BigDecimal basePrice;
        private Integer discountRate;
    }

    @AllArgsConstructor
    @Getter
    public static class TimeDiscount {
        private Long idOfBook;
        private Integer discountRate;
    }

    private Set<BookOverallData> getListWithBooks(){

        return Set.of(new BookOverallData(1L, "1984", "George Orwell", BigDecimal.valueOf(25.6), 10),
                new BookOverallData(2L, "Sentimental Education", "Gustave Flaubert", BigDecimal.valueOf(12.3), 0),
                new BookOverallData(3L, "Absalom, Absalom!", "William Faulkner", BigDecimal.valueOf(32.62), 30),
                new BookOverallData(4L, "The Hunger Games", "Suzanne Collins", BigDecimal.valueOf(12.45), 40),
                new BookOverallData(5L, "To Kill a Mockingbird", "Harper Lee", BigDecimal.valueOf(10.6), 10),
                new BookOverallData(6L, "Pride and Prejudice", "Jane Austen", BigDecimal.valueOf(15.4), 0),
                new BookOverallData(7L, "The Book Thief", "Markus Zusak", BigDecimal.valueOf(9.99), 15),
                new BookOverallData(8L, "Animal Farm", "George Orwell", BigDecimal.valueOf(22.0), 24),
                new BookOverallData(9L, "Gone with the Wind", "Margaret Mitchell", BigDecimal.valueOf(12.4), 27),
                new BookOverallData(10L, "The Fault in Our Stars", "John Green", BigDecimal.valueOf(16.00), 17),
                new BookOverallData(11L, "The Giving Tree", "Shel Silverstein", BigDecimal.valueOf(19.99), 15),
                new BookOverallData(12L, "Wuthering Heights", "Emily Brontë", BigDecimal.valueOf(20.0), 11),
                new BookOverallData(13L, "The Da Vinci Code", "Dan Brown", BigDecimal.valueOf(10.0), 7),
                new BookOverallData(14L, "Memoirs of a Geisha", "Arthur Golden", BigDecimal.valueOf(45.0), 4),
                new BookOverallData(15L, "The Picture of Dorian Gray", "Oscar Wilde", BigDecimal.valueOf(23.99), 16),
                new BookOverallData(16L, "Les Misérables", "Victor Hugo", BigDecimal.valueOf(12.00), 24),
                new BookOverallData(17L, "Jane Eyre", "Charlotte Brontë", BigDecimal.valueOf(55.99), 12),
                new BookOverallData(18L, "Lord of the Flies", "William Golding", BigDecimal.valueOf(24.00), 6),
                new BookOverallData(19L, "Crime and Punishment", "Fyodor Dostoyevsky", BigDecimal.valueOf(17.55), 0),
                new BookOverallData(20L, "The Perks of Being a Wallflower", "Stephen Chbosky", BigDecimal.valueOf(15.99), 0));
    }

    private Set<TimeDiscount> getListWithDiscounts(){
        return Set.of(new TimeDiscount(1L, 20), new TimeDiscount(8L, 20), new TimeDiscount(19L, 20));
    }

    @Benchmark
    public Set<BookOverallData> usedForLoop() {
        Blackhole.consumeCPU(blackholeTokens);

        final Set<BookOverallData> listWithBooks = getListWithBooks();
        final Set<TimeDiscount> listWithDiscounts = getListWithDiscounts();

        Blackhole.consumeCPU(blackholeTokens);

        for (BookOverallData bookOverallData : listWithBooks){
            for (TimeDiscount timeDiscount : listWithDiscounts){
                if (timeDiscount.getIdOfBook().equals(bookOverallData.getIdOfBook())){
                    bookOverallData.setDiscountRate(timeDiscount.getDiscountRate() + bookOverallData.getDiscountRate());
                }
            }
        }

        Blackhole.consumeCPU(blackholeTokens);

        return listWithBooks;
    }

    @Benchmark
    public Set<BookOverallData> streamOfStream() {
        Blackhole.consumeCPU(blackholeTokens);

        final Set<BookOverallData> listWithBooks = getListWithBooks();
        final Set<TimeDiscount> listWithDiscounts = getListWithDiscounts();

        Blackhole.consumeCPU(blackholeTokens);

        listWithBooks.forEach(
                p -> {
                    final Optional<TimeDiscount> promotion = listWithDiscounts.stream().filter(ap -> Objects.equals(ap.getIdOfBook(), p.getIdOfBook())).findFirst();
                    promotion.ifPresent(ap -> p.setDiscountRate(ap.getDiscountRate() + p.getDiscountRate()));
                }
        );

        Blackhole.consumeCPU(blackholeTokens);

        return listWithBooks;
    }

    @Benchmark
    public Set<BookOverallData> streamOptimized() {
        Blackhole.consumeCPU(blackholeTokens);

        final Set<BookOverallData> listWithBooks = getListWithBooks();
        final Set<TimeDiscount> listWithDiscounts = getListWithDiscounts();

        Blackhole.consumeCPU(blackholeTokens);

        Map<Long, TimeDiscount> accumulator =
                listWithDiscounts.stream()
                        .collect(Collectors.toMap(TimeDiscount::getIdOfBook, Function.identity()));

        listWithBooks.forEach(e ->
                Optional.ofNullable(accumulator.get(e.getIdOfBook()))
                        .ifPresent(p -> e.setDiscountRate(e.getDiscountRate() + p.getDiscountRate()))
        );

        Blackhole.consumeCPU(blackholeTokens);

        return listWithBooks;
    }
}

Graf na którym zwizualizowane jest zmiana ns/op w kontekście ilości tokenów:
screenshot-20181208183843.png

Zastanawiają mnie te duże odchylenia dla pomiaru streamu streamów są w niektórych momentach przeogromne. Bardzo fajne wyniki wychodzą np. przy 2, 3, 6, 17, 18, 25 39 tokenach.

Dla zdublowanych metod zmieniłem tylko warmup i measurment, bo z wcześniejszymi opcjami trwałoby to wieki:

@Warmup(iterations = 5, time = 1)
@Measurement(iterations = 5, time = 1)

Poniżej też wrzucam grafy par zdublowanych metod z errorarmi.

screenshot-20181208193152.png

screenshot-20181208193823.png

screenshot-20181208202637.png

Niby wyniki mieszczą się w zakresie błędów, ale w moim odczuciu różnice są dość spore dla niezoptymalizowanego streama, reszta wygląda dobrze poza kilkoma wyjątkami. Spróbuję to jeszcze puścić na 6 forkach i zobaczę czy wyniki dla tych samych funkcji będą bardziej zbliżone.
Dorzucam także logi z powyższych prób.

1

Zastanawiam sie jak by wygladal wykres dla metody majacej tylko blackhole albo blackhole + zwracanie stalej. Bo wcale nie zdzwilibym sie jakby nawet tam byly drobne fluktuacje.

Nawet pobieranie czasu potrafi wprowadzac fluktuacje. Poza tym czy na maszynie gdzie testujesz jak ida testy nie dzieje sie nic innego typu youtube albo Windows Update ?

Warto byloby do logu zobaczyc i odpalic JVM z parametrami:

-XX:+PrintGCApplicationStopped time
-XX:+PrintGCApplicationConcurrentTime

Btw jeszcze doloz jeszcze w pierwszym poscie podstawowa informacje jaki OS, jaka JVM, jakie parametry itp. (wiem ze jest w zalaczonym logu ale ktos kiedys wroci do postu to bedzie bardziej wartosciowy ) :)

1

Siemka, sry za poślizg, wcześniej nie mogłem.
Na początku zaczynam od kodu, @WhiteLightning dzięki za rady, dodałem baseline, a takżę flagi które pokażą ile gc spędza w safepointach. Co do safepointów, to wydaje mi się, że te staty mają znaczenie przy małej ilości wywołań, bo wtedy gc może mocno zakłócić rezultaty, ale jak wrzucam 6 forków to już nie ma takiego wpływu.

package pl.thisismyway.updateobjectinlist;

import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.Setter;
import org.openjdk.jmh.annotations.*;
import org.openjdk.jmh.infra.Blackhole;

import java.math.BigDecimal;
import java.util.*;
import java.util.concurrent.TimeUnit;
import java.util.function.Function;
import java.util.stream.Collectors;

@Fork(1)
@Warmup(iterations = 5, time = 1)
@Measurement(iterations = 5, time = 1)
@BenchmarkMode(Mode.AverageTime)
@OutputTimeUnit(TimeUnit.NANOSECONDS)
@State(Scope.Thread)
public class UpdateObjectInOneListBasedOnSecondOne {

    @Param({"0", "1", "2", "3", "4", "5", "6", "7", "8", "9",
            "10", "11", "12", "13", "14", "15", "16", "17", "18", "19",
            "20", "21", "22", "23", "24", "25", "26", "27", "28", "29",
            "30", "31", "32", "33", "34", "35", "36", "37", "38", "39","40"})
    private int blackholeTokens;

    @AllArgsConstructor
    @Getter
    @Setter
    public static class BookOverallData {
        private Long idOfBook;
        private String title;
        private String authour;
        private BigDecimal basePrice;
        private Integer discountRate;
    }

    @AllArgsConstructor
    @Getter
    public static class TimeDiscount {
        private Long idOfBook;
        private Integer discountRate;
    }

    private Set<BookOverallData> getListWithBooks(){

        return Set.of(new BookOverallData(1L, "1984", "George Orwell", BigDecimal.valueOf(25.6), 10),
                new BookOverallData(2L, "Sentimental Education", "Gustave Flaubert", BigDecimal.valueOf(12.3), 0),
                new BookOverallData(3L, "Absalom, Absalom!", "William Faulkner", BigDecimal.valueOf(32.62), 30),
                new BookOverallData(4L, "The Hunger Games", "Suzanne Collins", BigDecimal.valueOf(12.45), 40),
                new BookOverallData(5L, "To Kill a Mockingbird", "Harper Lee", BigDecimal.valueOf(10.6), 10),
                new BookOverallData(6L, "Pride and Prejudice", "Jane Austen", BigDecimal.valueOf(15.4), 0),
                new BookOverallData(7L, "The Book Thief", "Markus Zusak", BigDecimal.valueOf(9.99), 15),
                new BookOverallData(8L, "Animal Farm", "George Orwell", BigDecimal.valueOf(22.0), 24),
                new BookOverallData(9L, "Gone with the Wind", "Margaret Mitchell", BigDecimal.valueOf(12.4), 27),
                new BookOverallData(10L, "The Fault in Our Stars", "John Green", BigDecimal.valueOf(16.00), 17),
                new BookOverallData(11L, "The Giving Tree", "Shel Silverstein", BigDecimal.valueOf(19.99), 15),
                new BookOverallData(12L, "Wuthering Heights", "Emily Brontë", BigDecimal.valueOf(20.0), 11),
                new BookOverallData(13L, "The Da Vinci Code", "Dan Brown", BigDecimal.valueOf(10.0), 7),
                new BookOverallData(14L, "Memoirs of a Geisha", "Arthur Golden", BigDecimal.valueOf(45.0), 4),
                new BookOverallData(15L, "The Picture of Dorian Gray", "Oscar Wilde", BigDecimal.valueOf(23.99), 16),
                new BookOverallData(16L, "Les Misérables", "Victor Hugo", BigDecimal.valueOf(12.00), 24),
                new BookOverallData(17L, "Jane Eyre", "Charlotte Brontë", BigDecimal.valueOf(55.99), 12),
                new BookOverallData(18L, "Lord of the Flies", "William Golding", BigDecimal.valueOf(24.00), 6),
                new BookOverallData(19L, "Crime and Punishment", "Fyodor Dostoyevsky", BigDecimal.valueOf(17.55), 0),
                new BookOverallData(20L, "The Perks of Being a Wallflower", "Stephen Chbosky", BigDecimal.valueOf(15.99), 0));
    }

    private Set<TimeDiscount> getListWithDiscounts(){
        return Set.of(new TimeDiscount(1L, 20), new TimeDiscount(8L, 20), new TimeDiscount(19L, 20));
    }

    @Benchmark
    public int baseLine(){
        Blackhole.consumeCPU(blackholeTokens);
        return 31;
    }

    @Benchmark
    public int baseLine2(){
        Blackhole.consumeCPU(blackholeTokens);
        return 31;
    }

    @Benchmark
    public Set<BookOverallData> usedForLoop() {
        Blackhole.consumeCPU(blackholeTokens);

        final Set<BookOverallData> listWithBooks = getListWithBooks();
        final Set<TimeDiscount> listWithDiscounts = getListWithDiscounts();

        Blackhole.consumeCPU(blackholeTokens);

        for (BookOverallData bookOverallData : listWithBooks){
            for (TimeDiscount timeDiscount : listWithDiscounts){
                if (timeDiscount.getIdOfBook().equals(bookOverallData.getIdOfBook())){
                    bookOverallData.setDiscountRate(timeDiscount.getDiscountRate() + bookOverallData.getDiscountRate());
                }
            }
        }

        Blackhole.consumeCPU(blackholeTokens);

        return listWithBooks;
    }

    @Benchmark
    public Set<BookOverallData> streamOfStream() {
        Blackhole.consumeCPU(blackholeTokens);

        final Set<BookOverallData> listWithBooks = getListWithBooks();
        final Set<TimeDiscount> listWithDiscounts = getListWithDiscounts();

        Blackhole.consumeCPU(blackholeTokens);

        listWithBooks.forEach(
                p -> {
                    final Optional<TimeDiscount> promotion = listWithDiscounts.stream().filter(ap -> Objects.equals(ap.getIdOfBook(), p.getIdOfBook())).findFirst();
                    promotion.ifPresent(ap -> p.setDiscountRate(ap.getDiscountRate() + p.getDiscountRate()));
                }
        );

        Blackhole.consumeCPU(blackholeTokens);

        return listWithBooks;
    }

    @Benchmark
    public Set<BookOverallData> streamOptimized() {
        Blackhole.consumeCPU(blackholeTokens);

        final Set<BookOverallData> listWithBooks = getListWithBooks();
        final Set<TimeDiscount> listWithDiscounts = getListWithDiscounts();

        Blackhole.consumeCPU(blackholeTokens);

        Map<Long, TimeDiscount> accumulator =
                listWithDiscounts.stream()
                        .collect(Collectors.toMap(TimeDiscount::getIdOfBook, Function.identity()));

        listWithBooks.forEach(e ->
                Optional.ofNullable(accumulator.get(e.getIdOfBook()))
                        .ifPresent(p -> e.setDiscountRate(e.getDiscountRate() + p.getDiscountRate()))
        );

        Blackhole.consumeCPU(blackholeTokens);

        return listWithBooks;
    }

    @Benchmark
    public Set<BookOverallData> usedForLoop2() {
        Blackhole.consumeCPU(blackholeTokens);

        final Set<BookOverallData> listWithBooks = getListWithBooks();
        final Set<TimeDiscount> listWithDiscounts = getListWithDiscounts();

        Blackhole.consumeCPU(blackholeTokens);

        for (BookOverallData bookOverallData : listWithBooks){
            for (TimeDiscount timeDiscount : listWithDiscounts){
                if (timeDiscount.getIdOfBook().equals(bookOverallData.getIdOfBook())){
                    bookOverallData.setDiscountRate(timeDiscount.getDiscountRate() + bookOverallData.getDiscountRate());
                }
            }
        }

        Blackhole.consumeCPU(blackholeTokens);

        return listWithBooks;
    }

    @Benchmark
    public Set<BookOverallData> streamOfStream2() {
        Blackhole.consumeCPU(blackholeTokens);

        final Set<BookOverallData> listWithBooks = getListWithBooks();
        final Set<TimeDiscount> listWithDiscounts = getListWithDiscounts();

        Blackhole.consumeCPU(blackholeTokens);

        listWithBooks.forEach(
                p -> {
                    final Optional<TimeDiscount> promotion = listWithDiscounts.stream().filter(ap -> Objects.equals(ap.getIdOfBook(), p.getIdOfBook())).findFirst();
                    promotion.ifPresent(ap -> p.setDiscountRate(ap.getDiscountRate() + p.getDiscountRate()));
                }
        );

        Blackhole.consumeCPU(blackholeTokens);

        return listWithBooks;
    }

    @Benchmark
    public Set<BookOverallData> streamOptimized2() {
        Blackhole.consumeCPU(blackholeTokens);

        final Set<BookOverallData> listWithBooks = getListWithBooks();
        final Set<TimeDiscount> listWithDiscounts = getListWithDiscounts();

        Blackhole.consumeCPU(blackholeTokens);

        Map<Long, TimeDiscount> accumulator =
                listWithDiscounts.stream()
                        .collect(Collectors.toMap(TimeDiscount::getIdOfBook, Function.identity()));

        listWithBooks.forEach(e ->
                Optional.ofNullable(accumulator.get(e.getIdOfBook()))
                        .ifPresent(p -> e.setDiscountRate(e.getDiscountRate() + p.getDiscountRate()))
        );

        Blackhole.consumeCPU(blackholeTokens);

        return listWithBooks;
    }
}

Wrzucam parami wykresy dla jednego forka:
screenshot-20181227213153.png
screenshot-20181227220050.png
screenshot-20181227222332.png
screenshot-20181227224123.png

Dla jednego forka wszystko wygląda w miarę spójnie, są różne wahania, ale mieszczą się one w zakresie błędu. Większą wagę przykładam do wyników puszczonych na 6 forkach, gdyż próbka tam większa, więc i powinna dać bardziej wiarygodne wyniki.

Tutaj wykresy dla 6 forków:
screenshot-20181230162251.png
screenshot-20181230162304.png
screenshot-20181230162332.png
screenshot-20181230162351.png

Baseline wyszedł mega ładnie, linie niemal idealnie się pokrywają, większa ilość tokenów do przerobienia zwiększa czas. Tak samo idealnie wygląda wykres dla zoptymalizowanych streamów oprócz tych dwóch wahań. Chaos wkrada się w wykres streamów streamów, ale po chwili zastanowienia ich wyniki mieszczą się w zakresie błędów, wykresy są różne, ale trzymają się siebie. Prawdziwy mindfuck zaczyna się w małej ilości tokenów dla pętli, wyniki wyglądają, jak gdybym mierzył zupełnie różne rzeczy.

Na podstawie wyników uzyskanych z 6 forków wykonałem też wykres spinający te pomiary, wyliczyłem średnią i oto rezultat:
screenshot-20181230170056.png

Wynik pokazuje, iż zoptymalizowane streamy są szybsze od dwóch pozostałych metod. Na tym wykresie te różnice są mocniej nakreślone, niż miało to miejsce w poprzednich pomiarach.

Dzięki @WhiteLightning za podpowiedzenie flag, pojawiło się wiele liczb, nie mogę jednak wyłapać jakichś zależności. Aktualnie na potrzeby tego eksperymentu do wizualizacji danych wspomagałem się prostym excelem, polecasz jakieś narzędzia, które ułatwią analizę tych wyników ?

1 użytkowników online, w tym zalogowanych: 0, gości: 1, botów: 0