Tại sao vòng lặp chậm trong R?


86

Tôi biết rằng các vòng lặp diễn ra chậm Rvà thay vào đó tôi nên cố gắng thực hiện mọi việc theo cách thức vecto.

Nhưng tại sao? Tại sao vòng lặp chậm và applynhanh? applygọi một số chức năng con - điều đó có vẻ không nhanh.

Cập nhật: Tôi xin lỗi, câu hỏi đã được đặt sai. Tôi đã nhầm lẫn vectorisation với apply. Câu hỏi của tôi lẽ ra phải là,

"Tại sao vectorisation nhanh hơn?"


3
Tôi có ấn tượng rằng "apply là cách nhanh hơn so với vòng lặp for" trong R là một chút hoang đường . Hãy để cho system.timecuộc chiến ở các câu trả lời bắt đầu ...
Joran

1
Rất nhiều thông tin tốt ở đây về chủ đề: stackoverflow.com/questions/2275896/…
Đuổi theo

7
Đối với hồ sơ: Áp dụng KHÔNG phải là vector hóa. Áp dụng là một cấu trúc vòng lặp với các tác dụng phụ khác nhau (như trong: không). Xem thảo luận @Chase liên kết tới.
Joris Meys

4
Các vòng lặp trong S ( S-Plus ?) Theo truyền thống rất chậm. Đây không phải là trường hợp của R ; như vậy, Câu hỏi của bạn không thực sự phù hợp. Tôi không biết tình hình với S-Plus hôm nay như thế nào.
Gavin Simpson

4
Tôi không rõ tại sao câu hỏi lại bị bỏ phiếu nhiều - câu hỏi này rất phổ biến ở những người đến R từ các khu vực khác, và nên được thêm vào Câu hỏi thường gặp.
Patrickmdnet

Câu trả lời:


69

Các vòng lặp trong R chậm vì cùng một lý do khiến bất kỳ ngôn ngữ thông dịch nào cũng chậm: mọi hoạt động đều mang thêm rất nhiều hành lý.

Nhìn vào R_execClosuretrongeval.c (đây là chức năng gọi để gọi một chức năng người dùng định nghĩa). Nó dài gần 100 dòng và thực hiện tất cả các loại hoạt động - tạo môi trường để thực thi, gán các đối số vào môi trường, v.v.

Hãy nghĩ xem sẽ ít hơn bao nhiêu khi bạn gọi một hàm trong C (đẩy args lên để xếp, nhảy, bật args).

Vì vậy, đó là lý do tại sao bạn nhận được thời gian như thế này (như joran đã chỉ ra trong nhận xét, nó không thực sự applynhanh; đó là vòng lặp C nội bộ trong mean đó đang nhanh. applyChỉ là mã R cũ thông thường):

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

Sử dụng một vòng lặp: 0,342 giây:

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

Sử dụng sum: nhỏ vô ưu:

sum(A)

Nó có một chút bối rối bởi vì, về mặt tiệm cận, vòng lặp cũng tốt như sum; không có lý do thực tế nào mà nó phải chậm; nó chỉ làm thêm công việc mỗi lần lặp lại.

Vì vậy, hãy xem xét:

# 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
    }
})

(Ví dụ đó được phát hiện bởi Radford Neal )

