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

เวกเตอร์ไรซ์เพื่อความเร็ว

แทนที่ลูปที่เขียนอย่างชัดเจนด้วยการดำเนินการแบบเวกเตอร์เพื่อเพิ่มความเร็วอย่างมาก

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

เหตุใดการประมวลผลแบบเวกเตอร์จึงสำคัญ

R เป็นภาษาแบบแปลคำสั่ง ดังนั้นลูป for จึงมีค่าใช้จ่ายในทุกครั้งที่วนซ้ำ ทั้งการส่งต่อการเรียกฟังก์ชัน การตรวจสอบขอบเขต และการแปลงชนิดข้อมูล การดำเนินการแบบเวกเตอร์ย้ายงานเหล่านี้ไปยังโค้ด C ที่คอมไพล์แล้ว ซึ่งทำงานได้เร็วกว่าอย่างมหาศาล

การประมวลผลแบบเวกเตอร์เป็นการปรับปรุงประสิทธิภาพที่ส่งผลมากที่สุดใน R พื้นฐาน

ตัวอย่างลูปเทียบกับ cumsum()

การคำนวณผลรวมสะสมด้วยลูป for เทียบกับ cumsum() ที่มีมาให้ในตัว แสดงความแตกต่างได้อย่างชัดเจน cumsum() เรียกใช้โค้ดที่คอมไพล์ระดับ C และประมวลผลเวกเตอร์ทั้งหมดในรอบเดียว

n <- 500000
x <- rnorm(n)

t_loop <- system.time({
  result <- numeric(n)
  result[1] <- x[1]
  for (i in 2:n) result[i] <- result[i-1] + x[i]
})['elapsed']

t_vec <- system.time({
  result2 <- cumsum(x)
})['elapsed']

