2012-02-07 35 views
6

Tôi đang tìm cách tạo mã hóa thuật toán thích hợp bằng .NET với tất cả các lợi ích của ngôn ngữ hiện đại (ví dụ: tôi thích kiểm tra kiểu mạnh, quá tải toán tử, lambdas, thuật toán chung). Thông thường tôi viết các thuật toán của tôi (chủ yếu là prozessing hình ảnh) trong C++. Vì F # như một ngôn ngữ có vẻ khá thú vị, tôi đã chơi một chút, nhưng nó có vẻ rất chậm. Là một thử nghiệm đơn giản nhất mà tôi chỉ làm một số thao tác mảng -> độ sáng tăng của một hình ảnh:Tối ưu hóa mã F # hay nó thực sự chậm?

let r1 = rgbPixels |> Array.map (fun x -> x + byte(10)) 

Nó có vẻ là một yếu tố của chậm hơn so với C++ thực hiện so với ít nhất 8 lần - thậm chí tồi tệ hơn cho các thuật toán phức tạp hơn ví dụ 2D convolution. Có cách nào nhanh hơn hay tôi bỏ sót bất kỳ cài đặt trình biên dịch cụ thể nào (vâng, xây dựng bản phát hành có tối ưu hóa trên ...)? Tôi sẵn sàng trả tiền cho abstracion đẹp và cao, nhưng một chi phí như vậy không phải là tốt đẹp (tôi sẽ cần phải song song trên 8 lõi để bù đắp :)) - ít nhất nó phá hủy động lực để tìm hiểu thêm ... tùy chọn sẽ được để lại các thuật toán nặng hơn của tôi trong C + + và giao diện với C++ manged, nhưng điều này là không tốt đẹp, như duy trì wrapper quản lý sẽ được khá một gánh nặng.

+1

Có thể nó sẽ được gạch chân, nhưng bạn có thể bắt đầu bằng cách thay thế cuộc gọi thành 'byte' bằng' 10uy'. – Daniel

+0

Bạn có thể đăng bài thực thi C++ của mình lên [ideone] (http://ideone.com/) để chúng tôi có một số thứ để làm việc không? – Daniel

+0

[Những kết quả này] (http://ideone.com/Z4Gvj) có thể so sánh với bạn không? – Daniel

Trả lời

6

Nếu bạn lo lắng về hiệu suất, một trong những điều quan trọng cần nhớ là F # theo mặc định không làm thay đổi bất cứ điều gì. Điều này đòi hỏi phải sao chép trong nhiều triển khai ngây thơ của các thuật toán, chẳng hạn như một trong những bạn mô tả.

EDIT: Tôi không biết tại sao, nhưng các thử nghiệm đơn giản của mã sau cung cấp kết quả kém hơn Array.map. Hãy chắc chắn cấu hình bất kỳ thuật toán nào bạn thử khi thực hiện các loại tối ưu hóa này. Tuy nhiên tôi có kết quả rất giống nhau giữa formap.

Array.map tạo mảng mới cho kết quả của thao tác, thay vào đó bạn muốn Array.iteri.

rgbPixels |> Array.iteri (fun i x -> rgbPixels.[i] <- x + 10uy) 

Lưu ý rằng điều này có thể được gói gọn trong mô-đun riêng của bạn như sau

module ArrayM = 
    let map f a = a |> Array.iteri (fun i x -> a.[i] <- f x) 

Thật không may đây là một điều ác cần thiết kể từ khi một trong những người thuê nhà chính của lập trình chức năng là để dính vào đối tượng bất biến càng nhiều như thuật toán của bạn sẽ cho phép, và sau đó sau khi hoàn thành, thay đổi đột biến, nơi hiệu suất là rất quan trọng. Nếu bạn biết hiệu suất của bạn là rất quan trọng từ khi bắt đầu, bạn sẽ cần phải bắt đầu với những người trợ giúp này.

Cũng lưu ý rằng có thể có một thư viện ở đó cung cấp chức năng này, tôi chỉ không biết điều đó.

+1

Hoặc thậm chí nhanh hơn: ' cho i = 0 đến rgbPixels.Length-1 làm rgbPixels. [i] <- rgbPixels. [i] + 10uy'. – Daniel

+0

@Daniel: Tôi đã cố gắng giữ gần mã gốc nhất có thể. Tôi sẽ giả định rằng hàm này được JIT gạch chân. – Guvante

+0

@Guvante: Tôi đã thử nghiệm với FSI: vòng lặp mất ít hơn một nửa thời gian ... có thể do không có giới hạn kiểm tra, như kvb đã đề cập. – Daniel

5

Tôi nghĩ rằng nó an toàn để nói rằng thành ngữ F # thường sẽ thất bại để phù hợp với việc thực hiện C tối ưu hóa ++ cho thao tác mảng, đối với một vài lý do:

  1. Mảng truy cập được kiểm tra chống lại các giới hạn của mảng trong .NET để đảm bảo an toàn bộ nhớ. Trình biên dịch JIT của CLR có thể vượt qua giới hạn kiểm tra đối với một số mã khuôn mẫu, nhưng điều này thường sẽ yêu cầu bạn sử dụng vòng lặp for với các ràng buộc rõ ràng hơn là cấu trúc F # thành ngữ.
  2. Thường có một lượng nhỏ chi phí để sử dụng các phép trừu tượng như lambdas (ví dụ: fun i -> ...). Trong các vòng eo hẹp, chi phí nhỏ này có thể kết thúc đáng kể so với công việc đang được thực hiện trong thân vòng lặp.
  3. Theo như tôi biết, trình biên dịch CLR JIT không tận dụng các hướng dẫn SSE đến cùng mức độ mà trình biên dịch C++ có thể.

Ở phía bên kia của sổ cái,

  1. Bạn sẽ không bao giờ có một lỗi tràn bộ đệm trong F # mã.
  2. Mã của bạn sẽ dễ dàng hơn để giải thích. Như một hệ quả, đối với một mức độ phức tạp mã nhất định, bạn thường có thể thực hiện một thuật toán phức tạp hơn trong F # hơn bạn có thể trong C++.
  3. Nếu cần, bạn có thể viết mã unidiomatic chạy ở tốc độ C++ và cũng có các tiện ích để tương tác với mã C++ không an toàn nếu bạn thấy cần phải viết C++ để đáp ứng yêu cầu hiệu suất của mình.
+0

1. Ngoại trừ việc bạn có vòng lặp này trong Array.map 2. Có thể tránh được bằng cách nội tuyến 3. Mặt khác song song dễ dàng hơn nhiều trong các ngôn ngữ chức năng (như thêm một hàm gọi bổ sung) và các hoạt động vector có thể được thêm vào một cách kỹ thuật theo cách tương tự. –

+0

Tôi hoàn toàn đồng ý với tất cả các điểm trên và tôi là Absolutelly sẵn sàng hy sinh một số hiệu suất (đó là động cơ của tôi để bắt đầu chơi với F #). Tôi đã làm điều gì đó sai). Đến song song: nếu tôi bắt đầu với một hệ số 5 Tôi sẽ giữ 5 lõi bận rộn và hơn có lẽ vẫn chỉ là nơi tôi đã ở trước với một lõi trong C++ ... – TheBigW

+0

@Maciej - Đối với mã cần ghi vào mảng , không có hàm bậc cao nào trong mô-đun 'Array' tương đương với vòng lặp for (nghĩa là, trong' arr |> Array.iteri (vui iv -> arr. [i] <- v + 1) 'kiểm tra để đọc sẽ được elided, nhưng vẫn sẽ có một kiểm tra để viết tại mỗi lần lặp). – kvb