R Academy · Pelajaran

Men-debug dan Menyeimbangkan Beban Kode Paralel

Tangani galat pada pekerja paralel dan seimbangkan beban kerja yang tidak merata secara efektif.

Pelajaran 4 dari 413 langkah

Men-debug dan Menyeimbangkan Beban Kode Paralel adalah pelajaran R Academy gratis di CoddyKit. Ini adalah pelajaran 4 dari 4. Kamu bisa membaca pelajaran lengkapnya di bawah secara gratis — lalu praktikkan langsung di browser dengan editor kode bawaan dan tutor AI 24/7. Ini adalah bagian dari jalur belajar R Academy, dan progresmu tersinkronisasi di web dan aplikasi CoddyKit. Kursus R Academy mencakup 4 pelajaran total.

Mengapa Debugging Paralel Itu Sulit

Debugging kode paralel menantang karena pekerja berjalan dalam proses terpisah: pernyataan print() tidak terlihat oleh proses utama, debugger interaktif seperti browser() tidak berfungsi di dalam pekerja, dan error diserialisasi lalu dihasilkan kembali pada proses utama sehingga jejak tumpukan aslinya hilang.

library(parallel)

# Problem: cat() inside worker is invisible in the master
cl <- makeCluster(2)
clusterExport(cl, c())

result <- parLapply(cl, 1:4, function(i) {
  # This cat() goes to the worker's stdout, NOT the master
  cat('Worker processing', i, '\n')  # you will NOT see this
  i^2
})

cat('Master received:', unlist(result), '\n')
stopCluster(cl)

Penerusan Error dalam parLapply

Ketika pekerja menghasilkan error, parLapply() membungkusnya lalu menghasilkan kembali error tersebut pada proses utama. Pesan error dipertahankan, tetapi jejak tumpukan dari jarak jauh tidak. Selalu uji fungsi Anda secara berurutan terlebih dahulu sebelum menjalankannya secara paralel.

library(parallel)
cl <- makeCluster(2)

# Step 1: test sequentially first!
my_fn <- function(x) {
  if (x == 3) stop('bad value: 3')
  sqrt(x)
}

# Sequential test reveals the bug before parallelising
tryCatch(
  lapply(1:5, my_fn),
  error = function(e) cat('Sequential error caught:', e$message, '\n')
)

# Fix: guard the bad case
my_fn_safe <- function(x) {
  if (x <= 0) return(NA_real_)
  sqrt(x)
}
clusterExport(cl, 'my_fn_safe')
cat(unlist(parLapply(cl, 1:5, my_fn_safe)), '\n')

stopCluster(cl)

tryCatch di Dalam Fungsi Pekerja

Membungkus isi fungsi pekerja dengan tryCatch() memungkinkan Anda menangkap dan mencatat error untuk setiap elemen tanpa membuat seluruh pekerjaan paralel gagal. Kembalikan nilai penanda (misalnya NA) saat terjadi kegagalan agar pemrosesan pasca-eksekusi dapat mengenali input yang bermasalah.

library(parallel)
cl <- makeCluster(2)

safe_compute <- function(x) {
  tryCatch({
    if (x == 3) stop('simulated error on element 3')
    list(value = x^2, error = NULL)
  }, error = function(e) {
    list(value = NA_real_, error = conditionMessage(e))
  })
}
clusterExport(cl, 'safe_compute')

results <- parLapply(cl, 1:5, safe_compute)

for (i in seq_along(results)) {
  r <- results[[i]]
  if (is.null(r$error)) {
    cat('Element', i, ': value =', r$value, '\n')
  } else {
    cat('Element', i, ': ERROR -', r$error, '\n')
  }
}

stopCluster(cl)

future::value() dengan tryCatch

Dalam paket future, value(f) menghasilkan kembali error dari jarak jauh. Membungkusnya dengan tryCatch() memungkinkan Anda menangani kegagalan future satu per satu sambil terus mengumpulkan hasil dari future lainnya.

library(future)
plan(multisession, workers = 2)

# Create futures — some will fail
inputs <- list(4, -1, 9, 'x', 16)