Bởi vì (trong R là một toán tử và thực sự yêu cầu tra cứu tên mỗi khi bạn sử dụng nó:

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

Hoặc, nói chung, các hoạt động được thông dịch (bằng bất kỳ ngôn ngữ nào) có nhiều bước hơn. Tất nhiên, những bước đó cũng mang lại lợi ích: bạn không thể làm điều đó (trong C.


10
Vậy điểm của ví dụ cuối cùng là gì? Đừng làm điều ngu ngốc trong R và mong đợi nó làm chúng nhanh chóng?
Đuổi theo

6
@Chase Tôi đoán đó là một cách để nói điều đó. Vâng, ý tôi là một ngôn ngữ như C sẽ không có sự khác biệt về tốc độ với các dấu ngoặc đơn lồng nhau, nhưng R không tối ưu hóa hoặc biên dịch.
Owen

1
Ngoài ra (), hoặc {} trong thân vòng lặp - tất cả những thứ này liên quan đến việc tra cứu tên. Hay nói chung, trong R khi bạn viết nhiều hơn, trình thông dịch sẽ làm được nhiều hơn.
Owen

1
Tôi không chắc bạn đang cố gắng thực hiện điểm nào với các for()vòng lặp? Họ không làm điều tương tự chút nào. Các for()vòng lặp được lặp lại trên mỗi phần tử của Avà tổng hợp chúng. Lời apply()gọi đang chuyển toàn bộ vectơ A[,1](của bạn Acó một cột duy nhất) đến một hàm vectorised mean(). Tôi không thấy điều này giúp ích gì cho cuộc thảo luận và chỉ làm bối rối tình hình.
Gavin Simpson

3
@Owen Tôi đồng ý với quan điểm chung của bạn, và đó là một quan điểm quan trọng; chúng tôi không sử dụng R vì nó phá vỡ kỷ lục tốc độ, chúng tôi sử dụng nó vì nó dễ sử dụng và rất mạnh mẽ. Sức mạnh đó đi kèm với cái giá của sự diễn giải. Nó chỉ không rõ ràng những gì bạn đang cố gắng thể hiện trong ví dụ for()vs. apply()Tôi nghĩ bạn nên loại bỏ ví dụ đó vì trong khi tổng kết là phần lớn của việc tính toán giá trị trung bình, tất cả những gì ví dụ của bạn thực sự cho thấy là tốc độ của một hàm vectorised mean(), qua phép lặp giống C trên các phần tử.
Gavin Simpson

78

Không phải lúc nào vòng lặp cũng chậm và applynhanh. Có một cuộc thảo luận thú vị về điều này trên tạp chí R News vào tháng 5 năm 2008 :

Uwe Ligges và John Fox. R Bộ phận trợ giúp: Làm cách nào để tránh vòng lặp này hoặc làm cho nó nhanh hơn? R News, 8 (1): 46-50, tháng 5 năm 2008.

Trong phần "Vòng lặp!" (bắt đầu từ trang 48), họ nói:

Nhiều ý kiến ​​về trạng thái R rằng sử dụng vòng lặp là một ý tưởng đặc biệt tồi. Điều này không thực sự đúng. Trong một số trường hợp, rất khó để viết mã vectơ hóa hoặc mã vectơ hóa có thể tiêu tốn một lượng lớn bộ nhớ.

Họ đề xuất thêm:

  • Khởi tạo các đối tượng mới với độ dài đầy đủ trước vòng lặp, thay vì tăng kích thước của chúng trong vòng lặp.
  • Không làm những việc trong một vòng lặp mà có thể thực hiện bên ngoài vòng lặp.
  • Đừng tránh các vòng lặp chỉ vì mục đích tránh các vòng lặp.

Họ có một ví dụ đơn giản trong đó một forvòng lặp mất 1,3 giây nhưng applyhết bộ nhớ.


35

Câu trả lời duy nhất cho Câu hỏi được đặt ra là; vòng lặp không chậm nếu những gì bạn cần làm là lặp lại trên một tập hợp dữ liệu thực hiện một số chức năng và chức năng đó hoặc hoạt động không được vector hóa. for()Nói chung, một vòng lặp sẽ nhanh bằng apply(), nhưng có thể chậm hơn một chút so với một lapply()cuộc gọi. Điểm cuối cùng được đề cập kỹ lưỡng trên SO, ví dụ như trong Câu trả lời này và áp dụng nếu mã liên quan đến việc thiết lập và vận hành vòng lặp là một phần quan trọng trong gánh nặng tính toán tổng thể của vòng lặp .

Tại sao nhiều người nghĩ rằng for()vòng lặp chậm là do họ, người dùng, đang viết mã xấu. Nói chung (mặc dù có một số ngoại lệ), nếu bạn cần mở rộng / phát triển một đối tượng, điều đó cũng sẽ liên quan đến việc sao chép, do đó bạn có cả chi phí sao chép phát triển đối tượng. Điều này không chỉ giới hạn ở các vòng lặp, nhưng nếu bạn sao chép / phát triển ở mỗi lần lặp lại của một vòng lặp, tất nhiên, vòng lặp sẽ chậm vì bạn phải chịu nhiều thao tác sao chép / phát triển.

Thành ngữ chung để sử dụng for()vòng lặp trong R là bạn phân bổ dung lượng lưu trữ mà bạn yêu cầu trước khi vòng lặp bắt đầu, và sau đó điền vào đối tượng được cấp phát do đó. Nếu bạn làm theo thành ngữ đó, các vòng lặp sẽ không bị chậm. Đây là những gì apply()quản lý cho bạn, nhưng nó chỉ bị ẩn khỏi chế độ xem.

Tất nhiên, nếu một hàm vectorised tồn tại cho hoạt động bạn đang thực hiện với for()vòng lặp, đừng làm điều đó . Tương tự như vậy, không sử dụng apply()vv nếu tồn tại một hàm vectorised (ví dụ: apply(foo, 2, mean)được thực hiện tốt hơn thông qua colMeans(foo)).


9

Chỉ là một so sánh (đừng đọc quá nhiều!): Tôi đã chạy một vòng lặp for (rất) đơn giản trong R và trong JavaScript trong Chrome và IE 8. Lưu ý rằng Chrome thực hiện biên dịch sang mã gốc và R với trình biên dịch gói biên dịch thành 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, mất 1162 mili giây trong S-Plus ...

Và mã "giống" với 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;
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.