Tại sao mã F # này quá chậm?


127

Việc triển khai Levenshtein trong C # và F #. Phiên bản C # nhanh hơn 10 lần cho hai chuỗi khoảng 1500 ký tự. C #: 69 ms, F # 867 ms. Tại sao? Theo như tôi có thể nói, họ làm điều tương tự chính xác? Không thành vấn đề nếu đó là Bản phát hành hoặc bản dựng Gỡ lỗi.

EDIT: Nếu bất cứ ai đến đây tìm kiếm cụ thể cho việc thực hiện Chỉnh sửa khoảng cách, nó sẽ bị hỏng. Mã làm việc là đây .

C # :

private static int min3(int a, int b, int c)
{
   return Math.Min(Math.Min(a, b), c);
}

public static int EditDistance(string m, string n)
{
   var d1 = new int[n.Length];
   for (int x = 0; x < d1.Length; x++) d1[x] = x;
   var d0 = new int[n.Length];
   for(int i = 1; i < m.Length; i++)
   {
      d0[0] = i;
      var ui = m[i];
      for (int j = 1; j < n.Length; j++ )
      {
         d0[j] = 1 + min3(d1[j], d0[j - 1], d1[j - 1] + (ui == n[j] ? -1 : 0));
      }
      Array.Copy(d0, d1, d1.Length);
   }
   return d0[n.Length - 1];
}

F # :

let min3(a, b, c) = min a (min b c)

let levenshtein (m:string) (n:string) =
   let d1 = Array.init n.Length id
   let d0 = Array.create n.Length 0
   for i=1 to m.Length-1 do
      d0.[0] <- i
      let ui = m.[i]
      for j=1 to n.Length-1 do
         d0.[j] <- 1 + min3(d1.[j], d0.[j-1], d1.[j-1] + if ui = n.[j] then -1 else 0)
      Array.blit d0 0 d1 0 n.Length
   d0.[n.Length-1]

7
Hiệu suất khác nhau khi sử dụng nội tuyến là gì?
gradbot

Câu trả lời:


202

Vấn đề là min3hàm được biên dịch thành hàm chung sử dụng so sánh chung (tôi nghĩ rằng nó chỉ sử dụng IComparable, nhưng nó thực sự phức tạp hơn - nó sẽ sử dụng so sánh cấu trúc cho các loại F # và logic khá phức tạp).

> let min3(a, b, c) = min a (min b c);;
val min3 : 'a * 'a * 'a -> 'a when 'a : comparison

Trong phiên bản C #, chức năng này không chung chung (nó chỉ mất int). Bạn có thể cải thiện phiên bản F # bằng cách thêm chú thích loại (để có được điều tương tự như trong C #):

let min3(a:int, b, c) = min a (min b c)

... hoặc bằng cách làm min3như inline(trong trường hợp này, nó sẽ được chuyên để intkhi sử dụng):

let inline min3(a, b, c) = min a (min b c);;

Đối với một chuỗi ngẫu nhiên strcó độ dài 300, tôi nhận được các số sau:

> levenshtein str ("foo" + str);;
Real: 00:00:03.938, CPU: 00:00:03.900, GC gen0: 275, gen1: 1, gen2: 0
val it : int = 3

> levenshtein_inlined str ("foo" + str);;
Real: 00:00:00.068, CPU: 00:00:00.078, GC gen0: 0, gen1: 0, gen2: 0
val it : int = 3

1
Tại sao F # không biên dịch min3 dưới dạng hàm lấy int? Nó đã biết đủ thông tin loại tại thời điểm biên dịch để làm điều này. Đây là cách nó sẽ hoạt động nếu min3 là một hàm mẫu C ++, vì vậy tôi hơi bối rối về lý do tại sao F # không làm điều này.
sashang

42
F # cho rằng nó càng chung chung càng tốt, ví dụ: "cho tất cả các loại X hỗ trợ so sánh". inlinehoạt động giống như một mẫu C ++, chuyên dùng intcho trang web cuộc gọi.
Brian

13
Các mẫu C ++ hoạt động cơ bản như của F # inline. Lý do tại sao hành vi mặc định là khác nhau là vì nó xây dựng trên các tổng quát .Net được xử lý bởi thời gian chạy (và, có thể nói là không tuyệt vời để viết mã số chung). Tuy nhiên, việc sử dụng hành vi C ++ trong F # sẽ dẫn đến sự phình to mã, bởi vì F # sử dụng nhiều tổng quát hơn rất nhiều.
Tomas Petricek

4
Ngữ nghĩa mẫu C ++ có thể dẫn đến sự phình to mã ngay cả trong C ++ và việc thiếu một cách thuận tiện để chuyển sang sử dụng cơ chế thời gian chạy để tránh điều đó đôi khi gặp rắc rối. Tuy nhiên, nỗi sợ mã hóa thường không hợp lý - nói chung, các mẫu C ++ hoạt động tốt.
Steve314

@ Steve314: Nói chung cũng dễ tránh bằng cách tái cấu trúc tất cả các mã không sử dụng loại phụ thuộc, do đó mã không bị trùng lặp cho các lần xuất hiện khác nhau.
ildjarn
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.