10

thể trùng lặp:
Is recursion ever faster than looping?Chi phí đệ quy - mức độ nghiêm trọng của nó?

Tôi được đào tạo đầu tiên cho chương trình nghiêm túc trong C, khoảng 15 năm trước đây. Chủ nhân của tôi muốn mã được tối ưu hóa cao cho các nhiệm vụ khó tính toán. Tôi nhớ được khuyên nhiều hơn một lần để viết lại các cuộc tham khảo như vòng lặp, ngay cả ở mức đắt tiền dễ đọc, để tránh "phí trên mặt đệ quy". Khi tôi hiểu nó sau đó, đệ quy trên không là nỗ lực thêm cần thiết để đẩy dữ liệu vào một ngăn xếp và sau đó bật nó đi.

Bây giờ tôi viết mã bằng C, Python, Perl, và đôi khi Java, và tôi tự hỏi đôi khi về việc thu thập. Vẫn còn một thứ gì đó để đạt được bằng cách viết lại chúng? Điều gì sẽ xảy ra nếu họ đang theo đuôi? Có trình biên dịch hiện đại thực hiện tất cả những vấn đề này tranh luận? Các mối lo ngại đó có liên quan đến ngôn ngữ thông dịch không?

+1

Chức năng gọi điện trên cao có thể thay đổi rất nhiều trên các hệ thống, vì vậy câu hỏi này chỉ có ý nghĩa trong một ngữ cảnh cụ thể. Điều đó nói rằng, tôi nghĩ rằng xu hướng chung trong vài thập kỷ qua đã hướng tới ít chi phí hơn. – dmckee

Trả lời

15

Việc đệ quy có thể dẫn đến chi phí đáng kể nếu hạt nhân của hàm đệ quy ít tốn kém tính toán hơn mã nhập/xuất chức năng và chi phí của cuộc gọi. Cách tốt nhất để tìm hiểu đơn giản là lập hồ sơ hai phiên bản của mã - một trong những đệ quy, và một không.

Điều đó nói rằng, nếu ý tưởng tránh đệ quy của bạn là tự tạo cấu trúc giống như chồng, xem ra - nó có thể không nhất thiết phải nhanh hơn phương pháp đệ quy đơn giản hơn. Một lần nữa, hồ sơ là bạn của bạn.

Cuối cùng, hãy nhớ rằng thời gian lập trình viên đắt hơn thời gian của CPU. Trước khi bạn tối ưu hóa mã của mình, thật sự là một ý tưởng hay để đo lường xem nó có thực sự là vấn đề hay không.

+0

Lưu ý rằng các trình biên dịch giờ đây có thể dịch chuyển đệ quy thành một vòng lặp lặp lại đơn giản, ngay cả khi hàm không đệ quy đuôi. Xem http://www.linux-kongress.org/2009/slides/compiler_survey_felix_von_leitner.pdf. – liori

+0

Tôi sẽ vô cùng ngạc nhiên nếu ai đó kéo ra viết cấu trúc dữ liệu ngăn xếp hiệu quả hơn so với ngăn xếp cuộc gọi của chính CPU…! Tất nhiên, có thể có các lý do khác để sử dụng ngăn xếp thủ công thay vì dựa vào ngăn xếp cuộc gọi, nhưng hiệu suất không bao giờ nên là một trong số đó. –

+1

