Mengapa kode F # ini sangat lambat?


127

Implementasi Levenshtein dalam C # dan F #. Versi C # adalah 10 kali lebih cepat untuk dua string sekitar 1500 karakter. C #: 69 ms, F # 867 ms. Mengapa? Sejauh yang saya tahu, mereka melakukan hal yang persis sama? Tidak masalah apakah itu rilis atau versi Debug.

EDIT: Jika ada orang yang datang ke sini mencari implementasi Edit Jarak, itu rusak. Kode kerjanya ada di sini .

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
Apa perbedaan kinerja menggunakan inline?
gradbot

Jawaban:


202

Masalahnya adalah bahwa min3fungsi dikompilasi sebagai fungsi generik yang menggunakan perbandingan generik (saya pikir ini hanya menggunakan IComparable, tapi sebenarnya lebih rumit - itu akan menggunakan perbandingan struktural untuk tipe F # dan itu logika yang cukup kompleks).

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

Dalam versi C #, fungsi ini tidak generik (hanya membutuhkan waktu int). Anda dapat meningkatkan versi F # dengan menambahkan anotasi jenis (untuk mendapatkan hal yang sama seperti di C #):

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

... atau dengan menjadikan min3sebagai inline(dalam hal ini, itu akan dikhususkan untuk intketika digunakan):

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

Untuk string acak dengan strpanjang 300, saya mendapatkan angka-angka berikut:

> 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
Mengapa F # tidak mengkompilasi min3 sebagai fungsi yang mengambil int? Sudah tahu cukup ketik informasi pada waktu kompilasi untuk melakukan ini. Ini adalah cara kerjanya jika min3 adalah fungsi template C ++, jadi saya agak bingung mengapa F # tidak melakukan ini.
sashang

42
F # menyimpulkannya sebagai generik mungkin, misalnya "untuk semua tipe X yang mendukung perbandingan". inlineberfungsi seperti templat C ++, yang berspesialisasi pada intberdasarkan situs panggilan.
Brian

13
Templat C ++ pada dasarnya berperilaku sebagai F # inline. Alasan mengapa perilaku default berbeda adalah karena dibangun di atas. Net generik yang ditangani oleh runtime (dan, bisa dibilang, tidak terlalu bagus untuk menulis kode numerik generik). Namun, menggunakan perilaku C ++ di F # akan menyebabkan kode mengasapi, karena F # lebih banyak menggunakan obat generik.
Tomas Petricek

4
Semantik templat C ++ dapat menyebabkan kode mengasapi bahkan di C ++, dan kurangnya cara yang nyaman untuk beralih menggunakan mekanisme run-time untuk menghindari hal itu kadang-kadang merepotkan. Namun, takut kode mengasapi biasanya tidak rasional - umumnya, C ++ templat bekerja dengan baik.
Steve314

@ Steve314: Biasanya juga mudah dihindari dengan refactoring semua kode yang tidak menggunakan tipe dependen, sehingga kode tidak diduplikasi untuk instantiations yang berbeda.
ildjarn
Dengan menggunakan situs kami, Anda mengakui telah membaca dan memahami Kebijakan Cookie dan Kebijakan Privasi kami.
Licensed under cc by-sa 3.0 with attribution required.