futures <- lapply(inputs, function(val) {
  future({
    if (!is.numeric(val)) stop('non-numeric input')
    if (val < 0) stop('negative input')
    sqrt(val)
  })
})

# Collect with individual error handling
results <- lapply(futures, function(f) {
  tryCatch(
    value(f),
    error = function(e) paste('ERROR:', e$message)
  )
})

for (i in seq_along(results)) {
  cat('Input:', inputs[[i]], '-> Result:', as.character(results[[i]]), '\n')
}

plan(sequential)

foreach dengan %dopar% dan .errorhandling

Operator %dopar% dari paket foreach mendistribusikan iterasi ke seluruh backend yang telah didaftarkan. Argumen .errorhandling mengendalikan tindakan saat terjadi error: 'stop' (bawaan), 'remove' (lewati), atau 'pass' (sertakan objek kondisi).

library(foreach)
library(doParallel)

cl <- makeCluster(2)
registerDoParallel(cl)

# .errorhandling = 'pass': failed elements return the condition
results <- foreach(
  x = 1:6,
  .errorhandling = 'pass'
) %dopar% {
  if (x == 4) stop('bad element')
  x^2
}

for (i in seq_along(results)) {
  if (inherits(results[[i]], 'error')) {
    cat('Element', i, ': ERROR -', results[[i]]$message, '\n')
  } else {
    cat('Element', i, ': value =', results[[i]], '\n')
  }
}

stopCluster(cl)

Pembagian Beban: Statis vs Dinamis

Penjadwalan statis menetapkan lebih dahulu potongan berukuran sama kepada para pekerja. Penjadwalan dinamis memberikan satu tugas kepada setiap pekerja pada satu waktu, sehingga pekerja yang cepat mengambil lebih banyak tugas. Penjadwalan dinamis lebih baik jika durasi tugas sangat bervariasi.

library(parallel)
cl <- makeCluster(2)

# Simulate unequal task durations (element i takes i*0.05 seconds)
unequal_task <- function(i) {
  Sys.sleep(i * 0.05)
  i
}
clusterExport(cl, 'unequal_task')

# Static: parLapply distributes in fixed chunks
static_time <- system.time(
  parLapply(cl, 1:6, unequal_task)
)[['elapsed']]

# Dynamic: clusterApplyLB assigns one task per available worker
dynamic_time <- system.time(
  clusterApplyLB(cl, 1:6, unequal_task)
)[['elapsed']]

cat('Static LB: ', round(static_time, 2), 's\n')
cat('Dynamic LB:', round(dynamic_time, 2), 's\n')

stopCluster(cl)

Pengelompokan: Mengurangi Beban Tambahan

Komunikasi antarproses memiliki beban tambahan tetap untuk setiap tugas. Saat memproses banyak elemen kecil, kelompokkan elemen tersebut menjadi potongan yang lebih besar agar setiap pemanggilan pekerja melakukan lebih banyak pekerjaan per pesan, sehingga mengurangi rasio beban tambahan terhadap perhitungan.

library(parallel)
cl <- makeCluster(2)

# Naive: 1000 tiny tasks — high overhead
tiny_task <- function(x) x^2
clusterExport(cl, 'tiny_task')

t1 <- system.time(parLapply(cl, 1:1000, tiny_task))[['elapsed']]

# Chunked: 10 tasks of 100 items each — low overhead
chunk_task <- function(chunk) sapply(chunk, function(x) x^2)
chunks <- split(1:1000, cut(1:1000, 10, labels = FALSE))
clusterExport(cl, 'chunk_task')

t2 <- system.time(parLapply(cl, chunks, chunk_task))[['elapsed']]

cat('Unchunked:', round(t1, 4), 's\n')
cat('Chunked:  ', round(t2, 4), 's\n')

stopCluster(cl)

Hindari Mengirim Objek Besar

Serialisasi objek besar (bingkai data, matriks, model) ke pekerja membutuhkan biaya besar. Sebagai gantinya, teruskan hanya indeksnya dan baca data dari sumber bersama (file, basis data, atau data yang telah dimuat di dalam pekerja). Pertahankan muatan pekerja agar tetap kecil.

