ExecutorService + Future

ExecutorService + Future
KI
  • Rejestracja:ponad 10 lat
  • Ostatnio:około 9 lat
  • Postów:29
0

Napisałem program, który tworzy trzy listy i sprawdza ile czasu to zajmuje. Wygląda tak:

Kopiuj
public class Main {

    public static void main(String[] args) throws ExecutionException, InterruptedException {
        createThreeLists();
    }

    public static void createThreeLists() {
        long start = System.currentTimeMillis();

        List<String> converted1 = createList();
        List<String> converted2 = createList();
        List<String> converted3 = createList();

        long stop = System.currentTimeMillis();
        System.out.println("Time: " + (stop - start));
    }

    private static List<String> createList() {
        List<String> toReturn = new ArrayList<>();

        for (int i = 0; i < 10000000; i++)
            toReturn.add("heheszki");

        return toReturn;
    }
}

Wykonanie teko programu zajmuje na moim komputerze ~750ms

A tak wygląda program z użyciem ExecutorService i Future:

Kopiuj
public class Main {

    public static void main(String[] args) throws ExecutionException, InterruptedException {
        createThreeArrays();
    }

    public static void createThreeArrays() throws ExecutionException, InterruptedException {
        long start = System.currentTimeMillis();

        ExecutorService executor = Executors.newFixedThreadPool(3);
        Future<List<String>> f1 = executor.submit(getCallable());
        Future<List<String>> f2 = executor.submit(getCallable());
        Future<List<String>> f3 = executor.submit(getCallable());

        List<String> converted1 = f1.get();
        List<String> converted2 = f2.get();
        List<String> converted3 = f3.get();

        long stop = System.currentTimeMillis();
        System.out.println("Time: " + (stop - start));
    }

    private static Callable<List<String>> getCallable() {
        Callable<List<String>> callable = () -> {
            List<String> toReturn = new ArrayList<>();

            for (int i = 0; i < 10000000; i++)
                toReturn.add("heheszki");

            return toReturn;
        };

        return callable ;
    }
}

Jego wykonanie zajmuje dwa razy więcej czasu.

Czy użycie Futurenie powinno przyspieszyć jego działania?

edytowany 2x, ostatnio: killermannnnn
JasnyPatryk
  • Rejestracja:ponad 9 lat
  • Ostatnio:około 9 lat
  • Postów:42
1

Czy użycie Futurenie powinno przyspieszyć jego działania?

Dlaczego ?

Zdajesz sobie sprawę że stworzenie executora, wątków kosztuje ?


je suis @niezdecydowany
edytowany 1x, ostatnio: JasnyPatryk
JasnyPatryk
@Shalom, jeżeli ten lajkt to mają być przeprosiny za Twój hejt, to przyjmuje, wybaczam :D:D:D:D:D:D:D:D:DD:D:D:D:D:D:D:D:DD:D:D:D:D:D:D:D:DD:D:D:D:D:D:D:D:D
Shalom
Nie no uznałem że warto ci dać żebyś miał chociaż 1 punkt bo obciach trochę... :P
Kundel Burek
Koledzy widzę romantycy :)
Shalom
  • Rejestracja:ponad 21 lat
  • Ostatnio:około 3 lata
  • Lokalizacja:Space: the final frontier
  • Postów:26433
0

Przy takim krótkim czasie to raczej zysku nie będzie bo overhead za duży. Odpal coś co sie liczy kilka minut.


"Nie brookliński most, ale przemienić w jasny, nowy dzień najsmutniejszą noc - to jest dopiero coś!"
Kundel Burek
  • Rejestracja:około 9 lat
  • Ostatnio:około rok
  • Postów:10
0

Nic już tutaj mądrego nie dopowiem, bo koledzy praktycznie wyczerpali temat. Może chyba tylko tyle, że po to właśnie są Executory, żeby uniknąć overheadu tworzenia wątków, tylko je reużywać. A sam overhead na tworzenie wątków już sam widzisz w różnicy czasu dla drugiego przypadku.

Wibowit
  • Rejestracja:około 20 lat
  • Ostatnio:29 minut
2

U mnie podobnie się zachowuje. Wersja z Executorem działa 2 - 3 razy dłużej. Teoria z narzutem na tworzenie wątku jest trochę mało wiarygodna, bo jak zmniejszę rozmiar zadania 10x to czasy też zmniejszą się mniej więcej 10x, ale zostaje zachowana proporcja, że wersja z Executorem działa 2 - 3 razy dłużej. Czyli przy mniejszych licznikach pętli narzut na tworzenie wątku jest mniejszy? Wątpię.

