0Pricing
R Academy · บทเรียน

แพ็กเกจ parallel และ detectCores()

เปิดคลัสเตอร์แบบ fork หรือ socket และกระจายงานไปยังแกนประมวลผล CPU

แพ็กเกจ parallel และ detectCores() เป็นบทเรียน R Academy ฟรีบน CoddyKit นี่คือบทเรียนที่ 1 จากทั้งหมด 4 บทเรียน คุณสามารถอ่านบทเรียนทั้งหมดด้านล่างฟรี — จากนั้นลองปฏิบัติด้วยตัวคุณเองในเบราว์เซอร์พร้อมตัวแก้ไขโค้ดในตัวและติวเตอร์ AI ตลอด 24/7 บทเรียนนี้เป็นส่วนหนึ่งของเส้นทางการเรียน R Academy และความก้าวหน้าของคุณจะซิงค์ข้ามเว็บและแอป CoddyKit คอร์ส R Academy มีบทเรียนทั้งหมด 4 บทเรียน

เหตุใดจึงต้องใช้การประมวลผลแบบขนาน

คอมพิวเตอร์สมัยใหม่มีหลายแกน CPU โดยค่าเริ่มต้น R จะทำงานบนแกนเดียว ทำให้แกนที่เหลือไม่ได้ใช้งาน แพ็กเกจ parallel (ที่มีมาใน R) ช่วยให้คุณใช้แกนทั้งหมดเพื่อเร่งการคำนวณซ้ำ ๆ

# 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 และ stopCluster

makeCluster(n) สร้างกระบวนการผู้ปฏิบัติงานจำนวน n กระบวนการ ควรเรียก stopCluster(cl) เมื่อทำงานเสร็จเสมอเพื่อคืนทรัพยากร โดยทั่วไปมักใช้ detectCores() - 1 เพื่อเหลือหนึ่งแกนไว้ให้ OS

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: การแบ่งปันตัวแปร

กระบวนการผู้ปฏิบัติงานมีพื้นที่หน่วยความจำของตนเองและมองไม่เห็นสภาพแวดล้อมส่วนกลางของคุณ ใช้ clusterExport(cl, varlist) เพื่อคัดลอกออบเจ็กต์ที่ระบุชื่อจากกระบวนการหลักไปยังกระบวนการผู้ปฏิบัติงานทั้งหมด

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: การเรียกใช้โค้ดตั้งค่า

clusterEvalQ(cl, expr) ประเมินนิพจน์บนผู้ปฏิบัติงานทุกตัว — เหมาะสำหรับการโหลดแพ็กเกจหรือเรียกใช้ไฟล์ตัวช่วยบนทุกโหนดก่อนเริ่มการคำนวณหลัก

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: lapply แบบขนาน

parLapply(cl, X, FUN) เป็นรูปแบบเทียบเท่าแบบขนานของ lapply() โดยกระจายสมาชิกของ X ไปยังผู้ปฏิบัติงานและรวบรวมผลลัพธ์เป็นรายการ ใช้งานได้บนทุกแพลตฟอร์ม (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: ผลลัพธ์แบบย่อ

parSapply(cl, X, FUN) เป็นรุ่นแบบขนานของ sapply() โดยจะทำให้ผลลัพธ์เรียบง่ายเป็นเวกเตอร์หรือเมทริกซ์โดยอัตโนมัติเมื่อทำได้ จึงสะดวกเมื่อฟังก์ชันของคุณส่งคืนค่าเดี่ยว

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: การประมวลผลแบบขนานด้วยการแยกกระบวนการ

mclapply() ใช้การแยกกระบวนการ โดยคัดลอกกระบวนการ R ปัจจุบันแทนการสร้างกระบวนการใหม่ ทำให้เริ่มทำงานได้เร็วขึ้นและผู้ปฏิบัติงานเข้าถึงสภาพแวดล้อมของกระบวนการหลักได้โดยปริยาย อย่างไรก็ตาม ใช้ได้เฉพาะบน Unix/macOS ไม่ใช่ 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')
}

การวัดประสิทธิภาพแบบลำดับเทียบกับแบบขนาน

การประมวลผลแบบขนานมีต้นทุนแฝง ทั้งการเริ่มคลัสเตอร์ การทำให้ข้อมูลเป็นอนุกรม และการสื่อสารระหว่างกระบวนการล้วนใช้เวลา จะคุ้มค่าเฉพาะเมื่อการคำนวณต่อสมาชิกมีค่าใช้จ่ายสูงพอที่จะชดเชยต้นทุนแฝงเหล่านั้น

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)

การกำหนดค่าเริ่มต้น RNG ในการประมวลผลแบบขนาน

การสร้างตัวเลขสุ่มแบบขนานทำได้ซับซ้อน — ผู้ปฏิบัติงานแต่ละตัวต้องมีลำดับตัวเลขที่เป็นอิสระและสร้างซ้ำได้ ใช้ clusterSetRNGStream(cl, seed) ร่วมกับตัวสร้าง L'Ecuyer-CMRG เพื่อให้การสุ่มแบบขนานสร้างซ้ำได้

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)

การจัดการข้อผิดพลาดในคลัสเตอร์

