เวกเตอร์ไรซ์เพื่อความเร็ว
แทนที่ลูปที่เขียนอย่างชัดเจนด้วยการดำเนินการแบบเวกเตอร์เพื่อเพิ่มความเร็วอย่างมาก
เวกเตอร์ไรซ์เพื่อความเร็ว เป็นบทเรียน 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)— ค่า FALSEvector('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 ในทันที — ไม่ต้องติดตั้งในเครื่องของคุณ
บทเรียนทั้งหมดในหลักสูตรนี้
- system.time() และ proc.time()
- การวิเคราะห์ประสิทธิภาพโค้ดด้วย Rprof และ profvis
- เวกเตอร์ไรซ์เพื่อความเร็ว
- การเปรียบเทียบประสิทธิภาพด้วย microbenchmark