Aktualizacja:
Moim zdaniem wąskim gardłem jest GC, a narzut na tworzenie wątku jest niewielki - pojedyncze milisekundy na co najwyżej.

Z ciekawości, zrobiłem programy testowe, które mniej obciążają GC - nie tworzą gigantycznych tablic:

Kopiuj
public class Main1 {

    public static void main(String[] args) throws Exception {
        long start = System.currentTimeMillis();

        int converted1 = createResult();
        int converted2 = createResult();
        int converted3 = createResult();

        System.out.println(converted1 + converted2 + converted3);

        long stop = System.currentTimeMillis();
        System.out.println("Time: " + (stop - start));
    }

    private static Integer createResult() {
        int result = 0;

        for (int i = 0; i < 12345678; i++) {
            result = (result * 5) + String.valueOf(i).hashCode();
        }

        return result;
    }
}
Kopiuj
import java.util.Arrays;
import java.util.List;
import java.util.concurrent.*;

public class Main2 {

    public static void main(String[] args) throws Exception {
        long start = System.currentTimeMillis();

        ExecutorService executor = Executors.newFixedThreadPool(3);
        List<Future<Integer>> futures = executor.invokeAll(
                Arrays.asList(createResult(), createResult(), createResult()));

        int converted1 = futures.get(0).get();
        int converted2 = futures.get(1).get();
        int converted3 = futures.get(2).get();

        System.out.println(converted1 + converted2 + converted3);

        long stop = System.currentTimeMillis();
        System.out.println("Time: " + (stop - start));

        executor.shutdown();
    }

    private static Callable<Integer> createResult() {
        return () -> {
            int result = 0;

            for (int i = 0; i < 12345678; i++) {
                result = (result * 5) + String.valueOf(i).hashCode();
            }

            return result;
        };
    }
}

Teraz kod z Executorem jest znacznie szybszy od jednowątkowego.


"Programs must be written for people to read, and only incidentally for machines to execute." - Abelson & Sussman, SICP, preface to the first edition
"Ci, co najbardziej pragną planować życie społeczne, gdyby im na to pozwolić, staliby się w najwyższym stopniu niebezpieczni i nietolerancyjni wobec planów życiowych innych ludzi. Często, tchnącego dobrocią i oddanego jakiejś sprawie idealistę, dzieli od fanatyka tylko mały krok."
Demokracja jest fajna, dopóki wygrywa twoja ulubiona partia.
edytowany 1x, ostatnio: Wibowit
SP
  • Rejestracja:ponad 9 lat
  • Ostatnio:ponad 2 lata
  • Postów:127
0

Executor#invokeAll jest blokujący (czeka aż wszystkie zadania się wykonają), Executor#submit - nie, więc w oryginalnym teście program czeka najpierw na wykonanie pierwszego zadania, a może ono się wykonywać najdłużej (edit- w sumie na jedno wychodzi). Poza tym dobrze zrobić najpierw jakieś puste iteracje na rozgrzewkę. Nie podano też liczby rdzeni. W tym pierwszym teście sama alokacja pamięci pewnie jest wąskim gardłem.
Ps. O jakim narzucie piszecie, przecież tu jest jednorazowe utworzenie 3 wątków. I co ma robić GC, skoro wszystko jest "trzymane" do końca programu?

edytowany 1x, ostatnio: student pro
Wibowit
  • Rejestracja:około 20 lat
  • Ostatnio:29 minut
1

I co ma robić GC, skoro wszystko jest "trzymane" do końca programu?

  1. Generacyjny GC przerzuca obiekty między generacjami.
  2. ArrayLista podczas wzrostu tworzy nowe (coraz to większe) tablice do przechowywania danych - GC próbuje zawsze najpierw wrzucić nowy obiekt do jak najświeższej generacji, a to może go zachęcić do kolejnych odśmieceń.
  3. Obciążanie GC z wielu wątków jednocześnie może prowadzić do nieoczekiwanych konsekwencji - jak wykazały moje eksperymenty.

W ramach eksperymentu odpaliłem programy podane przez autora z G1 GC i wtedy działały z taką samą prędkością. Domyślny GC lepiej działał z wersją jednowątkową - taki sam rezultat jak podał autor.


