2012-06-09 24 views
13

Reading Bắt đầu từ F # - Robert Pickering tôi tập trung vào những đoạn như sau:CLR vs OCaml ngoại lệ phí

Các lập trình viên đến từ một nền OCaml nên cẩn thận khi sử dụng ngoại lệ trong F #. Do kiến ​​trúc của CLR, ném một ngoại lệ khá đắt tiền - khá đắt hơn so với OCaml. Nếu bạn ném rất nhiều ngoại lệ, hãy cấu hình mã của bạn cẩn thận để quyết định liệu chi phí hiệu suất có đáng giá hay không. Nếu chi phí quá cao, hãy sửa đổi mã một cách thích hợp.

Tại sao, vì CLR, việc ném ngoại lệ sẽ đắt hơn nếu F# hơn trong OCaml? Và cách tốt nhất để sửa đổi mã một cách thích hợp trong trường hợp này là gì?

+1

Có một [thảo luận về trọng lượng nhẹ của ngoại lệ OCaml] (http://stackoverflow.com/questions/8564025/ocaml-internals-exceptions) –

+0

@JeffreyScofield cảm ơn, tuy nhiên tôi quan tâm nhiều hơn đến các khía cạnh liên quan đến CLR . – gliderkite

Trả lời

7

Reed đã giải thích tại sao ngoại lệ .NET hoạt động khác với ngoại lệ OCaml. Nói chung, ngoại lệ .NET chỉ phù hợp với trường hợp đặc biệt và được thiết kế cho mục đích đó. OCaml có mô hình nhẹ hơn và do đó chúng được sử dụng để thực hiện một số mẫu điều khiển luồng.

Để đưa ra một ví dụ cụ thể, trong OCaml bạn có thể sử dụng ngoại lệ để thực hiện phá vỡ vòng lặp. Ví dụ: giả sử bạn có hàm test kiểm tra xem một số có phải là số mà chúng tôi muốn hay không. Sau đây lặp qua các số từ 1 đến 100 và trả lại số kết hợp đầu tiên:

// Simple exception used to return the result 
exception Returned of int 

try 
    // Iterate over numbers and throw if we find matching number 
    for n in 0 .. 100 do 
    printfn "Testing: %d" n 
    if test n then raise (Returned n) 
    -1     // Return -1 if not found 
with Returned r -> r // Return the result here 

Để thực hiện điều này mà không có ngoại lệ, bạn có hai tùy chọn. Bạn có thể viết một hàm đệ quy có hành vi tương tự - nếu bạn gọi find 0 (và nó được biên soạn để về cơ bản mã IL tương tự như sử dụng return n bên for vòng lặp trong C#):

let rec find n = 
    printfn "Testing: %d" n 
    if n > 100 then -1 // Return -1 if not found 
    elif test n then n // Return the first found result 
    else find (n + 1) // Continue iterating 

Mã hóa sử dụng các hàm đệ quy có thể có một chút thời gian, nhưng bạn cũng có thể sử dụng các hàm chuẩn được cung cấp bởi thư viện F #. Đây thường là cách tốt nhất để viết lại mã có thể sử dụng các ngoại lệ OCaml cho luồng điều khiển. Trong trường hợp này, bạn có thể viết:

// Find the first value matching the 'test' predicate 
let res = seq { 0 .. 100 } |> Seq.tryFind test 
// This returns an option type which is 'None' if the value 
// was not found and 'Some(x)' if the value was found. 
// You can use pattern matching to return '-1' in the default case: 
match res with 
| None -> -1 
| Some n -> n 

Nếu bạn không quen thuộc với các loại tùy chọn, hãy xem một số tài liệu giới thiệu. F# wikibook has a good tutorialMSDN documentation has useful examples.

Sử dụng một chức năng thích hợp từ mô-đun Seq thường làm cho mã ngắn hơn rất nhiều, do đó, nó là thích hợp hơn. Nó có thể hơi kém hiệu quả hơn so với sử dụng đệ quy trực tiếp, nhưng trong hầu hết các trường hợp, bạn không cần phải lo lắng về điều đó.

EDIT: Tôi tò mò về hiệu suất thực tế. Phiên bản sử dụng Seq.tryFind sẽ hiệu quả hơn nếu đầu vào là chuỗi được tạo lazily seq { 1 .. 100 } thay vì danh sách [ 1 .. 100 ] (do chi phí phân bổ danh sách). Với những thay đổi này, và test hàm trả về yếu tố thứ 25, thời gian cần thiết để chạy mã 100000 lần trên máy tính của tôi là:

exceptions 2.400sec 
recursion 0.013sec 
Seq.tryFind 0.240sec 

Đây là mẫu cực kỳ tầm thường, vì vậy tôi nghĩ rằng giải pháp sử dụng Seq sẽ không thường chạy Chậm hơn 10 lần so với mã tương đương được viết bằng cách sử dụng đệ quy. Sự chậm lại có lẽ là do việc phân bổ các cấu trúc dữ liệu bổ sung (đối tượng biểu diễn trình tự, đóng cửa, ...) và cũng do bổ sung thêm (mã cần nhiều cuộc gọi phương thức ảo, thay vì chỉ thao tác số và nhảy đơn giản). Tuy nhiên, các ngoại lệ thậm chí còn tốn kém hơn và không làm cho mã ngắn hơn hoặc dễ đọc hơn theo bất kỳ cách nào ...

+0

+1 Tôi chắc chắn phải đọc sách của bạn. Chỉ một điều, tại sao sử dụng một hàm từ Seq có thể kém hiệu quả hơn việc sử dụng đệ quy trực tiếp? – gliderkite

+0

@gliderkite Tôi chạy thử nghiệm nguyên thủy và thêm một số thông tin hiệu suất. –

12

Ngoại lệ trong CLR rất phong phú và cung cấp nhiều chi tiết. Rico Mariani đã đăng một bài đăng cũ (nhưng vẫn có liên quan) trên the cost of exceptions trong CLR, trong đó nêu chi tiết một số điều này.

Như vậy, việc ném ngoại lệ trong CLR có chi phí tương đối cao hơn so với một số môi trường khác, bao gồm OCaml.

Và cách tốt nhất để sửa đổi mã phù hợp trong trường hợp này là gì?

Nếu bạn mong đợi trừ được nâng lên trong những hoàn cảnh bình thường, không đặc biệt, bạn có thể suy nghĩ lại thuật toán và API của bạn trong một cách mà tránh được ngoại trừ hoàn toàn. Hãy thử cung cấp một API thay thế, nơi bạn có thể kiểm tra tình huống trước khi gây ra ngoại lệ được nêu ra, ví dụ.

+1

Loại 'tùy chọn' cực kỳ có liên quan ở đây. – ildjarn

+0

@ildjarn Vâng, chính xác. Thông thường, bạn có thể sử dụng 'None' thay vì sử dụng ngoại lệ. –

+0

Tôi xin lỗi, tôi không biết loại 'None' và' option'. Bạn có thể xây dựng các khía cạnh liên quan này không? – gliderkite

8

Tại sao, vì CLR, ném ngoại lệ là đắt hơn nếu F # hơn trong OCaml?

OCaml được tối ưu hóa rất nhiều cho việc sử dụng ngoại lệ làm luồng kiểm soát. Ngược lại, ngoại lệ trong .NET chưa được tối ưu hóa.

Lưu ý rằng sự khác biệt hiệu suất là lớn. Trường hợp ngoại lệ là khoảng 600 × nhanh hơn trong OCaml hơn là trong F #. Ngay cả C++ là khoảng 6 × chậm hơn so với OCaml trong lĩnh vực này, theo tiêu chuẩn của tôi.

Mặc dù ngoại lệ .NET được cho là cung cấp nhiều hơn (OCaml cung cấp dấu vết ngăn xếp, bạn muốn gì hơn?) Tôi không thể nghĩ ra bất kỳ lý do nào khiến chúng chậm như chúng.

Và cách tốt nhất để sửa đổi mã phù hợp trong trường hợp này là gì?

Trong F #, bạn nên viết các hàm "tổng". Điều này có nghĩa là hàm của bạn sẽ trả về giá trị của một loại liên kết cho biết loại kết quả, ví dụ: bình thường hoặc đặc biệt.

Đặc biệt, các cuộc gọi đến find nên được thay thế bằng các cuộc gọi đến tryFind mà trả về giá trị của các loại option đó là một trong hai Some giá trị hoặc None nếu yếu tố then chốt đã không có mặt trong bộ sưu tập.

+0

'Tôi không thể nghĩ ra bất kỳ lý do nào tại sao họ nên chậm như họ. 'Đây chính xác là những gì tôi đang hỏi. Liên kết [_The Cost of Exceptions_] (http://blogs.msdn.com/b/ricom/archive/2003/12/19/44697.aspx) được đăng bởi Reed Copsey giải thích rất đơn giản lý do tại sao sự chậm chạp này. – gliderkite

+0

@gliderkite Bài viết nói rằng .NET chậm và mô tả cách giải quyết (tránh phần này của .NET) nhưng tôi không nghĩ nó mô tả tại sao. Tôi nghĩ câu trả lời đơn giản là Microsoft không bao giờ làm phiền việc tối ưu hóa nó. –

+0

'Tôi không nghĩ nó mô tả lý do tại sao 'không may nó là như vậy, nhưng tôi không nghĩ rằng Microsoft không bao giờ làm phiền tối ưu hóa nó, những gì đạt được sẽ thu được theo cách này? – gliderkite

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