cat('Loop  :', t_loop, 's
')
cat('cumsum:', t_vec, 's
')

ifelse() เทียบกับ for + if

ifelse(condition, yes, no) เป็นเงื่อนไขแบบเวกเตอร์ที่ประเมินเงื่อนไขกับเวกเตอร์ทั้งหมดในครั้งเดียว โดยแทนที่ลูป for + if ที่ทำงานทีละองค์ประกอบด้วยการประมวลผลระดับ C เพียงรอบเดียว

n <- 300000
x <- rnorm(n)

t_loop <- system.time({
  result <- numeric(n)
  for (i in seq_len(n)) result[i] <- if (x[i] > 0) x[i] else -x[i]
})['elapsed']

t_vec <- system.time({
  result2 <- ifelse(x > 0, x, -x)
})['elapsed']

cat('for+if :', t_loop, 's
')
cat('ifelse :', t_vec, 's
')

การจองเวกเตอร์ผลลัพธ์ล่วงหน้า

เมื่อหลีกเลี่ยงลูปไม่ได้ ให้จองเวกเตอร์ผลลัพธ์ล่วงหน้าก่อนเริ่มลูป การขยายเวกเตอร์ด้วย c(result, new_val) ภายในลูปจะคัดลอกเวกเตอร์ทั้งหมดในทุกครั้งที่วนซ้ำ ทำให้มีการดำเนินการกับหน่วยความจำรวมเป็น O(n^2)

n <- 20000

t_grow <- system.time({
  result <- c()
  for (i in seq_len(n)) result <- c(result, i^2)
})['elapsed']

t_prealloc <- system.time({
  result2 <- numeric(n)
  for (i in seq_len(n)) result2[i] <- i^2
})['elapsed']

cat('Growing vector:', t_grow, 's
')
cat('Pre-allocated :', t_prealloc, 's
')

ชนิดข้อมูลที่ถูกต้องสำหรับการจองล่วงหน้า

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

  • numeric(n) — จำนวนทศนิยมความละเอียดสองเท่า
  • integer(n) — จำนวนเต็ม
  • character(n) — ข้อความว่าง
  • logical(n) — ค่า FALSE
  • vector('list', n) — รายการของค่า NULL
n <- 5
cat('numeric  :', numeric(n), '
')
cat('integer  :', integer(n), '
')
cat('logical  :', logical(n), '
')
cat('character:', character(n), '
')
cat('list len :', length(vector('list', n)), '
')

colSums() และ rowSums() เทียบกับ apply()

สำหรับการดำเนินการกับเมทริกซ์ colSums(m), rowSums(m), colMeans(m) และ rowMeans(m) เป็นรูทีน C ที่ปรับประสิทธิภาพมาอย่างดี จึงเร็วกว่า apply(m, 1, sum) อย่างสม่ำเสมอ ซึ่งต้องเรียกใช้ฟังก์ชัน R sum หนึ่งครั้งต่อแถว

m <- matrix(rnorm(1000 * 2000), nrow = 1000)

t_apply <- system.time(apply(m, 2, sum))['elapsed']
t_colsums <- system.time(colSums(m))['elapsed']

cat('apply(m,2,sum):', t_apply, 's
')
cat('colSums(m)    :', t_colsums, 's
')

การคำนวณแบบเวกเตอร์รวดเร็วเสมอ

การคำนวณพื้นฐานกับเวกเตอร์ — +, -, *, /, ^, sqrt(), log() และ exp() — ล้วนรองรับการประมวลผลแบบเวกเตอร์ โดยทำงานกับแต่ละองค์ประกอบของเวกเตอร์ทั้งหมดภายในการเรียกใช้ C เพียงครั้งเดียว ควรเลือกใช้สิ่งเหล่านี้แทนลูปเสมอ

x <- 1:1000000

t1 <- system.time(y <- x^2 + 2*x + 1)['elapsed']

t2 <- system.time({
  y2 <- numeric(length(x))
  for (i in seq_along(x)) y2[i] <- x[i]^2 + 2*x[i] + 1
})['elapsed']

cat('Vectorized:', t1, 's
')
cat('Loop      :', t2, 's
')

การเลือกข้อมูลย่อยเชิงตรรกะแทนการใช้ลูป

การกรองเวกเตอร์ด้วยเงื่อนไขเชิงตรรกะเป็นการประมวลผลแบบเวกเตอร์ แทนที่จะวนลูปแล้วเพิ่มข้อมูลตามเงื่อนไข ให้สร้างดัชนีเชิงตรรกะแล้วเลือกข้อมูลย่อยเพียงครั้งเดียว — โค้ดภาษา C ที่อยู่เบื้องหลังจะประมวลผลข้อมูลหนึ่งรอบ

x <- rnorm(500000)

t_loop <- system.time({
  pos <- c()
  for (v in x) if (v > 0) pos <- c(pos, v)
})['elapsed']

t_vec <- system.time({
  pos2 <- x[x > 0]
})['elapsed']

cat('Loop filter:', t_loop, 's
')
cat('Logical idx:', t_vec, 's
')

การใช้ which() และ tabulate() กับงานดัชนี

เมื่อจำเป็นต้องหาตำแหน่งของค่า TRUE ให้ใช้ which(condition) ซึ่งประมวลผลแบบเวกเตอร์และทำงานรวดเร็ว ส่วน tabulate(bin_vector) ใช้นับจำนวนการปรากฏของจำนวนเต็มได้เร็วกว่า table() เมื่อช่วงจำนวนเต็มมีข้อมูลหนาแน่น

x <- sample(1:10, 100000, replace = TRUE)

t_table    <- system.time(table(x))['elapsed']
t_tabulate <- system.time(tabulate(x, nbins = 10))['elapsed']

cat('table()   :', t_table, 's
')
cat('tabulate():', t_tabulate, 's
')

idx <- which(x == 5)
cat('Positions of 5: first 5 =', head(idx, 5), '
')

กรณีที่ยังใช้ลูปได้

ลูปไม่ได้ไม่ดีเสมอไป ลูปเหมาะสมเมื่อ:

  • ผลลัพธ์ของการวนแต่ละครั้งขึ้นอยู่กับผลลัพธ์ก่อนหน้า (มีการพึ่งพากันตามลำดับ)
  • จำนวนครั้งที่วนมีไม่มาก (< 1000)
  • ส่วนคำสั่งภายในลูปเรียกใช้ฟังก์ชันที่ซับซ้อนและไม่มีวิธีประมวลผลแบบเวกเตอร์ที่เทียบเท่ากัน

ในกรณีเหล่านี้ ให้เน้นการจองพื้นที่ล่วงหน้า และหลีกเลี่ยงการขยายโครงสร้างข้อมูลภายในลูป

# Sequential dependency -- loop is correct here
fib <- function(n) {
  result <- integer(n)
  result[1] <- 1L
  if (n >= 2) result[2] <- 1L
  for (i in seq_len(n)[-c(1,2)]) result[i] <- result[i-1] + result[i-2]
  result
}
cat('Fibonacci:', fib(10), '
')

สรุปการประมวลผลแบบเวกเตอร์

กฎสำคัญของการประมวลผลแบบเวกเตอร์สำหรับโค้ด R ที่ทำงานรวดเร็ว:

  • ใช้ cumsum/cumprod/diff สำหรับการสะสมค่าตามลำดับ
  • ใช้ ifelse() สำหรับเงื่อนไขที่ใช้กับสมาชิกแต่ละตัว
  • จองพื้นที่ล่วงหน้าด้วย numeric(n) / vector('list',n)
  • ใช้ colSums/rowSums/colMeans/rowMeans แทน apply()
  • การเลือกข้อมูลย่อยเชิงตรรกะมีประสิทธิภาพดีกว่าลูปกรองข้อมูล

ตรวจสอบอย่างรวดเร็ว: การจองพื้นที่ล่วงหน้า

เหตุใดการขยายเวกเตอร์ด้วย result <- c(result, new_val) ภายในลูปจึงช้ามากเมื่อ n มีค่ามาก

ทบทวนการประมวลผลแบบเวกเตอร์

การประมวลผลแบบเวกเตอร์เป็นกลไกหลักที่ช่วยเพิ่มประสิทธิภาพของ R:

  • ฟังก์ชันแบบเวกเตอร์ (cumsum, ifelse, ตัวดำเนินการทางคณิตศาสตร์) เรียกใช้โค้ดภาษา C ที่คอมไพล์แล้ว จึงเร็วกว่า ลูป R ที่ทำงานเทียบเท่ากัน 10–100 เท่า
  • จองพื้นที่ให้ภาชนะเก็บผลลัพธ์ล่วงหน้าก่อนลูปที่หลีกเลี่ยงไม่ได้ เพื่อหลีกเลี่ยงการคัดลอกข้อมูลแบบ O(n^2)
  • colSums/rowSums ทำงานกับการรวมค่าของเมทริกซ์ได้ดีกว่า apply()
  • การเลือกข้อมูลย่อยเชิงตรรกะแทนที่ลูปกรองข้อมูลได้อย่างเรียบร้อยและรวดเร็ว

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

บทเรียน “เวกเตอร์ไรซ์เพื่อความเร็ว” ฟรีหรือไม่

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

คุณจะเรียนรู้อะไรในบทเรียน “เวกเตอร์ไรซ์เพื่อความเร็ว”

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

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

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

บทเรียน “เวกเตอร์ไรซ์เพื่อความเร็ว” ใช้เวลานานแค่ไหน

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

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

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

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

  1. system.time() และ proc.time()
  2. การวิเคราะห์ประสิทธิภาพโค้ดด้วย Rprof และ profvis
  3. เวกเตอร์ไรซ์เพื่อความเร็ว
  4. การเปรียบเทียบประสิทธิภาพด้วย microbenchmark
← กลับไปที่ R Academy