"Programs must be written for people to read, and only incidentally for machines to execute." - Abelson & Sussman, SICP, preface to the first edition
"Ci, co najbardziej pragną planować życie społeczne, gdyby im na to pozwolić, staliby się w najwyższym stopniu niebezpieczni i nietolerancyjni wobec planów życiowych innych ludzi. Często, tchnącego dobrocią i oddanego jakiejś sprawie idealistę, dzieli od fanatyka tylko mały krok."
Demokracja jest fajna, dopóki wygrywa twoja ulubiona partia.
edytowany 2x, ostatnio: Wibowit
W0
  • Rejestracja:ponad 12 lat
  • Ostatnio:około 2 godziny
  • Postów:3606
0

Skoro już testujemy na staticach:

Kopiuj
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.*;

public class Challenge001 {
    private static ExecutorService commonService = Executors.newFixedThreadPool(3);

    public static void main(String[] args) throws ExecutionException, InterruptedException {
        long sum = 0;
        long current;

        for(int i=0; i<5; i++){
            syncTest();
            asyncTest();
            createThreeListsAsyncCommonPool();
        }

        System.out.println("SyncTest");
        for(int i=0; i<10; i++){
            current = syncTest();
            sum = sum + current;

            System.out.println(i + "=" + current);
        }
        System.out.println("sum=" + sum);
        sum = 0;

        System.out.println("asyncTest");
        for(int i=0; i<10; i++){
            current = asyncTest();
            sum = sum + current;

            System.out.println(i + "=" + current);
        }
        System.out.println("sum=" + sum);
        sum = 0;

        System.out.println("asyncCommonTest");
        for(int i=0; i<10; i++){
            current = asyncCommonTest();
            sum = sum + current;

            System.out.println(i + "=" + current);
        }
        System.out.println("sum=" + sum);
        sum = 0;

    }

    public static long asyncCommonTest() throws ExecutionException, InterruptedException {
        long start = System.currentTimeMillis();
        createThreeListsAsyncCommonPool();

        long stop = System.currentTimeMillis();

        return (stop-start);
    }

    public static long syncTest(){
        long start = System.currentTimeMillis();
        createThreeListsSync();

        long stop = System.currentTimeMillis();

        return (stop-start);
    }

    public static long asyncTest() throws ExecutionException, InterruptedException {
        long start = System.currentTimeMillis();
        createThreeListsAsync();

        long stop = System.currentTimeMillis();

        return (stop-start);
    }

    public static void createThreeListsAsync() throws ExecutionException, InterruptedException {
        Callable<List<String>> callable = new ListCallable();
        ExecutorService service = Executors.newFixedThreadPool(3);
        Future<List<String>> f1 = service.submit(callable);
        Future<List<String>> f2 = service.submit(callable);
        Future<List<String>> f3 = service.submit(callable);

        f1.get();
        f2.get();
        f3.get();
    }

    public static void createThreeListsAsyncCommonPool() throws ExecutionException, InterruptedException {
        Callable<List<String>> callable = new ListCallable();
        Future<List<String>> f1 = commonService.submit(callable);
        Future<List<String>> f2 = commonService.submit(callable);
        Future<List<String>> f3 = commonService.submit(callable);

        f1.get();
        f2.get();
        f3.get();
    }

    public static void createThreeListsSync() {
        List<String> converted1 = createList();
        List<String> converted2 = createList();
        List<String> converted3 = createList();
    }

    private static List<String> createList() {
        List<String> toReturn = new ArrayList<>();

        for (int i = 0; i < 10000000; i++)
            toReturn.add("heheszki");

        return toReturn;
    }

    private static class ListCallable implements Callable<List<String>> {

        @Override
        public List<String> call() throws Exception {
            List<String> result = new ArrayList<>();
            for (int i = 0; i < 10000000; i++)
                result.add("heheszki");


            return result;
        }
    }
}

Okład na tworzenie wątków jest rzeczywiście względnie mały, czasami jak GC się odpali to i asyncCommonTest może być wolniejszy od asyncTesta. Tyle tylko, że oba są dużo szybsze od normalnej implementacji (u mnie średnio 900-1000 ms).

Przy czym NIE masz gwarancji - dużo może zależeć od runtime'u. Future ze swojej natury gwarantują współbieżność (concurrency), ale nie zrównoleglenie (parallel).

Zarejestruj się i dołącz do największej społeczności programistów w Polsce.

Otrzymaj wsparcie, dziel się wiedzą i rozwijaj swoje umiejętności z najlepszymi.