2016-03-03 18 views
11

FWIW Tôi nghĩ rằng các vấn đề chi tiết ở đây chỉ đi xuống trình biên dịch C# thông minh hơn, và tạo ra một máy trạng thái hiệu quả dựa mô hình để xử lý mã async, trong khi trình biên dịch F # tạo ra vô số các đối tượng và các cuộc gọi hàm thường chỉ kém hiệu quả.Hiệu suất của async trong F # vs C# (có cách nào tốt hơn để viết async {...})

Dù sao, nếu tôi có C# chức năng dưới đây:

public async static Task<IReadOnlyList<T>> CSharpAsyncRead<T>(
     SqlCommand cmd, 
     Func<SqlDataReader, T> createDatum) 
{ 
    var result = new List<T>(); 
    var reader = await cmd.ExecuteReaderAsync(); 

    while (await reader.ReadAsync()) 
    { 
     var datum = createDatum(reader); 
     result.Add(datum); 
    } 

    return result.AsReadOnly(); 
} 

Và sau đó chuyển đổi này để F # như sau:

let fsharpAsyncRead1 (cmd:SqlCommand) createDatum = async { 
    let! reader = 
     Async.AwaitTask (cmd.ExecuteReaderAsync()) 

    let rec readRows (results:ResizeArray<_>) = async { 
     let! readAsyncResult = Async.AwaitTask (reader.ReadAsync()) 
     if readAsyncResult then 
      let datum = createDatum reader 
      results.Add datum 
      return! readRows results 
     else 
      return results.AsReadOnly() :> IReadOnlyList<_> 
    } 

    return! readRows (ResizeArray()) 
} 

Sau đó, tôi thấy rằng việc thực hiện cáC# mã f là chậm hơn đáng kể và nhiều CPU đói hơn phiên bản C#. Tôi đã tự hỏi nếu có tốt hơn là để soạn nó. Tôi đã cố gắng loại bỏ các hàm đệ quy như sau (trong đó xuất hiện một chút xấu xí với không gian và không thể thay đổi let s!):

let fsharpAsyncRead2 (cmd:SqlCommand) createDatum = async { 
    let result = ResizeArray() 

    let! reader = 
     Async.AwaitTask (cmd.ExecuteReaderAsync()) 

    let! moreData = Async.AwaitTask (reader.ReadAsync()) 
    let mutable isMoreData = moreData 
    while isMoreData do 
     let datum = createDatum reader 

     result.Add datum 

     let! moreData = Async.AwaitTask (reader.ReadAsync()) 
     isMoreData <- moreData 

    return result.AsReadOnly() :> IReadOnlyList<_> 
} 

Nhưng việc thực hiện là cơ bản giống nhau.

Như một ví dụ về việc thực hiện, khi tôi đã tải một thanh dữ liệu thị trường như:

type OHLC = { 
    Time : DateTime 
    Open : float 
    High : float 
    Low : float 
    Close : float 
} 

Trên máy tính của tôi, F phiên bản # async mất ~ gấp đôi thời gian, và tiêu thụ ~ gấp đôi Tài nguyên CPU cho toàn bộ thời gian nó chạy - do đó lấy khoảng 4x tài nguyên nhiều (tức là nội bộ nó phải được kéo lên nhiều chủ đề hơn?).

(Có thể nó hơi mơ hồ khi đọc một cấu trúc tầm thường như vậy? Tôi thực sự chỉ chọc máy để xem nó hoạt động như thế nào. So với phiên bản không đồng bộ (nghĩa là chỉ đọc thẳng) C# một hoàn thành trong cùng một thời gian, nhưng tiêu thụ> gấp đôi CPU, tức là đọc thẳng() tiêu thụ < 1/8 của tài nguyên f #)

Vì vậy, câu hỏi của tôi là, khi tôi thực hiện F # async "đúng" cách (đây là lần đầu tiên tôi sử dụng)?

(... và nếu tôi, sau đó tôi chỉ cần đi và sửa đổi các trình biên dịch để thêm một máy nhà nước dựa trên thực hiện cho Asyncs biên soạn ... làm thế nào cứng có thể được :-))

+0

Bạn có chắc chắn cả hai đều chạy với cùng một cài đặt liên quan đến bitness, tối ưu hóa và như vậy? –

+3

Bạn đã xem 'hopac' (https://github.com/Hopac/Hopac) chưa? nó có chi phí CPU thấp hơn đáng kể so với 'Async' (và IIRC thấp hơn' Task'). FYI; Cá nhân tôi sẽ không xem xét sử dụng 'Async' hoặc 'TPL' và như vậy khi tôi cần song song. Nếu tôi bị ràng buộc CPU, tôi rất có thể không thể làm hỏng các trừu tượng. – FuleSnabel

+0

@FuleSnabel BTW, tôi đã xem xét Hopac vì nó thực sự nhanh hơn 'async {}'. Nhưng interop với C# (thiếu) là một dealbreaker. 'task {}' biểu thức tính toán đưa ra một cách để sử dụng TPL (có lẽ là phần được tối ưu hóa nhất của .NET) theo cách thành ngữ F #. –

Trả lời

11

F # 's Async và ranh giới TPL (Async.AwaitTask/Async.StartAsTask) là điều chậm nhất. Nhưng nói chung, F # Async là chậm hơn chính nó và nên được sử dụng cho IO bị ràng buộc không CPU nhiệm vụ ràng buộc. Bạn có thể tìm thấy repo này thú vị: https://github.com/buybackoff/FSharpAsyncVsTPL

Về cơ bản, tôi đã chấm điểm cả hai và cũng là biểu thức tính toán trình xây dựng nhiệm vụ, ban đầu là từ dự án FSharpx. Trình tạo tác vụ nhanh hơn nhiều khi được sử dụng cùng với TPL. Tôi sử dụng phương pháp này trong thư viện Spread của tôi - được viết bằng F # nhưng tận dụng TPL. Trên this line là liên kết tối ưu hóa cao của biểu thức tính toán có hiệu quả giống như C# 's async/chờ đợi đằng sau hậu trường. Tôi đã chuẩn hóa mọi cách sử dụng biểu thức tính toán task{} trong thư viện và nó rất nhanh (không được sử dụng cho/trong khi biểu thức tính toán, nhưng đệ quy). Ngoài ra, nó làm cho mã tương thích với C#, trong khi không đồng bộ của F # không thể được tiêu thụ từ C#.

+0

Tôi không thể thấy bất kỳ hiệu suất nào khác với GetAwaiter(). Dưới đây là một ý chính của các bài kiểm tra của tôi - https://gist.github.com/manofstick/e0f1cbf14febf3666c06 - Tôi đã thử 32/64-bit, có và không có gcServer. (và tôi đã kiểm tra rằng đường dẫn nhanh m.IsCompleted không bị ảnh hưởng mọi lúc) –

+0

... và thực sự nếu bạn sửa đổi gist đó để tăng số lượng tác vụ được tạo ra khá lớn (triệu), hãy cắt giảm tải công việc để họ sẽ được khá nhanh chóng, tạo ra các nhiệm vụ bị đình chỉ, và sau đó chỉ cần Start() chúng ngay trước khi các ràng buộc, sau đó bạn thấy rằng ContinueWith tốt hơn phiên bản GetAwaiter. –

+0

@PaulWestcott Khi tôi kiểm tra sự khác biệt không lớn và nằm trong lề lỗi. Sử dụng AsyncTaskMethodBuilder thay vì TaskCompletionSource thực sự là chậm hơn một chút (vài phần trăm), nhưng tôi quyết định không phân bổ các đối tượng bổ sung nếu có thể. –

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