library(parallel)
cl <- makeCluster(2)

# BAD: sends the entire large data frame to every worker
big_df <- data.frame(x = rnorm(1e5), y = rnorm(1e5))
clusterExport(cl, 'big_df')  # 800 KB shipped to each worker

# BETTER: workers generate/read their own data slice
clusterEvalQ(cl, {
  set.seed(Sys.getpid())  # unique per worker
  local_data <- data.frame(x = rnorm(500), y = rnorm(500))
})

# Each worker uses its own local_data without receiving it from master
result <- parLapply(cl, 1:2, function(i) {
  coef(lm(y ~ x, data = local_data))
})
print(result)

stopCluster(cl)

Pencatatan dari Pekerja

Karena pekerja tidak dapat menulis ke konsol proses utama, arahkan keluaran pekerja ke file pencatatan per pekerja menggunakan makeCluster(outfile = '/path/to/log'). Di Unix, Anda dapat mengarahkannya ke /dev/null atau ke file pencatatan khusus untuk setiap pekerja.

library(parallel)

# Route all worker stdout/stderr to a log file
log_file <- tempfile(fileext = '.log')
cl <- makeCluster(2, outfile = log_file)

clusterEvalQ(cl, {
  cat('[Worker', Sys.getpid(), '] started\n')
})

parLapply(cl, 1:4, function(i) {
  cat('[Worker] processing item', i, '\n')  # goes to log file
  i * 10
})

stopCluster(cl)

# Read the log
log_content <- readLines(log_file)
cat(head(log_content, 8), sep = '\n')

Membuat Profil Kode Paralel

Gunakan system.time() untuk mengukur total waktu jam dinding, lalu uraikan beban tambahan pekerja dan waktu perhitungan secara manual. Untuk mendapatkan detail lebih lanjut, jalankan fungsi target secara berurutan dengan profvis::profvis() terlebih dahulu, lalu paralelkan bagian yang menjadi hambatan.

library(parallel)

# Profile the task sequentially first
target_fn <- function(n) {
  x <- rnorm(n)
  list(
    mean = mean(x),
    sd   = sd(x),
    q95  = quantile(x, 0.95)
  )
}

# Measure sequential baseline
t_seq <- system.time(lapply(rep(1000, 20), target_fn))[['elapsed']]

# Measure parallel speedup
cl <- makeCluster(2)
clusterExport(cl, 'target_fn')
t_par <- system.time(parLapply(cl, rep(1000, 20), target_fn))[['elapsed']]
stopCluster(cl)

cat('Sequential:', round(t_seq, 4), 's\n')
cat('Parallel:  ', round(t_par, 4), 's\n')
cat('Efficiency:', round(t_seq / (t_par * 2) * 100, 1), '%\n')

Pola Tangguh Lengkap

Dengan menggabungkan semua praktik terbaik: kelompokkan pekerjaan, gunakan penetapan dengan pembagian beban, tangani error per elemen, catat ke file, dan pastikan pembersihan klaster dengan on.exit().

library(parallel)

robust_parallel <- function(items, fn, workers = 2) {
  cl <- makeCluster(workers, outfile = tempfile())
  on.exit(stopCluster(cl), add = TRUE)

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

  safe_fn <- function(x) {
    tryCatch(
      fn(x),
      error = function(e) list(error = e$message, value = NA)
    )
  }
  clusterExport(cl, 'safe_fn', envir = environment())

  # Load-balanced assignment for unequal task times
  clusterApplyLB(cl, items, safe_fn)
}

results <- robust_parallel(1:8, function(x) {
  if (x == 5) stop('deliberate error')
  x^3
})

cat('Results:\n')
for (i in seq_along(results)) {
  cat(' [', i, ']', ifelse(is.na(results[[i]]$value),
    paste('ERROR:', results[[i]]$error),
    results[[i]]
  ), '\n')
}

Pemeriksaan Singkat