@Konrad, nó đã được thực hiện, xem ví dụ như thực hiện của glibc qsort (http://goo.gl/6ptK) - mà nói, nó không phải là _easy_ để đánh bại trình biên dịch, và một nỗ lực ngây thơ có khả năng làm hại nhiều hơn tốt. – bdonlan

2

Tôi không nghĩ bất kỳ ngôn ngữ nào bạn đã đề cập yêu cầu rằng nền tảng/trình biên dịch triển khai tail call elimination. Bạn có thể tìm thấy các ngôn ngữ mà làm yêu cầu tối ưu hóa này - hầu hết các ngôn ngữ chức năng đều có yêu cầu này. Tuy nhiên một điều khác bạn cần phải xem xét là các máy tính đã trở thành đơn đặt hàng của magnitudes nhanh hơn so với 15 năm trước đây, do đó, nó hiếm hơn bây giờ thì trước đó bạn cần phải lo lắng về tối ưu hóa vi mô. Một chương trình mà 15 năm trước có thể đã yêu cầu tối ưu hóa tay cẩn thận trong assembler để có được hiệu năng tốt có thể chạy nhanh trên một máy tính hiện đại, ngay cả khi được viết bằng một ngôn ngữ cấp cao hơn như Java. Điều đó không có nghĩa là hiệu suất không bao giờ là vấn đề nữa - nhưng bạn nên tập trung vào việc chọn thuật toán chính xác và trên văn bản có thể đọc được mã. Chỉ thực hiện tối ưu hóa vi mô sau khi bạn đã đo hiệu suất và bạn có thể thấy rằng mã được đề cập là nút cổ chai.

Một điều bạn làm cần phải lo lắng về việc đang tràn ngăn xếp. Nếu có bất kỳ rủi ro nào xảy ra, nó có thể đáng để viết lại một hàm đệ quy theo một cách lặp đi lặp lại.

+0

gcc thực hiện tối ưu hóa đệ quy đuôi. (xem ví dụ: http://stackoverflow.com/questions/490324/how-do-i-check-if-gcc-is-performing-tail-recursion-optimization) – nos

+0

@nos: Được viết lại - hy vọng chính xác hơn bây giờ. –

2

Điều đó là nghiêm trọng. Hầu hết các ngôn ngữ tôi viết mã có chi phí thực sự để thực hiện các cuộc gọi hàm (các trình biên dịch cho chúng thường có thể thực hiện đệ quy đuôi cũng vì vậy đôi khi nó không phải là một vấn đề).

Chi phí đó và thực tế là ngăn xếp không phải là tài nguyên không giới hạn, thường khiến tôi có xu hướng chỉ sử dụng đệ quy cho các trường hợp tôi biết có giới hạn về độ sâu mà nó có thể truy cập.

Ví dụ, tôi biết một tìm kiếm cây nhị phân cân bằng sẽ chỉ đi sâu năm mươi cấp cho một mục nhập nghìn tỷ. Tuy nhiên, tôi sẽ không sử dụng:

def sum1through (n): 
    if n == 0 return 0 
    return n + sum1through (n-1) 

vì làm điều đó cho n trong số hai mươi triệu sẽ không tốt cho một chồng.

2

Sự cố vẫn tồn tại. Phép đệ quy chiếm rất nhiều không gian ngăn xếp, như mỗi lần một phương thức gọi chính nó, một con trỏ tới nó và các biến cục bộ của nó được tạo lại. Số lượng các cuộc gọi hàm được thực hiện trong quá trình đệ quy làm cho việc sử dụng bộ nhớ O (n); so với O (1) của hàm không đệ quy như vòng lặp.

+2

... trừ khi bạn đang sử dụng đệ quy đuôi và ngôn ngữ/trình biên dịch/nền tảng hiểu được. – SoftMemes

+0

@Được rồi, vâng, nhưng đệ quy đuôi là một cách khác để viết một vòng lặp trong quan điểm khiêm tốn của tôi. –

+0

Điều này cũng phụ thuộc vào việc sử dụng bộ nhớ thực tế và độ sâu đệ quy. Đối với độ sâu đệ quy được giới hạn đã biết, điều này thường không phải là vấn đề vì trong trường hợp đó không gian ngăn xếp không có chi phí. –

0

Mọi người nói nhiều điều ngớ ngẩn về hiệu suất.

  1. Nếu bạn cần đệ quy, muốn làm sâu-đầu tiên cây đi bộ, sau đó bạn cần nó, vì vậy sử dụng nó.

  2. Trước khi lo lắng về hiệu suất của mọi thứ, hãy tìm hiểu xem bạn có vấn đề gì không và ở đâu.
    Các vấn đề về hiệu suất giống như con người và những kẻ lừa đảo - họ chuyên về nơi bạn mong đợi nhất, vì vậy nếu bạn lo lắng về điều gì đó cụ thể như đệ quy, bạn gần như được đảm bảo lo lắng về điều sai trái.

Theo tôi, cách tốt nhất để tìm các vấn đề hiệu suất là do chồng lấy mẫu đúng thời hạn tường đồng hồ, và examining the samples to see what the program is doing, không chỉ bằng cách đo lường và tự hỏi ý nghĩa của chúng.

Điều đó nói rằng, nếu bạn tìm thấy 10% hoặc nhiều hơn thời gian đi vào một cuộc gọi đệ quy, và không có gì khác xảy ra bên trong thói quen đệ quy, và bạn có thể lặp nó, sau đó looping nó có lẽ sẽ giúp đỡ.

Các vấn đề liên quan