Kiedy równoległość pomaga
Czynniki związane z obciążeniem i rozmiarem danych
Kiedy równoległość pomaga to bezpłatna lekcja Java Academy na CoddyKit. To lekcja 2 z 4. Możesz przeczytać całą lekcję poniżej za darmo — a potem ćwiczyć ją interaktywnie w przeglądarce z wbudowanym edytorem kodu i tutorem AI dostępnym 24/7. To część ścieżki edukacyjnej Java Academy, a Twój postęp synchronizuje się między webem a aplikacją CoddyKit. Kurs Java Academy zawiera 4 lekcji w sumie.
Równoległość ma swoją cenę
Wykonywanie równoległe dodaje narzut związany z dzieleniem danych, przydzielaniem zadań i scalaniem wyników. Opłaca się tylko wtedy, gdy ten koszt jest mniejszy od zaoszczędzonego czasu.
import java.util.stream.IntStream;
public class Main {
public static void main(String[] args) {
long sum = IntStream.rangeClosed(1, 10_000_000)
.parallel()
.asLongStream()
.sum();
System.out.println(sum);
}
}Czynnik 1: rozmiar danych (N)
Duża wartość N pozwala rozłożyć stały narzut równoległości. Przybliżona praktyczna zasada mówi, że równoległość zaczyna się opłacać przy dziesiątkach tysięcy elementów.
import java.util.stream.IntStream;
public class Main {
public static void main(String[] args) {
long count = IntStream.rangeClosed(1, 5_000_000)
.parallel()
.filter(n -> n % 7 == 0)
.count();
System.out.println(count);
}
}Czynnik 2: praca na element (Q)
Znaczenie ma koszt Q przetwarzania każdego elementu. Kosztowne operacje na elementach, wymagające intensywnych obliczeń, korzystają z równoległości nawet przy mniejszej wartości N.
import java.util.stream.IntStream;
public class Main {
public static void main(String[] args) {
long primes = IntStream.rangeClosed(2, 200_000)
.parallel()
.filter(Main::isPrime)
.count();
System.out.println(primes);
}
static boolean isPrime(int n) {
for (int i = 2; (long) i * i <= n; i++)
if (n % i == 0) return false;
return true;
}
}Intuicja N razy Q
Całkowitą użyteczną pracę można rozumieć jako N x Q. Im większy jest ten iloczyn, tym bardziej równoległość może pomóc. Mała wartość N lub trywialna wartość Q rzadko uzasadnia jej użycie.
import java.util.stream.IntStream;
public class Main {
public static void main(String[] args) {
double sum = IntStream.rangeClosed(1, 1_000_000)
.parallel()
.mapToDouble(n -> Math.sqrt(n) * Math.log(n + 1))
.sum();
System.out.println(sum);
}
}Czynnik 3: podatność na podział
Źródła danych, które można tanio i równomiernie dzielić, dobrze nadają się do przetwarzania równoległego: tablice, ArrayList i IntStream.range. LinkedList oraz źródła oparte na iteratorach dzielą się słabo.
import java.util.ArrayList;
import java.util.List;
public class Main {
public static void main(String[] args) {
List<Integer> list = new ArrayList<>();
for (int i = 0; i < 1_000_000; i++) list.add(i);
long even = list.parallelStream().filter(n -> n % 2 == 0).count();
System.out.println(even);
}
}Dobre źródła: tablice i zakresy
Zakresy typów prostych mają znany rozmiar i można je dzielić w czasie O(1), dzięki czemu są idealnymi źródłami dla przetwarzania równoległego.
import java.util.stream.LongStream;
public class Main {
public static void main(String[] args) {
long sum = LongStream.rangeClosed(1, 20_000_000)
.parallel()
.sum();
System.out.println(sum);
}
}Słabe źródło: generatory iteracyjne
Stream.iterate z definicji generuje elementy sekwencyjnie, więc nie można go podzielić, dopóki elementy nie zostaną utworzone. Jest słabym źródłem dla przetwarzania równoległego.
import java.util.stream.Stream;
public class Main {
public static void main(String[] args) {
long count = Stream.iterate(1, n -> n + 1)
.limit(1_000_000)
.parallel()
.filter(n -> n % 2 == 0)
.count();
System.out.println(count);
}
}Czynnik 4: tanie scalanie
Równoległość wymaga niedrogiego sposobu łączenia częściowych wyników. sum i count można scalać w trywialny sposób, natomiast budowanie posortowanej listy lub kolektora opartego na złożonej strukturze drzewa jest kosztowne.
import java.util.stream.IntStream;
public class Main {
public static void main(String[] args) {
int max = IntStream.rangeClosed(1, 5_000_000)
.parallel()
.map(n -> n % 1000)
.max()
.getAsInt();
System.out.println(max);
}
}Należy unikać w przypadku operacji zależnych od wejścia-wyjścia
Strumienie równoległe są przeznaczone do zadań ograniczonych przez procesor i wykonywanych w puli fork-join. Blokujące operacje wejścia-wyjścia wyczerpują współdzieloną pulę i szkodzą całej aplikacji. W przypadku operacji wejścia-wyjścia należy zamiast tego używać dedykowanych executorów.
import java.util.stream.IntStream;
public class Main {
public static void main(String[] args) {
long total = IntStream.rangeClosed(1, 1_000_000)
.parallel()
.mapToLong(n -> (long) n * n)
.sum();
System.out.println(total);
}
}Mierz, nie zgaduj
Jedynym wiarygodnym sposobem sprawdzenia, czy równoległość pomaga, jest wykonanie benchmarku na realistycznych danych. Intuicja dotycząca wydajności często zawodzi.
import java.util.stream.LongStream;
public class Main {
public static void main(String[] args) {
long start = System.nanoTime();
long sum = LongStream.rangeClosed(1, 50_000_000).parallel().sum();
long ms = (System.nanoTime() - start) / 1_000_000;
System.out.println("sum=" + sum + " took ~" + ms + "ms");
}
}Praktyczna lista kontrolna
Równoległość warto stosować, gdy spełnione są wszystkie warunki:
- Duża wartość N lub kosztowne operacje na elementach.
- Źródło podatne na podział (tablica, ArrayList, zakres).
- Tanie, asocjacyjne scalanie.
- Operacje ograniczone przez procesor, bez blokujących operacji wejścia-wyjścia.
import java.util.stream.IntStream;
public class Main {
public static void main(String[] args) {
double avg = IntStream.rangeClosed(1, 10_000_000)
.parallel()
.mapToDouble(Math::sqrt)
.average()
.getAsDouble();
System.out.println(avg);
}
}Szybkie sprawdzenie
Który scenariusz jest najlepszym kandydatem do użycia strumienia równoległego?
Podsumowanie
Poznano sytuacje, w których równoległość pomaga:
- Korzyść rośnie wraz z N x Q (rozmiarem danych pomnożonym przez koszt operacji na elemencie).
- Potrzebne jest źródło podatne na podział (tablice, ArrayList, zakresy).
- Potrzebne jest tanie, asocjacyjne scalanie.
- Należy utrzymywać zadania w obszarze obliczeń procesora, unikać blokujących operacji wejścia-wyjścia i zawsze wykonywać pomiary.
Często zadawane pytania
Czy lekcja „Kiedy równoległość pomaga” jest bezpłatna?
Tak — pełny tekst „Kiedy równoległość pomaga” jest dostępny za darmo tutaj w sieci. Aby ćwiczyć ją interaktywnie (wbudowany edytor kodu i tutor AI dostępny 24/7) i odblokować resztę kursu Java Academy, przejdź na CoddyKit PRO. Kurs Java Academy zawiera 4 lekcji w sumie.
Co nauczysz się w „Kiedy równoległość pomaga”?
Czynniki związane z obciążeniem i rozmiarem danych Ćwiczysz Java Academy z praktycznym kodem, który uruchamiasz bezpośrednio w przeglądarce, a tutor AI dostępny 24/7 odpowiada na Twoje pytania podczas pracy nad lekcją.
Czy potrzebuję doświadczenia, aby zacząć Java Academy?
Nie wymagamy żadnego doświadczenia. Java Academy w CoddyKit jest strukturyzowany dla początkujących i zaawansowanych użytkowników, więc możesz zacząć tutaj lub od początku i uczyć się w swoim tempie. To lekcja 2 z 4.
Ile czasu zajmuje lekcja „Kiedy równoległość pomaga”?
Większość lekcji CoddyKit trwa około 5–10 minut. Każda lekcja to mały, interaktywny krok, dzięki czemu robisz systematyczne postępy i zawsze wracasz dokładnie do tego samego miejsca — na webie i w aplikacji.
Czy mogę pisać i uruchamiać kod w tej lekcji Java Academy?
Tak. Każda lekcja Java Academy zawiera wbudowany edytor kodu, więc piszesz i uruchamiasz prawdziwy kod bezpośrednio w przeglądarce i od razu otrzymujesz sprzężenie zwrotne od AI — bez konfiguracji na komputerze.
Wszystkie lekcje w tym kursie
- Tworzenie strumieni równoległych
- Kiedy równoległość pomaga
- Bezpieczeństwo wątkowe i efekty uboczne
- Typowe pułapki