Anda memiliki 100 tugas paralel dengan waktu eksekusi yang sangat bervariasi (sebagian memerlukan 10 md, sebagian lainnya 500 md). Strategi penjadwalan mana yang akan meminimalkan total waktu jam dinding?

Ringkasan: Debugging Kode Paralel

Hal-hal penting:

  • Uji fungsi secara berurutan sebelum menjalankannya secara paralel—gunakan lapply() terlebih dahulu
  • Bungkus isi fungsi pekerja dengan tryCatch() untuk menangkap error per elemen tanpa membuat pekerjaan gagal
  • future::value() dengan tryCatch() menangani kegagalan future satu per satu dengan baik
  • foreach %dopar% dengan .errorhandling = 'pass' mengembalikan objek error dalam daftar hasil
  • Gunakan clusterApplyLB() untuk pembagian beban dinamis dengan durasi tugas yang tidak sama
  • Kelompokkan tugas kecil untuk mengurangi beban tambahan komunikasi
  • Pertahankan muatan pekerja seminimal mungkin—teruskan indeks, bukan objek data besar
  • Catat keluaran pekerja ke file melalui makeCluster(outfile=)
# Canonical debugging workflow
# 1. Test sequentially
# lapply(inputs, my_fn)

# 2. Wrap in tryCatch
# safe_fn <- function(x) tryCatch(my_fn(x), error = function(e) NA)

# 3. Parallelise the safe version
# cl <- makeCluster(2); on.exit(stopCluster(cl))
# parLapply(cl, inputs, safe_fn)

cat('Debugging workflow: sequential -> tryCatch -> parallel\n')
Gratis untuk memulai

Belajar R dengan tutor AI — gratis

Tulis dan jalankan kode asli di browser kamu, dapatkan bantuan instan dari tutor AI 24/7, dan lanjutkan di mana kamu tinggalkan di web atau aplikasi.

Kursus
43
Pelajaran
159

Pertanyaan yang Sering Diajukan

Apakah pelajaran “Men-debug dan Menyeimbangkan Beban Kode Paralel” gratis?

Ya — teks lengkap “Men-debug dan Menyeimbangkan Beban Kode Paralel” gratis dibaca di sini di web. Untuk praktiknya secara interaktif (editor kode bawaan dan tutor AI 24/7) dan buka sisa kursus R Academy, upgrade ke CoddyKit PRO. Kursus R Academy mencakup 4 pelajaran total.

Apa yang akan aku pelajari di “Men-debug dan Menyeimbangkan Beban Kode Paralel”?

Tangani galat pada pekerja paralel dan seimbangkan beban kerja yang tidak merata secara efektif. Kamu berlatih R Academy dengan kode praktik yang langsung kamu jalankan di browser, dan tutor AI 24/7 menjawab pertanyaanmu saat kamu mengerjakan pelajaran ini.

Apakah aku perlu pengalaman untuk memulai R Academy?

Tidak diperlukan pengalaman sebelumnya. R Academy di CoddyKit dirancang untuk pemula hingga pelajar tingkat lanjut, jadi kamu bisa memulai di sini atau dari awal dan belajar sesuai kecepatan kamu sendiri. Ini adalah pelajaran 4 dari 4.

Berapa lama pelajaran “Men-debug dan Menyeimbangkan Beban Kode Paralel” memakan waktu?

Sebagian besar pelajaran CoddyKit memakan waktu sekitar 5–10 menit. Setiap pelajaran ringkas dan interaktif, jadi kamu membuat kemajuan stabil dan melanjutkan dari tempat kamu tinggalkan di web dan aplikasi.

Bisakah aku menulis dan menjalankan kode dalam pelajaran R Academy ini?

Ya. Setiap pelajaran R Academy menyertakan editor kode bawaan, jadi kamu menulis dan menjalankan kode nyata langsung di browser dan mendapatkan umpan balik AI instan — tidak diperlukan penyiapan lokal.

Semua pelajaran dalam kursus ini

  1. Paket parallel dan detectCores()
  2. Kerangka Kerja future
  3. furrr: Operasi purrr Paralel
  4. Men-debug dan Menyeimbangkan Beban Kode Paralel
← Kembali ke R Academy