หากผู้ปฏิบัติงานทำให้เกิดข้อผิดพลาด parLapply() จะส่งข้อผิดพลาดนั้นต่อในกระบวนการหลัก ให้ครอบการเรียกใช้ด้วย tryCatch() ภายในฟังก์ชัน หรือใช้ tryCatch() ครอบการเรียก parLapply() ทั้งหมดเพื่อจัดการความล้มเหลวอย่างเหมาะสม

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)

รูปแบบการประมวลผลแบบขนานที่ใช้งานได้จริง

ต่อไปนี้คือกระบวนการทำงานแบบขนานที่สมบูรณ์และเป็นรูปแบบตามธรรมเนียม: ตรวจหาจำนวนแกน สร้างคลัสเตอร์ ส่งออกสิ่งที่ต้องพึ่งพา ดำเนินการคำนวณ รวบรวมผลลัพธ์ และหยุดคลัสเตอร์เสมอ — แม้เกิดข้อผิดพลาด — โดยใช้ 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')

ตรวจสอบด่วน

คุณควรใช้ฟังก์ชันใดเพื่อรับประกันว่าจะมีการเรียก stopCluster(cl) แม้เกิดข้อผิดพลาดภายในโค้ดแบบขนาน

ทบทวน: แพ็กเกจ parallel

ประเด็นสำคัญ:

  • detectCores() รายงานจำนวนแกนที่พร้อมใช้งาน ใช้ detectCores() - 1 เป็นขนาดคลัสเตอร์
  • makeCluster(n) / stopCluster(cl) จัดการช่วงอายุการทำงานของผู้ปฏิบัติงาน
  • clusterExport() คัดลอกออบเจ็กต์ ส่วน clusterEvalQ() เรียกใช้โค้ดตั้งค่าบนผู้ปฏิบัติงาน
  • parLapply() / parSapply() เป็นตัววนซ้ำแบบขนานที่ใช้ได้ข้ามแพลตฟอร์ม
  • mclapply() ใช้ได้เฉพาะ Unix แต่เริ่มทำงานได้เร็วกว่าเนื่องจากการแยกกระบวนการ
  • ใช้ on.exit(stopCluster(cl)) เพื่อรับประกันการคืนทรัพยากร
  • การประมวลผลแบบขนานจะคุ้มค่าเฉพาะกับงานที่ใช้การคำนวณมาก
# 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')

คำถามที่พบบ่อย

บทเรียน “แพ็กเกจ parallel และ detectCores()” ฟรีหรือไม่

ใช่ — ข้อความเต็มของ “แพ็กเกจ parallel และ detectCores()” ฟรีให้อ่านที่นี่บนเว็บ เพื่อปฏิบัติแบบโต้ตอบ (ตัวแก้ไขโค้ดในตัวและติวเตอร์ AI ตลอด 24/7) และปลดล็อคส่วนที่เหลือของคอร์ส R Academy ให้อัปเกรดเป็น CoddyKit PRO คอร์ส R Academy มีบทเรียนทั้งหมด 4 บทเรียน

คุณจะเรียนรู้อะไรในบทเรียน “แพ็กเกจ parallel และ detectCores()”

เปิดคลัสเตอร์แบบ fork หรือ socket และกระจายงานไปยังแกนประมวลผล CPU คุณปฏิบัติ R Academy ด้วยโค้ดที่ใช้งานได้จริงที่คุณเรียกใช้โดยตรงในเบราว์เซอร์ และติวเตอร์ AI ตลอด 24/7 ตอบคำถามของคุณขณะที่คุณไปผ่านบทเรียน

คุณต้องมีประสบการณ์ก่อนที่จะเริ่มเรียน R Academy หรือไม่

ไม่จำเป็นต้องมีประสบการณ์มาก่อน R Academy บน CoddyKit ออกแบบมาสำหรับผู้เริ่มต้นไปจนถึงผู้เรียนขั้นสูง คุณสามารถเริ่มต้นที่นี่หรือเริ่มจากตัวแรกและเรียนด้วยความเร็วของคุณเอง นี่คือบทเรียนที่ 1 จากทั้งหมด 4 บทเรียน

บทเรียน “แพ็กเกจ parallel และ detectCores()” ใช้เวลานานแค่ไหน

บทเรียน CoddyKit ส่วนใหญ่ใช้เวลาประมาณ 5–10 นาที แต่ละบทเรียนจึงสั้นและเป็นแบบโต้ตอบ คุณสามารถก้าวหน้าอย่างต่อเนื่องและกลับมาเรียนต่อจากตรงที่เพิ่งหยุดบนเว็บและแอปได้เลย

ฉันเขียนและรันโค้ดในบทเรียน R Academy นี้ได้ไหม

ได้ บทเรียน R Academy ทุกบทมีตัวแก้ไขโค้ดในตัว คุณจึงเขียนและรันโค้ดจริงได้เลยในเบราว์เซอร์ และได้รับข้อเสนอแนะจาก AI ในทันที — ไม่ต้องติดตั้งในเครื่องของคุณ

บทเรียนทั้งหมดในหลักสูตรนี้

  1. แพ็กเกจ parallel และ detectCores()
  2. เฟรมเวิร์ก future
  3. furrr: การดำเนินการ purrr แบบขนาน
  4. การดีบักและการปรับสมดุลภาระงานของโค้ดแบบขนาน
← กลับไปที่ R Academy