R Academy · Lekcja

Pakiet parallel i detectCores()

Uruchamiaj klastry forkowane lub gniazdowe i rozdzielaj pracę między rdzenie procesora.

Lekcja 1 z 413 kroki

Pakiet parallel i detectCores() to bezpłatna lekcja R Academy na CoddyKit. To lekcja 1 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 R Academy, a Twój postęp synchronizuje się między webem a aplikacją CoddyKit. Kurs R Academy zawiera 4 lekcji w sumie.

Dlaczego obliczenia równoległe?

Współczesne komputery mają wiele rdzeni CPU. Domyślnie R działa na jednym rdzeniu, pozostawiając pozostałe bezczynne. Pakiet parallel (wbudowany w R) umożliwia wykorzystanie wszystkich rdzeni w celu przyspieszenia powtarzalnych obliczeń.

# Check how many cores your machine has
library(parallel)

total_cores <- detectCores()
logical_cores <- detectCores(logical = TRUE)
physical_cores <- detectCores(logical = FALSE)

cat('Total logical cores:', total_cores, '
')
cat('Physical cores:', physical_cores, '
')

makeCluster i stopCluster

makeCluster(n) uruchamia n procesów roboczych. Po zakończeniu zawsze wywołuj stopCluster(cl), aby zwolnić zasoby. Często stosuje się konwencję detectCores() - 1, pozostawiając jeden rdzeń dla systemu operacyjnego.

library(parallel)

# Spawn workers (leave 1 core for system)
n_cores <- detectCores() - 1
cl <- makeCluster(n_cores)

cat('Cluster created with', n_cores, 'workers\n')

# Always clean up!
stopCluster(cl)
cat('Cluster stopped.\n')

clusterExport: udostępnianie zmiennych

Procesy robocze mają własną przestrzeń pamięci i nie widzą środowiska globalnego. Użyj clusterExport(cl, varlist), aby skopiować nazwane obiekty z procesu głównego do wszystkich procesów roboczych.

library(parallel)
cl <- makeCluster(2)

# Define a variable and a function in the master
base_value <- 100
add_base <- function(x) x + base_value

# Export them to workers
clusterExport(cl, varlist = c('base_value', 'add_base'))

# Now workers can use them
result <- parLapply(cl, 1:4, function(x) add_base(x))
cat(unlist(result), '\n')  # 101 102 103 104

stopCluster(cl)

clusterEvalQ: uruchamianie kodu konfiguracyjnego

clusterEvalQ(cl, expr) oblicza wyrażenie w każdym procesie roboczym — jest przydatne do ładowania pakietów lub plików pomocniczych we wszystkich węzłach przed rozpoczęciem głównych obliczeń.

library(parallel)
cl <- makeCluster(2)

# Load a package on every worker
clusterEvalQ(cl, {
  library(stats)
  set.seed(42)  # set seed per worker
})

# Each worker can now use stats functions
result <- parLapply(cl, 1:4, function(n) rnorm(n, mean = 0, sd = 1))
result[[1]]  # one random normal value

stopCluster(cl)

parLapply: równoległe lapply

parLapply(cl, X, FUN) jest równoległym odpowiednikiem lapply(). Rozdziela elementy X między procesy robocze i zbiera wyniki w postaci listy. Działa na wszystkich platformach (Windows, macOS, Linux).

library(parallel)
cl <- makeCluster(2)

# Simulate slow computation: sleep 0.1s per item
clusterEvalQ(cl, Sys.sleep)

slow_square <- function(x) {
  Sys.sleep(0.05)
  x^2
}

clusterExport(cl, 'slow_square')

system.time(
  result <- parLapply(cl, 1:8, slow_square)
)
cat(unlist(result), '\n')  # 1 4 9 16 25 36 49 64

stopCluster(cl)

parSapply: uproszczone wyniki

parSapply(cl, X, FUN) jest równoległą wersją sapply(). Automatycznie upraszcza wynik do wektora lub macierzy, gdy jest to możliwe, dzięki czemu sprawdza się, gdy funkcja zwraca wartość skalarną.

library(parallel)
cl <- makeCluster(2)

# parSapply returns a simplified vector
squares <- parSapply(cl, 1:10, function(x) x^2)
cat(squares, '\n')  # 1 4 9 16 25 36 49 64 81 100

# Returns a matrix if FUN returns a vector of same length
stats_result <- parSapply(cl, 1:4, function(n) {
  x <- rnorm(100)
  c(mean = mean(x), sd = sd(x))
})
print(stats_result)  # 2x4 matrix

stopCluster(cl)

mclapply: równoległość oparta na forkowaniu

mclapply() używa forkowania — kopiuje bieżący proces R zamiast uruchamiać nowe procesy, dzięki czemu szybciej się uruchamia i zapewnia procesom roboczym niejawny dostęp do środowiska nadrzędnego. Działa jednak tylko w systemach Unix/macOS, a nie w systemie Windows.

library(parallel)

# mclapply: Unix/macOS only
# Workers inherit the parent environment automatically
base_value <- 42

if (.Platform$OS.type != 'windows') {
  result <- mclapply(
    1:8,
    function(x) x * base_value,  # base_value visible without export
    mc.cores = 4
  )
  cat(unlist(result), '\n')
} else {
  cat('mclapply not supported on Windows. Use parLapply instead.\n')
}

Testowanie wydajności: sekwencyjnie a równolegle

Obliczenia równoległe wiążą się z narzutem: uruchomienie klastra, serializacja danych i komunikacja między procesami wymagają czasu. Zyski pojawiają się tylko wtedy, gdy obliczenia dla pojedynczego elementu są wystarczająco kosztowne, aby przewyższyć ten narzut.

library(parallel)
cl <- makeCluster(2)

# Task: compute 100 iterations of matrix multiply
heavy_task <- function(n) {
  m <- matrix(rnorm(200), nrow = 100)
  sum(m %*% t(m))
}
clusterExport(cl, 'heavy_task')

seq_time <- system.time(lapply(1:20, heavy_task))[['elapsed']]
par_time <- system.time(parLapply(cl, 1:20, heavy_task))[['elapsed']]

cat('Sequential:', round(seq_time, 3), 's\n')
cat('Parallel:  ', round(par_time, 3), 's\n')
cat('Speedup:   ', round(seq_time / par_time, 2), 'x\n')

stopCluster(cl)

Inicjalizacja generatora liczb losowych w obliczeniach równoległych

Generowanie liczb losowych w obliczeniach równoległych jest trudne — każdy proces roboczy potrzebuje niezależnego i powtarzalnego strumienia. Użyj clusterSetRNGStream(cl, seed) wraz z generatorem L'Ecuyer-CMRG, aby uzyskać powtarzalną losowość równoległą.

library(parallel)
cl <- makeCluster(2)

# Set reproducible RNG streams across workers
clusterSetRNGStream(cl, iseed = 123)

# Each worker uses its own independent random stream
results1 <- parSapply(cl, 1:6, function(i) rnorm(1))

# Reset and repeat — same results
clusterSetRNGStream(cl, iseed = 123)
results2 <- parSapply(cl, 1:6, function(i) rnorm(1))

cat('Run 1:', round(results1, 4), '\n')
cat('Run 2:', round(results2, 4), '\n')
cat('Identical:', identical(results1, results2), '\n')

stopCluster(cl)

Obsługa błędów w klastrach

Jeśli proces roboczy zgłosi błąd, parLapply() ponownie zgłasza go w procesie głównym. Umieść wywołania w tryCatch() wewnątrz funkcji albo użyj tryCatch() wokół całego wywołania parLapply(), aby łagodnie obsługiwać błędy.

library(parallel)
cl <- makeCluster(2)

# Wrap risky code inside the worker function
safe_log <- function(x) {
  tryCatch(
    log(x),
    warning = function(w) NA_real_,
    error   = function(e) NA_real_
  )
}
clusterExport(cl, 'safe_log')

# -1 produces NaN warning, 'a' produces an error
input <- list(4, 9, -1, 'a', 16)
result <- parLapply(cl, input, safe_log)
cat(unlist(result), '\n')  # 1.386 2.197 NaN NA 2.773

stopCluster(cl)

Praktyczny schemat obliczeń równoległych

Oto kompletny, idiomatyczny przebieg pracy z obliczeniami równoległymi: wykryj liczbę rdzeni, utwórz klaster, wyeksportuj zależności, wykonaj obliczenia, zbierz wyniki i zawsze zatrzymaj klaster — nawet jeśli wystąpi błąd — używając on.exit().

library(parallel)

run_parallel <- function(data, fn, n_workers = detectCores() - 1) {
  cl <- makeCluster(n_workers)
  on.exit(stopCluster(cl))  # guaranteed cleanup

  clusterExport(cl, 'fn', envir = environment())

  result <- parLapply(cl, data, fn)
  result
}

# Example: bootstrap mean estimation
samples <- lapply(1:100, function(i) rnorm(50, mean = 5, sd = 2))
means <- run_parallel(samples, mean)

cat('Grand mean:', round(mean(unlist(means)), 3), '\n')
cat('95% CI: [',
    round(quantile(unlist(means), 0.025), 3), ',',
    round(quantile(unlist(means), 0.975), 3), ']\n')

Szybkie sprawdzenie

Jakiej funkcji należy użyć, aby zagwarantować wywołanie stopCluster(cl) nawet wtedy, gdy w kodzie równoległym wystąpi błąd?

Podsumowanie: pakiet parallel

Najważniejsze informacje:

  • detectCores() podaje liczbę dostępnych rdzeni; użyj detectCores() - 1 jako rozmiaru klastra
  • makeCluster(n) / stopCluster(cl) zarządzają cyklem życia procesów roboczych
  • clusterExport() kopiuje obiekty, a clusterEvalQ() uruchamia kod konfiguracyjny w procesach roboczych
  • parLapply() / parSapply() to wieloplatformowe iteratory równoległe
  • mclapply() działa tylko w systemach Unix, ale szybciej się uruchamia dzięki forkowaniu
  • Używaj on.exit(stopCluster(cl)), aby zagwarantować sprzątanie zasobów
  • Obliczenia równoległe opłacają się tylko w przypadku zadań wymagających dużej mocy obliczeniowej
# Minimal reproducible parallel pattern
library(parallel)

cl <- makeCluster(max(1, detectCores() - 1))
on.exit(stopCluster(cl))

clusterSetRNGStream(cl, iseed = 42)
result <- parSapply(cl, 1:8, function(x) x^2 + rnorm(1, 0, 0.1))
cat(round(result, 2), '\n')
Bezpłatny start

Ucz się R dzięki korepetycjom AI — za darmo

Pisz i uruchamiaj kod w przeglądarce, otrzymuj natychmiastową pomoc od korepetytora AI dostępnego 24/7 i kontynuuj naukę w sieci lub w aplikacji.

Kursy
43
Lekcje
159

Często zadawane pytania

Czy lekcja „Pakiet parallel i detectCores()” jest bezpłatna?

Tak — pełny tekst „Pakiet parallel i detectCores()” 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 R Academy, przejdź na CoddyKit PRO. Kurs R Academy zawiera 4 lekcji w sumie.

Co nauczysz się w „Pakiet parallel i detectCores()”?

Uruchamiaj klastry forkowane lub gniazdowe i rozdzielaj pracę między rdzenie procesora. Ćwiczysz R 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ąć R Academy?

Nie wymagamy żadnego doświadczenia. R 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 1 z 4.

Ile czasu zajmuje lekcja „Pakiet parallel i detectCores()”?

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 R Academy?

Tak. Każda lekcja R 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

  1. Pakiet parallel i detectCores()
  2. Framework future
  3. furrr: równoległe operacje purrr
  4. Debugowanie i równoważenie obciążenia kodu równoległego
← Powrót do R Academy