ทำไมลูปจึงช้าใน R


87

ฉันรู้ว่าลูปช้าRและฉันควรพยายามทำสิ่งต่างๆในลักษณะเวกเตอร์แทน

แต่ทำไม? ทำไมลูปจึงช้าและapplyเร็ว applyเรียกใช้ฟังก์ชันย่อยหลายอย่างซึ่งดูเหมือนจะไม่เร็ว

อัปเดต:ขออภัยคำถามไม่ถูกต้อง ฉันสับสนกับเวกapplyเตอร์ คำถามของฉันควรจะเป็น

"เหตุใด vectorisation จึงเร็วกว่า"


3
ผมก็รู้สึกว่า "ใช้เป็นวิธีวิธีที่เร็วกว่าสำหรับลูป" ในการวิจัยเป็นบิตของตำนาน ให้system.timeสงครามในคำตอบเริ่มต้น ...
joran

1
ข้อมูลดีๆมากมายที่นี่ในหัวข้อ: stackoverflow.com/questions/2275896/…
Chase

7
สำหรับบันทึก: Apply is not vectorization นำไปใช้เป็นโครงสร้างแบบวนซ้ำที่มีผลข้างเคียง (ไม่) แตกต่างกัน ดูลิงก์การสนทนา @ Chase
Joris Meys

4
ลูปในS ( S-Plus ?) มักจะช้า นี่ไม่ใช่กรณีของR ; ดังนั้นคำถามของคุณจึงไม่เกี่ยวข้องจริงๆ ไม่ทราบว่าสถานการณ์ของS-Plusวันนี้เป็นอย่างไร
Gavin Simpson

4
ไม่ชัดเจนสำหรับฉันว่าทำไมคำถามถึงได้รับการโหวตอย่างหนัก - คำถามนี้พบบ่อยมากในกลุ่มผู้ที่มาที่ R จากด้านอื่น ๆ และควรเพิ่มไว้ในคำถามที่พบบ่อย
patrickmdnet

คำตอบ:


69

ลูปใน R ช้าด้วยเหตุผลเดียวกันกับภาษาใด ๆ ที่ตีความช้า: การดำเนินการทุกครั้งจะมีสัมภาระส่วนเกินจำนวนมาก

ดูR_execClosureในeval.c (นี่คือฟังก์ชันที่เรียกเพื่อเรียกใช้ฟังก์ชันที่ผู้ใช้กำหนดเอง) มีความยาวเกือบ 100 บรรทัดและดำเนินการได้ทุกประเภท - สร้างสภาพแวดล้อมสำหรับการดำเนินการกำหนดอาร์กิวเมนต์ให้กับสภาพแวดล้อม ฯลฯ

ลองนึกดูว่าจะเกิดอะไรขึ้นน้อยลงเมื่อคุณเรียกใช้ฟังก์ชันใน C (กด args ไปที่ stack, jump, pop args)

นั่นคือเหตุผลที่คุณได้รับการกำหนดเวลาเช่นนี้ (ดังที่ joran ชี้ให้เห็นในความคิดเห็นมันไม่ได้เร็วจริง ๆapplyมันเป็นวง C ภายในmean ที่กำลังเร็วapplyเป็นเพียงรหัส R เก่าปกติ):

A = matrix(as.numeric(1:100000))

ใช้ลูป: 0.342 วินาที:

system.time({
    Sum = 0
    for (i in seq_along(A)) {
        Sum = Sum + A[[i]]
    }
    Sum
})

การใช้ sum: เล็กเหลือทน:

sum(A)

มันค่อนข้างอึกทึกเพราะโดยไม่มีอาการห่วงนั้นดีพอ ๆ กับsum; ไม่มีเหตุผลในทางปฏิบัติที่ควรจะช้า เป็นเพียงการทำงานพิเศษเพิ่มเติมในแต่ละการทำซ้ำ

ดังนั้นพิจารณา:

# 0.370 seconds
system.time({
    I = 0
    while (I < 100000) {
        10
        I = I + 1
    }
})

# 0.743 seconds -- double the time just adding parentheses
system.time({
    I = 0
    while (I < 100000) {
        ((((((((((10))))))))))
        I = I + 1
    }
})

