ทำไมรหัส F # ช้าจัง


127

การใช้ Levenshtein ใน C # และ F # เวอร์ชัน C # เร็วกว่า 10 เท่าสำหรับสองสตริงประมาณ 1,500 ตัวอักษร C #: 69 ms, F # 867 มิลลิวินาที ทำไม? เท่าที่ฉันบอกได้พวกเขาทำสิ่งเดียวกันหรือไม่? ไม่สำคัญว่าจะเป็นรุ่นหรือรุ่นแก้ไขข้อบกพร่อง

แก้ไข: หากใครมาที่นี่เพื่อค้นหาการใช้งานแก้ไขระยะทางโดยเฉพาะถือว่าเสีย รหัสทำงานที่นี่

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
ประสิทธิภาพแตกต่างกันอย่างไรเมื่อใช้อินไลน์?
gradbot

คำตอบ:


202

ปัญหาคือmin3ฟังก์ชันถูกคอมไพล์เป็นฟังก์ชันทั่วไปที่ใช้การเปรียบเทียบทั่วไป (ฉันคิดว่าสิ่งนี้ใช้เพียงIComparableแต่จริงๆแล้วซับซ้อนกว่า - จะใช้การเปรียบเทียบโครงสร้างสำหรับประเภท F # และเป็นตรรกะที่ค่อนข้างซับซ้อน)

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

ในเวอร์ชัน C # ฟังก์ชันไม่ได้เป็นแบบทั่วไป (ใช้เวลาเพียงint) คุณสามารถปรับปรุงเวอร์ชัน F # ได้โดยการเพิ่มคำอธิบายประกอบประเภท (เพื่อให้ได้สิ่งเดียวกับใน C #):

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

... หรือโดยการทำให้min3เป็นinline(ซึ่งในกรณีนี้ก็จะมีความเชี่ยวชาญในการintเมื่อใช้):

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

สำหรับสตริงสุ่มที่มีstrความยาว 300 ฉันจะได้ตัวเลขต่อไปนี้:

> 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
เหตุใด F # จึงไม่รวบรวม min3 เป็นฟังก์ชันที่ใช้ int มันรู้ข้อมูลประเภทเพียงพอแล้วในเวลาคอมไพล์เพื่อทำสิ่งนี้ นี่คือวิธีการทำงานถ้า min3 เป็นฟังก์ชันเทมเพลต C ++ ดังนั้นฉันจึงงงเล็กน้อยว่าทำไม F # ไม่ทำเช่นนี้
ซาจัง

42
F # อนุมานได้ว่าเป็นข้อมูลทั่วไปมากที่สุดเช่น "สำหรับ X ทุกประเภทที่รองรับการเปรียบเทียบ" inlineทำงานเหมือนเทมเพลต C ++ ซึ่งจะเชี่ยวชาญintตามไซต์การโทร
Brian

13
c ++ แม่ทำตัวเป็นหลักเป็น F # inline's สาเหตุที่พฤติกรรมเริ่มต้นแตกต่างกันเนื่องจากสร้างขึ้นบน. อย่างไรก็ตามการใช้พฤติกรรม C ++ ใน F # จะนำไปสู่การขยายรหัสเนื่องจาก F # ใช้ generics มากกว่ามาก
Tomas Petricek

4
ความหมายของเทมเพลต C ++ สามารถนำไปสู่การขยายตัวของโค้ดได้แม้ใน C ++ และไม่มีวิธีที่สะดวกในการเปลี่ยนไปใช้กลไกรันไทม์เพื่อหลีกเลี่ยงปัญหานี้ในบางครั้ง อย่างไรก็ตามความกลัวในการขยายตัวของโค้ดมักจะไม่มีเหตุผลโดยทั่วไปแล้วเทมเพลต C ++ จะทำงานได้ดี
Steve314

@ Steve314: โดยทั่วไปแล้วมันง่ายที่จะหลีกเลี่ยงโดยการปรับโครงสร้างโค้ดทั้งหมดที่ไม่ได้ใช้ประเภทที่อ้างอิงเพื่อให้โค้ดไม่ซ้ำกันสำหรับอินสแตนซ์ที่แตกต่างกัน
ildjarn
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.