2012-05-06 24 views
6

Gần đây, tôi đã chạy vào một mã tối ưu hóa không mong muốn và muốn kiểm tra xem việc diễn giải của tôi về những gì tôi đã quan sát là chính xác hay không. Sau đây là ví dụ đơn giản hóa nhiều về tình huống:F # tổng quát và hiệu suất tự động

let demo = 
    let swap fst snd i = 
     if i = fst then snd else 
     if i = snd then fst else 
     i 
    [ for i in 1 .. 10000 -> swap 1 i i ] 

let demo2 = 
    let swap (fst: int) snd i = 
     if i = fst then snd else 
     if i = snd then fst else 
     i 
    [ for i in 1 .. 10000 -> swap 1 i i ] 

Sự khác biệt duy nhất giữa 2 khối mã là trong trường hợp thứ hai, tôi tuyên bố rõ ràng các đối số hoán đổi làm số nguyên. Tuy nhiên, khi tôi chạy 2 đoạn mã trong fsi với # time, tôi nhận được:

Trường hợp 1 Thực: 00: 00: 00.011, CPU: 00: 00: 00.000, GC gen0: 0, gen1: 0, gen2: 0
Trường hợp 2 Thực: 00: 00: 00.004, CPU: 00: 00: 00.015, GC gen0: 0, gen1: 0, gen2: 0

tức là đoạn thứ 2 chạy nhanh hơn 3 lần so với trước. Sự khác biệt hiệu suất tuyệt đối ở đây rõ ràng không phải là một vấn đề, nhưng nếu tôi sử dụng chức năng hoán đổi rất nhiều, nó sẽ chồng chất lên.

Giả định của tôi là lý do cho lần truy cập hiệu suất là trong trường hợp đầu tiên, hoán đổi là chung và "yêu cầu bình đẳng" và kiểm tra xem int có hỗ trợ nó hay không, trong khi trường hợp thứ hai không phải kiểm tra bất kỳ thứ gì. Đây có phải là lý do điều này đang xảy ra hay tôi đang thiếu thứ gì đó khác? Và nói chung hơn, tôi có nên xem xét việc tổng quát hóa tự động một thanh kiếm hai lưỡi, đó là một tính năng tuyệt vời có thể có tác động không mong muốn đến hiệu suất không?

Trả lời

10

Tôi nghĩ đây thường là trường hợp tương tự như trong câu hỏi Why is this F# code so slow. Trong câu hỏi đó, vấn đề hiệu suất là do ràng buộc yêu cầu comparison và trong trường hợp của bạn, nguyên nhân là do ràng buộc equality.

Trong cả hai trường hợp, mã chung được biên dịch phải sử dụng giao diện (và boxing), trong khi mã được biên dịch chuyên biệt có thể trực tiếp sử dụng hướng dẫn IL để so sánh hoặc bình đẳng số nguyên hoặc số dấu phẩy động.

Hai cách để tránh các vấn đề hiệu suất là:

  • Chuyên mã để sử dụng int hay float như bạn đã làm
  • Đánh dấu chức năng như inline để trình biên dịch chuyên nó tự động

Đối với các chức năng nhỏ hơn, cách tiếp cận thứ hai là tốt hơn, bởi vì nó không tạo ra quá nhiều mã và bạn vẫn có thể viết các hàm theo cách tổng quát. Nếu bạn sử dụng hàm chỉ cho kiểu đơn (theo thiết kế), thì việc sử dụng cách tiếp cận đầu tiên có lẽ là thích hợp.

+0

Cảm ơn các con trỏ đến các câu hỏi khác, thực sự rất giống nhau. Vì vậy, nếu tôi hiểu chính xác, đánh dấu một chức năng như nội tuyến nói "trong khi chức năng này là chung chung, tạo ra một phiên bản cụ thể dựa trên các loại được sử dụng mà nó được gọi là"? Có lý do nào để không đánh dấu mọi chức năng kiểu toán học là nội tuyến không? – Mathias

+0

Hoặc, được nêu khác đi, bạn có thể xây dựng một chút về "nó không tạo ra quá nhiều mã" không, tôi không hiểu lắm. – Mathias

+1

@Mathias Từ khoá 'inline' có nghĩa là trình biên dịch sẽ thay thế cuộc gọi đến hàm bằng cách thực thi nó. Điều này có nghĩa là mã của hàm sẽ được lặp lại một lần cho mỗi cuộc gọi. Đối với các hàm thực sự dài, điều này có thể làm cho assembly trở nên lớn hơn (hoặc tạo ra các phương thức dài hơn mất nhiều thời gian để JIT). Đó là lý do tại sao tôi đề nghị sử dụng nó chỉ cho các hàm ngắn hơn - các hàm trong ví dụ của bạn trông khá ngắn đối với tôi. –

3

Lý do cho sự khác biệt là trình biên dịch được tạo ra các cuộc gọi đến

IL_0002: call bool class [FSharp.Core]Microsoft.FSharp.Core.LanguagePrimitives/HashCompare::GenericEqualityIntrinsic<!!0> (!!0, !!0) 

trong phiên bản chung chung, trong khi phiên bản int chỉ có thể so sánh trực tiếp.

Nếu bạn sử dụng inline tôi nghi ngờ rằng vấn đề này sẽ biến mất khi trình biên dịch hiện nay có thêm thông tin loại

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