(ตัวอย่างนั้นถูกค้นพบโดยRadford Neal )

เนื่องจาก(ใน R เป็นตัวดำเนินการและต้องใช้การค้นหาชื่อทุกครั้งที่ใช้:

> `(` = function(x) 2
> (3)
[1] 2

หรือโดยทั่วไปการดำเนินการที่ตีความ (ในภาษาใด ๆ ) มีขั้นตอนมากกว่า แน่นอนขั้นตอนเหล่านั้นให้เกิดประโยชน์เช่นกัน: คุณไม่สามารถทำว่า(เคล็ดลับใน C.


10
แล้วประเด็นของตัวอย่างสุดท้ายคืออะไร? อย่าทำอะไรโง่ ๆ ใน R และคาดหวังว่ามันจะทำอย่างรวดเร็ว?
ไล่

6
@ Chase ฉันเดาว่าเป็นวิธีหนึ่งที่จะพูด ใช่ฉันหมายถึงภาษาเช่น C จะไม่มีความแตกต่างของความเร็วกับวงเล็บที่ซ้อนกัน แต่ R ไม่ได้ปรับให้เหมาะสมหรือรวบรวม
Owen

1
นอกจากนี้ () หรือ {} ในเนื้อวนซ้ำ - สิ่งเหล่านี้เกี่ยวข้องกับการค้นหาชื่อ หรือโดยทั่วไปใน R เมื่อคุณเขียนมากขึ้นล่ามจะทำมากขึ้น
Owen

1
ฉันไม่แน่ใจว่าคุณกำลังพยายามสร้างfor()ลูปอะไรอยู่? พวกเขาไม่ได้ทำสิ่งเดียวกันเลย for()วงวนมากกว่าองค์ประกอบของแต่ละAและข้อสรุปพวกเขา apply()โทรผ่านเวกเตอร์ทั้งหมดA[,1](ของคุณAมีคอลัมน์เดียว) mean()เพื่อฟังก์ชั่น ฉันไม่เห็นว่าสิ่งนี้ช่วยให้การอภิปรายและทำให้สถานการณ์สับสนได้อย่างไร
Gavin Simpson

3
@ โอเว่นฉันเห็นด้วยกับประเด็นทั่วไปของคุณและมันเป็นสิ่งที่สำคัญ เราไม่ใช้ R เพราะมันทำลายสถิติความเร็วเราใช้เพราะมันใช้งานง่ายและทรงพลังมาก พลังนั้นมาพร้อมกับราคาของการตีความ มันไม่ชัดเจนว่าคุณพยายามจะแสดงอะไรในตัวอย่างfor()vs apply()ฉันคิดว่าคุณควรลบตัวอย่างนั้นออกในขณะที่การสรุปเป็นส่วนใหญ่ของการคำนวณค่าเฉลี่ยตัวอย่างทั้งหมดของคุณแสดงให้เห็นจริงๆคือความเร็วของฟังก์ชัน vectorised mean()มากกว่าการวนซ้ำแบบ C บนองค์ประกอบ
Gavin Simpson

79

ไม่ใช่กรณีที่การวนapplyซ้ำช้าและเร็วเสมอไป มีการอภิปรายที่ดีเกี่ยวกับเรื่องนี้ในR News ฉบับเดือนพฤษภาคม 2551 :

Uwe Ligges และ John Fox R Help Desk: ฉันจะหลีกเลี่ยงลูปนี้หรือทำให้เร็วขึ้นได้อย่างไร? R News, 8 (1): 46-50, พฤษภาคม 2551.

ในหัวข้อ "ลูป!" (เริ่มตั้งแต่หน้า 48) พวกเขากล่าวว่า:

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

พวกเขาแนะนำเพิ่มเติม:

  • เริ่มต้นอ็อบเจ็กต์ใหม่ให้มีความยาวเต็มที่ก่อนลูปแทนที่จะเพิ่มขนาดภายในลูป
  • อย่าทำสิ่งที่เป็นห่วงที่สามารถทำได้นอกลูป
  • อย่าหลีกเลี่ยงการวนซ้ำเพื่อหลีกเลี่ยงการวนซ้ำ

มีตัวอย่างง่ายๆที่การforวนซ้ำใช้เวลา 1.3 วินาที แต่applyหน่วยความจำหมด


35

คำตอบเดียวสำหรับคำถามที่วางไว้คือ; การวนซ้ำจะไม่ช้าหากสิ่งที่คุณต้องทำคือการทำซ้ำชุดข้อมูลที่ทำหน้าที่บางอย่างและฟังก์ชันนั้นหรือการดำเนินการนั้นไม่ได้เป็นเวกเตอร์ การfor()วนซ้ำจะเร็วเหมือนapply()กัน แต่อาจจะช้ากว่าการlapply()โทรเล็กน้อย จุดสุดท้ายได้รับการคุ้มครองอย่างดีในดังนั้นสำหรับตัวอย่างในเรื่องนี้คำตอบและนำไปใช้ถ้ารหัสมีส่วนร่วมในการตั้งค่าและการดำเนินงานห่วงเป็นส่วนหนึ่งที่สำคัญของการคำนวณภาระโดยรวมของวง

สาเหตุที่หลายคนคิดว่าfor()ลูปทำงานช้าเป็นเพราะผู้ใช้เขียนโค้ดไม่ดี โดยทั่วไป (แม้ว่าจะมีข้อยกเว้นหลายประการ) หากคุณต้องการขยาย / ขยายวัตถุสิ่งนั้นก็เกี่ยวข้องกับการคัดลอกเช่นกันดังนั้นคุณจึงมีทั้งค่าใช้จ่ายในการคัดลอกและขยายวัตถุ สิ่งนี้ไม่ได้ จำกัด เพียงแค่การวนซ้ำเท่านั้น แต่ถ้าคุณคัดลอก / ขยายในการวนซ้ำแต่ละครั้งแน่นอนว่าการวนซ้ำจะช้าเพราะคุณต้องดำเนินการคัดลอก / ขยายจำนวนมาก

สำนวนทั่วไปสำหรับการใช้for()ลูปใน R คือคุณจัดสรรหน่วยเก็บข้อมูลที่คุณต้องการก่อนที่ลูปจะเริ่มต้นจากนั้นกรอกข้อมูลในวัตถุที่จัดสรร หากคุณทำตามสำนวนนั้นลูปจะไม่ช้า นี่คือสิ่งที่apply()จัดการให้คุณ แต่ซ่อนไว้ไม่ให้มองเห็น

แน่นอนถ้าฟังก์ชั่นที่มีอยู่สำหรับ vectorised ดำเนินการที่คุณมีการดำเนินการกับfor()ห่วงไม่ทำอย่างนั้น ในทำนองเดียวกันอย่าใช้apply()ฯลฯ หากมีฟังก์ชัน vectorised อยู่ (เช่นทำงานapply(foo, 2, mean)ได้ดีกว่าผ่านcolMeans(foo))


9

เช่นเดียวกับการเปรียบเทียบ (อย่าอ่านมากเกินไป!): ฉันใช้งานง่าย (มาก) สำหรับการวนซ้ำใน R และใน JavaScript ใน Chrome และ IE 8 โปรดทราบว่า Chrome จะรวบรวมโค้ดเนทีฟและ R กับคอมไพเลอร์ แพ็กเกจคอมไพล์เป็น bytecode

# In R 2.13.1, this took 500 ms
f <- function() { sum<-0.5; for(i in 1:1000000) sum<-sum+i; sum }
system.time( f() )

# And the compiled version took 130 ms
library(compiler)
g <- cmpfun(f)
system.time( g() )

@Gavin Simpson: Btw ใช้เวลา 1162 ms ใน S-Plus ...

และรหัส "เดียวกัน" กับ JavaScript:

// In IE8, this took 282 ms
// In Chrome 14.0, this took 4 ms
function f() {
    var sum = 0.5;
    for(i=1; i<=1000000; ++i) sum = sum + i;
    return sum;
}

var start = new Date().getTime();
f();
time = new Date().getTime() - start;
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.