ทำไม Haskell (GHC) ถึงเร็วขนาดนี้


247

Haskell (พร้อมGHCคอมไพเลอร์) นั้นเร็วกว่าที่คุณคาดไว้มาก ใช้อย่างถูกต้องสามารถใช้ภาษาใกล้เคียงกับภาษาระดับต่ำได้ (สิ่งที่ชื่นชอบสำหรับ Haskellers ที่ต้องทำคือพยายามและรับภายใน 5% ของ C (หรือแม้กระทั่งเอาชนะ แต่นั่นหมายความว่าคุณกำลังใช้โปรแกรม C ที่ไม่มีประสิทธิภาพเนื่องจาก GHC รวบรวม Haskell ถึง C) คำถามของฉันคือทำไม?

Haskell เป็นการประกาศและขึ้นอยู่กับแคลคูลัสแลมบ์ดา สถาปัตยกรรมเครื่องจักรมีความจำเป็นอย่างชัดเจนโดยขึ้นอยู่กับเครื่องจักรทัวริง ที่จริง Haskell ไม่มีคำสั่งประเมินที่เฉพาะเจาะจง นอกจากนี้แทนที่จะจัดการกับชนิดข้อมูลเครื่องคุณทำประเภทข้อมูลพีชคณิตตลอดเวลา

ที่แปลกประหลาดที่สุดคือฟังก์ชั่นการสั่งซื้อที่สูงขึ้น คุณอาจคิดว่าการสร้างฟังก์ชั่นได้ทันทีและการขว้างมันไปรอบ ๆ จะทำให้โปรแกรมช้าลง แต่การใช้ฟังก์ชันคำสั่งซื้อที่สูงกว่าทำให้ Haskell เร็วขึ้น ดูเหมือนว่าในการเพิ่มประสิทธิภาพรหัส Haskell คุณจะต้องทำให้มันดูสง่างามและเป็นนามธรรมมากกว่าการใช้เครื่องจักร ไม่มีคุณสมบัติขั้นสูงใด ๆ ของ Haskell ที่ดูเหมือนจะส่งผลกระทบต่อประสิทธิภาพการทำงานของมันหากไม่ได้ปรับปรุง

ขออภัยถ้านี่คือเสียงที่ฟังดู แต่นี่คือคำถามของฉัน: ทำไม Haskell (เรียบเรียงกับ GHC) เร็วขนาดนี้โดยพิจารณาจากธรรมชาติที่เป็นนามธรรมและความแตกต่างจากเครื่องจักรทางกายภาพ?

หมายเหตุ: เหตุผลที่ฉันพูดภาษา C และภาษาที่จำเป็นอื่น ๆ นั้นค่อนข้างคล้ายกับทัวริงแมชชีน (แต่ไม่ใช่ในระดับที่ Haskell คล้ายกับแลมบ์ดาแคลคูลัส) นั่นคือในภาษาที่จำเป็นคุณมีจำนวน จำกัด ของรัฐ (หมายเลขบรรทัดอาคา) พร้อมกับเทป (หน่วยความจำ) เช่นว่าสถานะและเทปปัจจุบันกำหนดว่าจะทำอย่างไรกับเทป ดูรายการ Wikipedia, ทัวริงเครื่องเทียบเท่าสำหรับการเปลี่ยนจากทัวริงเครื่องกับคอมพิวเตอร์


27
"เนื่องจาก GHC คอมไพล์ Haskell ถึง C" - มันไม่ GHC มีแบ็กเอนด์หลาย ๆ ตัวเก่าที่สุด (แต่ไม่ใช่ค่าเริ่มต้น) คือตัวสร้าง C มันสร้างโค้ด Cmm สำหรับ IR แต่นั่นไม่ใช่ "คอมไพล์เป็น C" ตามปกติคุณคาดหวัง ( downloads.haskell.org/~ghc/latest/docs/html/users_guide/… )
viraptor

20
ฉันขอแนะนำให้อ่านการใช้งานฟังก์ชั่นการเขียนโปรแกรมภาษาโดย Simon Payton Jones (ผู้ดำเนินการหลักของ GHC) มันจะตอบคำถามของคุณมากมาย
Joe Hillenbrand

94
ทำไม? ทำงานอย่างหนัก 25 ปี
สิงหาคม

31
“ ถึงแม้ว่าอาจมีคำตอบที่เป็นความจริง แต่ก็จะไม่ทำอะไรเลยนอกจากเรียกร้องความคิดเห็น” - นี่คือเหตุผลที่เป็นไปได้ที่เลวร้ายที่สุดในการปิดคำถาม เพราะมันอาจมีคำตอบที่ดี แต่มันก็อาจจะดึงดูดคนที่มีคุณภาพต่ำ yuck! ฉันจึงมีคำตอบที่ดีประวัติศาสตร์ความจริงเกี่ยวกับการวิจัยทางวิชาการและเมื่อมีการพัฒนาบางอย่างเกิดขึ้น แต่ฉันไม่สามารถโพสต์ได้เพราะคนกังวลคำถามนี้อาจดึงดูดคำตอบคุณภาพต่ำ อีกครั้ง yuck
sclv

7
@cimmanon ฉันจะต้องใช้เวลาหนึ่งเดือนหรือหลายบล็อกโพสต์เพื่อเดินผ่านพื้นฐานของรายละเอียดของการทำงานของคอมไพเลอร์ ฉันเพียงต้องการคำตอบเพื่อที่จะวาดไว้ในโครงร่างในวงกว้างว่าเครื่องกราฟสามารถดำเนินการเรียบร้อยบนฮาร์ดแวร์สต็อกและชี้ไปที่แหล่งที่เกี่ยวข้องสำหรับการอ่านต่อไป ...
sclv

คำตอบ:


264

ฉันเห็นด้วยกับ Dietrich Epp: เป็นการรวมกันของหลายสิ่งที่ทำให้ GHC รวดเร็ว

ก่อนอื่น Haskell อยู่ในระดับสูงมาก สิ่งนี้ทำให้คอมไพเลอร์สามารถทำการปรับให้เหมาะสมแบบก้าวร้าวโดยไม่ทำให้โค้ดของคุณเสียหาย

คิดเกี่ยวกับ SQL ตอนนี้เมื่อผมเขียนSELECTคำสั่งก็อาจมีลักษณะเช่นห่วงความจำเป็นแต่มันไม่ได้ อาจดูเหมือนว่ามันวนซ้ำแถวทั้งหมดในตารางนั้นพยายามค้นหาหนึ่งที่ตรงกับเงื่อนไขที่ระบุ แต่จริงๆแล้ว "compiler" (เอ็นจิน DB) สามารถทำการค้นหาดัชนีแทนซึ่งมีลักษณะการทำงานที่แตกต่างกันโดยสิ้นเชิง แต่เนื่องจาก SQL อยู่ในระดับสูงดังนั้น "คอมไพเลอร์" จึงสามารถแทนที่อัลกอริธึมที่แตกต่างกันโดยสิ้นเชิงใช้ตัวประมวลผลหลายตัวหรือ I / O channel หรือเซิร์ฟเวอร์ทั้งหมดอย่างโปร่งใสและอื่น ๆ

ฉันคิดว่า Haskell เหมือนกัน คุณอาจคิดว่าคุณเพิ่งขอให้ Haskell ทำการแมปรายการอินพุตเข้ากับรายการที่สองกรองรายการที่สองลงในรายการที่สามแล้วนับจำนวนผลลัพธ์ที่ได้จากรายการ แต่คุณไม่เห็น GHC ใช้กฎการเขียนซ้ำแบบสตรีมฟิวชั่นเบื้องหลังทุกสิ่งกลายเป็นลูปรหัสเครื่องเดียวที่ทำงานทั้งหมดในการส่งผ่านข้อมูลเดียวโดยไม่มีการจัดสรร - สิ่งที่จะ จะน่าเบื่อข้อผิดพลาดได้ง่ายและไม่สามารถบำรุงรักษาได้ด้วยมือ เป็นไปได้จริง ๆ เท่านั้นเนื่องจากไม่มีรายละเอียดในระดับต่ำในรหัส

อีกวิธีในการดูอาจเป็น ... ทำไมHaskell ไม่ควรรวดเร็ว? มันควรทำอะไรช้า ๆ ?

มันไม่ใช่ภาษาที่แปลเช่น Perl หรือ JavaScript มันไม่ได้เป็นระบบเครื่องเสมือนเช่น Java หรือ C # มันรวบรวมไปจนถึงรหัสเครื่องดั้งเดิมดังนั้นจึงไม่มีค่าใช้จ่าย

ต่างจากภาษา OO [Java, C #, JavaScript …], Haskell มีการลบแบบเต็มรูปแบบ [เช่น C, C ++, Pascal …] การตรวจสอบประเภททั้งหมดเกิดขึ้นในเวลารวบรวมเท่านั้น ดังนั้นจึงไม่มีการตรวจสอบชนิดเวลาทำงานเพื่อทำให้คุณช้าลง (ไม่มีการตรวจสอบตัวชี้โมฆะสำหรับเรื่องนั้นในพูด, Java, JVM จะต้องตรวจสอบพอยน์เตอร์พอยน์เตอร์และโยนข้อยกเว้นถ้าคุณให้ความเคารพ Haskell ไม่ต้องกังวลกับการตรวจสอบนั้น)

คุณบอกว่าฟังดูช้าในการ "สร้างฟังก์ชั่นการบินในเวลาทำงาน" แต่ถ้าคุณดูอย่างระมัดระวังคุณจะไม่ทำอย่างนั้น มันอาจดูเหมือนคุณ แต่คุณทำไม่ได้ ถ้าคุณบอก(+5)ว่านั่นเป็นรหัสที่ฮาร์ดโค้ดของคุณ มันไม่สามารถเปลี่ยนแปลงได้ในเวลาทำงาน ดังนั้นมันจึงไม่ใช่ฟังก์ชั่นไดนามิก แม้แต่ฟังก์ชั่น curried ก็แค่บันทึกพารามิเตอร์ลงในบล็อคข้อมูล รหัสปฏิบัติการทั้งหมดมีอยู่จริงในเวลารวบรวม ไม่มีการตีความขณะใช้งาน (ไม่เหมือนกับภาษาอื่น ๆ ที่มี "ฟังก์ชัน eval")

คิดเกี่ยวกับปาสกาล มันเก่าและไม่มีใครใช้มันใด ๆ มากขึ้น แต่ไม่มีใครจะบ่นว่าปาสคาลเป็นช้า มีหลายสิ่งที่ไม่ชอบเกี่ยวกับเรื่องนี้ แต่ความเชื่องช้าไม่ใช่หนึ่งในนั้น Haskell ไม่ได้ทำอะไรที่แตกต่างจาก Pascal มากนักนอกจากมีการรวบรวมขยะมากกว่าการจัดการหน่วยความจำด้วยตนเอง และข้อมูลที่ไม่เปลี่ยนรูปแบบนั้นทำให้เอ็นจิน GC หลายตัวสามารถทำการปรับให้เหมาะสม

ฉันคิดว่าสิ่งนี้คือ Haskell นั้นดูทันสมัยและมีความซับซ้อนและอยู่ในระดับสูงและทุกคนคิดว่า "โอ้ว้าวสิ่งนี้มีพลังมากจริงๆมันต้องช้าอย่างน่าอัศจรรย์! " แต่มันไม่ใช่ หรืออย่างน้อยก็ไม่ได้เป็นไปตามที่คุณคาดหวัง ใช่มันมีระบบการพิมพ์ที่น่าทึ่ง แต่คุณรู้อะไรไหม? ทุกอย่างเกิดขึ้นในเวลารวบรวม เมื่อถึงเวลาทำงานมันก็หายไป ใช่มันช่วยให้คุณสร้าง ADT ที่ซับซ้อนด้วยบรรทัดของโค้ด แต่คุณรู้อะไรไหม? ADT เป็นเพียง C สามัญธรรมดาunionของstructs ไม่มีอะไรเพิ่มเติม

นักฆ่าที่แท้จริงคือการประเมินผลที่ขี้เกียจ เมื่อคุณได้รหัสที่ถูกต้องแม่นยำ / เกียจคร้านคุณสามารถเขียนโค้ดที่รวดเร็วอย่างน่าประหลาดที่ยังคงสง่างามและสวยงาม แต่ถ้าคุณทำสิ่งนี้ผิดโปรแกรมของคุณจะช้าลงหลายพันครั้งและไม่ชัดเจนว่าทำไมสิ่งนี้จึงเกิดขึ้น

ตัวอย่างเช่นฉันเขียนโปรแกรมเล็ก ๆ น้อย ๆ เพื่อนับจำนวนครั้งที่แต่ละไบต์ปรากฏในไฟล์ สำหรับอินพุตไฟล์ 25KB โปรแกรมใช้เวลา20 นาทีในการรันและกลืนกินRAM ขนาด6 กิกะไบต์ ! นั่นไร้สาระ !! แต่แล้วฉันตระหนักว่าปัญหาได้รับการเพิ่มปังรูปแบบเดียวและเวลาทำงานลดลงถึง0.02 วินาที

นี่คือที่ที่ Haskell เดินช้าๆโดยไม่คาดคิด และแน่นอนว่าต้องใช้เวลาสักพักก่อนจะชินกับมัน แต่เมื่อเวลาผ่านไปมันจะง่ายต่อการเขียนโค้ดที่รวดเร็วมาก ๆ

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

แต่ฉันเดาว่าเป็นเพียงความคิดเห็นของฉัน ...


13
@cimmanon ฉันไม่คิดว่ามันอิงตามความคิดเห็นล้วนๆ เป็นคำถามที่น่าสนใจที่คนอื่นอาจต้องการคำตอบ แต่ฉันเดาว่าเราจะเห็นความคิดเห็นของผู้ลงคะแนนคนอื่น ๆ
คณิตศาสตร์

8
@cimmanon - การค้นหานั้นให้เพียงหนึ่งกระทู้ครึ่งและพวกเขาทั้งหมดจะทำอย่างไรกับการตรวจสอบตรวจสอบ และคำตอบที่ upvoted สำหรับเธรดบอกว่า "โปรดหยุดการกลั่นกรองสิ่งที่คุณไม่เข้าใจ" ฉันอยากจะแนะนำว่าถ้ามีคนคิดว่าคำตอบนี้กว้างเกินไปพวกเขาจะต้องประหลาดใจและสนุกกับคำตอบเนื่องจากคำตอบนั้นไม่กว้างเกินไป
sclv

34
"ในการพูด Java, JVM ต้องตรวจสอบพอยน์เตอร์พอยน์เตอร์และโยนข้อยกเว้นถ้าคุณให้ความเคารพ" การตรวจสอบ null โดยนัยของ Java คือ (ส่วนใหญ่) ไม่มีค่าใช้จ่าย การนำ Java ไปใช้และสามารถใช้ประโยชน์จากหน่วยความจำเสมือนเพื่อแมปที่อยู่ Null กับหน้าที่หายไปดังนั้นการลงทะเบียนตัวชี้ Null จึงทำให้เกิดความผิดพลาดของหน้าในระดับ CPU ซึ่ง Java จับและโยนเป็นข้อยกเว้นระดับสูง ดังนั้นการตรวจสอบโมฆะส่วนใหญ่ทำโดยหน่วยการแมปหน่วยความจำใน CPU ฟรี
Boann

4
@cimmanon: อาจเป็นเพราะผู้ใช้ Haskell ดูเหมือนจะเป็นชุมชนเดียวที่จริง ๆ แล้วเป็นกลุ่มคนที่เป็นมิตรที่เปิดกว้าง ... ซึ่งคุณคิดว่าเป็น "เรื่องตลก" …แทนที่จะเป็นชุมชนสุนัขกินหมาที่ปกครองพวกนาซี ฉีกเป็นชิ้นใหม่ในทุกโอกาสที่พวกเขาได้รับ ... ซึ่งดูเหมือนจะเป็นสิ่งที่คุณคิดว่าเป็น "ปกติ"
Evi1M4chine

14
@MathematicalOrchid: คุณมีสำเนาของโปรแกรมต้นฉบับของคุณซึ่งใช้เวลา 20 นาทีในการทำงานหรือไม่? ฉันคิดว่ามันค่อนข้างเป็นคำแนะนำในการศึกษาว่าทำไมมันจึงช้า
George

79

เป็นเวลานานที่คิดว่าภาษาที่ใช้งานไม่สามารถรวดเร็วและโดยเฉพาะอย่างยิ่งภาษาที่ใช้งานได้ดี แต่นี่เป็นเพราะการใช้งานในช่วงแรกของพวกเขานั้นเป็นสาระสำคัญตีความและไม่ได้รวบรวมอย่างแท้จริง

คลื่นลูกที่สองของการออกแบบเกิดขึ้นจากการลดกราฟและเปิดโอกาสในการรวบรวมที่มีประสิทธิภาพมากขึ้น Simon Peyton Jones เขียนเกี่ยวกับงานวิจัยนี้ในหนังสือสองเล่มของเขาThe Implementation of Programming Programming Languagesและภาษาที่ใช้งานได้: กวดวิชา (อดีตส่วนกับ Wadler และ Hancock และหลังเขียนด้วย David Lester) (Lennart Augustsson แจ้งด้วยว่าแรงจูงใจสำคัญอย่างหนึ่งสำหรับหนังสือเล่มก่อนหน้านี้ก็คือการอธิบายถึงวิธีการรวบรวม LML ของเขาซึ่งไม่ได้แสดงความคิดเห็นอย่างกว้างขวางทำให้การรวบรวมสำเร็จ)

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

นี่คือคำอธิบายในบทความต่อมาที่วางพื้นฐานสำหรับวิธี GHC ยังคงทำงานวันนี้ (แม้ว่า modulo tweaks ต่าง ๆ มากมาย): "การใช้งานฟังก์ชั่น Lazy ภาษาบนฮาร์ดแวร์หุ้น: G-Machine ไม่มีกระดูกสันหลัง Tagless" . รูปแบบการดำเนินการปัจจุบัน GHC เป็นเอกสารในรายละเอียดเพิ่มเติมได้ที่GHC วิกิพีเดีย

ดังนั้นข้อมูลเชิงลึกคือความแตกต่างที่เข้มงวดของ "ข้อมูล" และ "รหัส" ซึ่งเราคิดว่าเป็น "พื้นฐาน" ต่อการทำงานของเครื่องไม่ใช่วิธีที่เครื่องต้องทำงาน แต่ถูกกำหนดโดยคอมไพเลอร์ของเรา ดังนั้นเราสามารถโยนมันออกมาและมีรหัส (คอมไพเลอร์) ที่สร้างรหัสการแก้ไขตัวเอง (ปฏิบัติการ) และมันสามารถทำงานได้ค่อนข้างดี

ดังนั้นจึงปรากฎว่าในขณะที่สถาปัตยกรรมเครื่องมีความจำเป็นในบางแง่มุมภาษาอาจแมปกับพวกเขาในรูปแบบที่น่าแปลกใจมากที่ดูไม่เหมือนการควบคุมการไหลสไตล์ C ทั่วไปและถ้าเราคิดว่าระดับต่ำพอ ที่มีประสิทธิภาพ

นอกจากนี้ยังมีการเพิ่มประสิทธิภาพอื่น ๆ อีกมากมายที่เปิดขึ้นโดยเฉพาะอย่างยิ่งความบริสุทธิ์เนื่องจากจะช่วยให้ช่วงการเปลี่ยนภาพที่ "ปลอดภัย" มากขึ้น เมื่อใดและอย่างไรที่จะใช้การเปลี่ยนแปลงเหล่านี้เช่นที่พวกเขาทำสิ่งที่ดีกว่าและไม่เลวร้ายกว่านั้นเป็นคำถามเชิงประจักษ์และในทางเลือกเล็ก ๆ น้อย ๆ นี้มีการทำงานหลายปีเข้ามาทั้งในทางทฤษฎีและการเปรียบเทียบเชิงปฏิบัติ แน่นอนว่านี้มีบทบาทเช่นกัน กระดาษที่ให้ตัวอย่างที่ดีของการวิจัยประเภทนี้คือ "การทำแกงอย่างรวดเร็ว: กด / Enter กับ Eval / สมัครภาษาที่สูงกว่า"

ในที่สุดก็ควรสังเกตว่ารุ่นนี้ยังแนะนำค่าใช้จ่ายเนื่องจากทางอ้อม สิ่งนี้สามารถหลีกเลี่ยงได้ในกรณีที่เรารู้ว่า "ปลอดภัย" ในการทำสิ่งต่าง ๆ อย่างเคร่งครัดและทำให้เกิดการเบี่ยงเบนกราฟ กลไกที่สรุปเข้มงวด / ต้องมีเอกสารอีกครั้งในรายละเอียดบางที่GHC วิกิพีเดีย


2
ลิงก์ตัววิเคราะห์อุปสงค์นั้นมีค่าน้ำหนักเป็นทองคำ! ในที่สุดบางสิ่งบางอย่างเกี่ยวกับหัวข้อที่ไม่ได้ทำตัวราวกับว่ามันเป็นเวทมนตร์สีดำที่อธิบายไม่ได้ ฉันไม่เคยได้ยินเรื่องนี้ได้อย่างไร? มันควรจะเชื่อมโยงจากทุกที่ที่มีคนถามวิธีจัดการกับปัญหาด้วยความเกียจคร้าน!
Evi1M4chine

@ Evi1M4chine ฉันไม่เห็นลิงก์ที่เกี่ยวข้องกับตัววิเคราะห์ความต้องการบางทีมันอาจจะหายไปอย่างใด ใครสามารถคืนค่าลิงก์หรือทำให้การอ้างอิงชัดเจนขึ้น? ฟังดูน่าสนใจทีเดียว
Cris P

1
@CrisP ฉันเชื่อว่าลิงก์สุดท้ายคือสิ่งที่ถูกอ้างถึง มันไปที่หน้าใน GHC Wiki เกี่ยวกับตัววิเคราะห์ความต้องการใน GHC
Serp C

@ Serpentine Cougar, Chris P: ใช่มันเป็นสิ่งที่ฉันหมายถึง
Evi1M4chine

19

มีความคิดเห็นมากมายเกี่ยวกับที่นี่ ฉันจะพยายามตอบให้มากที่สุด

ใช้อย่างถูกต้องสามารถใช้ภาษาใกล้เคียงกับภาษาระดับต่ำได้

จากประสบการณ์ของฉันมักจะเป็นไปได้ที่จะเพิ่มประสิทธิภาพการทำงานของสนิมใน 2x ในหลายกรณี แต่ยังมีกรณีการใช้งาน (แบบกว้าง) บางกรณีที่ประสิทธิภาพการทำงานไม่ดีเมื่อเทียบกับภาษาระดับต่ำ

หรือแม้กระทั่งเอาชนะ แต่นั่นหมายความว่าคุณกำลังใช้โปรแกรม C ที่ไม่มีประสิทธิภาพเนื่องจาก GHC รวบรวม Haskell เป็น C)

นั่นไม่ถูกต้องทั้งหมด Haskell คอมไพล์ไปที่ C-- (ส่วนย่อยของ C) ซึ่งถูกคอมไพล์แล้วผ่านตัวสร้างโค้ดเนทีฟไปยังแอสเซมบลี เครื่องกำเนิดรหัสเนทีฟมักสร้างโค้ดที่เร็วกว่าคอมไพเลอร์ C เพราะสามารถใช้การปรับแต่งบางอย่างที่คอมไพเลอร์ C ธรรมดาไม่สามารถทำได้

สถาปัตยกรรมเครื่องจักรมีความจำเป็นอย่างชัดเจนโดยขึ้นอยู่กับเครื่องจักรทัวริง

นั่นไม่ใช่วิธีที่ดีที่จะคิดเกี่ยวกับเรื่องนี้โดยเฉพาะอย่างยิ่งเนื่องจากโปรเซสเซอร์สมัยใหม่จะประเมินคำสั่งที่ผิดปกติและอาจเกิดขึ้นพร้อมกัน

ที่จริง Haskell ไม่มีคำสั่งประเมินที่เฉพาะเจาะจง

ที่จริงแล้ว Haskell จะกำหนดลำดับการประเมินโดยปริยาย

นอกจากนี้แทนที่จะจัดการกับชนิดข้อมูลเครื่องคุณทำประเภทข้อมูลพีชคณิตตลอดเวลา

พวกเขาสอดคล้องกันในหลายกรณีหากคุณมีคอมไพเลอร์ขั้นสูงพอสมควร

คุณอาจคิดว่าการสร้างฟังก์ชั่นได้ทันทีและการขว้างมันไปรอบ ๆ จะทำให้โปรแกรมช้าลง

Haskell ได้รับการรวบรวมและฟังก์ชั่นการเรียงลำดับขั้นสูงจึงไม่ได้ถูกสร้างขึ้นอย่างแท้จริง

ดูเหมือนว่าการเพิ่มประสิทธิภาพรหัส Haskell คุณจะต้องทำให้มันดูสง่างามและเป็นนามธรรมมากกว่าการใช้เครื่องจักร

โดยทั่วไปแล้วการทำโค้ดให้มากกว่า "machine like" เป็นวิธีที่ไม่ก่อผลเพื่อให้ได้ประสิทธิภาพที่ดีขึ้นใน Haskell แต่การทำให้เป็นนามธรรมมากขึ้นก็ไม่ใช่ความคิดที่ดีเช่นกัน สิ่งที่เป็นความคิดที่ดีคือการใช้ร่วมกันโครงสร้างข้อมูลและฟังก์ชั่นที่ได้รับการปรับปรุงอย่างมาก (เช่นรายการที่เชื่อมโยง)

f x = [x]และf = pureเป็นสิ่งเดียวกันใน Haskell เช่น คอมไพเลอร์ที่ดีจะไม่ให้ประสิทธิภาพที่ดีขึ้นในกรณีก่อน

ทำไม Haskell (เรียบเรียงกับ GHC) จึงรวดเร็วโดยพิจารณาจากธรรมชาติที่เป็นนามธรรมและความแตกต่างจากเครื่องจักรทางกายภาพ?

คำตอบสั้น ๆ คือ "เพราะมันถูกออกแบบมาเพื่อทำอย่างนั้น" GHC ใช้สปินเลส g-machine (STG) ไร้สปิน คุณสามารถอ่านกระดาษเกี่ยวกับที่นี่ (มันค่อนข้างซับซ้อน) GHC ไม่มากของสิ่งอื่น ๆ ได้เป็นอย่างดีเช่นการวิเคราะห์ความเข้มงวดและการประเมินผลในแง่ดี

เหตุผลที่ฉันพูดภาษา C และภาษาที่จำเป็นอื่น ๆ นั้นค่อนข้างคล้ายกับทัวริงแมชชีน (แต่ไม่เท่า Haskell คล้ายกับแลมบ์ดาแคลคูลัส) นั่นคือในภาษาที่จำเป็นคุณมีจำนวน จำกัด ของรัฐ (หมายเลขบรรทัดอาคา) พร้อม ด้วยเทป (RAM) เช่นว่าสถานะและเทปปัจจุบันกำหนดว่าจะทำอย่างไรกับเทป

ประเด็นของความสับสนนั้นคือความไม่แน่นอนที่ควรนำไปสู่รหัสช้าลงหรือไม่? ความเกียจคร้านของ Haskell นั้นหมายถึงความไม่แน่นอนนั้นไม่สำคัญเท่ากับที่คุณคิดรวมถึงระดับสูงดังนั้นจึงมีการปรับแต่งมากมายที่คอมไพเลอร์สามารถนำไปใช้ได้ ดังนั้นการแก้ไขเรคคอร์ดในสถานที่จะไม่ค่อยช้ากว่าในภาษาเช่น C


3

ทำไม Haskell (GHC) ถึงเร็วขนาดนี้

บางสิ่งต้องเปลี่ยนไปอย่างมากตั้งแต่ฉันวัดประสิทธิภาพของ Haskell ครั้งล่าสุด ตัวอย่างเช่น:

ดังนั้นสิ่งที่เปลี่ยนไป? ฉันสังเกตเห็นว่าไม่ใช่คำถามหรือคำตอบใด ๆ ในปัจจุบันที่อ้างถึงมาตรฐานที่ตรวจสอบได้หรือแม้แต่รหัส

สิ่งที่โปรดปรานสำหรับ Haskellers ที่ต้องทำคือพยายามและให้ได้ภายใน 5% ของ C

คุณมีการอ้างอิงถึงผลลัพธ์ที่ตรวจสอบได้ซึ่งทุกคนอยู่ใกล้กับที่หรือไม่?


6
มีคนพูดชื่อของ Harrop ต่อหน้ากระจกอีกครั้งสามครั้งหรือไม่?
Chuck Adams

2
ไม่ใช่ 10x แต่ก็ยังคงรายการทั้งหมดนี้คือการโฆษณาและการตลาดผ้าขี้ริ้ว GHCนั้นมีความสามารถในการเข้าใกล้ C หรือแม้กระทั่งการเอาชนะมันในบางครั้งในแง่ของความเร็ว แต่โดยทั่วไปจะต้องมีรูปแบบการเขียนโปรแกรมระดับต่ำที่เกี่ยวข้องไม่แตกต่างจากการเขียนโปรแกรมใน C มากนัก น่าเสียดาย. รหัสในระดับที่สูงกว่าปกติจะช้ากว่า การรั่วไหลของพื้นที่สะดวกสบาย แต่มีประสิทธิภาพต่ำกว่าประเภท ADT ( พีชคณิตไม่ใช่นามธรรมตามคำสัญญา) และอื่น ๆ
Will Ness

1
ฉันแค่โพสต์นี้เพราะผมเห็นมันในวันนี้chrispenner.ca/posts/wc มันเป็นการใช้งานยูทิลิตี wc ที่เขียนใน Haskell ซึ่งควรจะเป็นเวอร์ชัน c
Garrison

3
ขอบคุณ @Garrison สำหรับการเชื่อมโยง 80 บรรทัดคือสิ่งที่ฉันเรียกว่า "รูปแบบการเขียนโปรแกรมระดับต่ำไม่แตกต่างจากการเขียนโปรแกรมใน C มากนัก" . "รหัสระดับที่สูงขึ้น" ที่จะเป็น fmap (length &&& length . words &&& length . lines) readFile"โง่" ถ้าที่เร็วกว่า (หรือแม้กระทั่งเทียบได้กับ) C, hype ที่นี่จะเป็นธรรมโดยสิ้นเชิงแล้ว เรายังคงต้องทำงานอย่างหนักเพื่อความเร็วใน Haskell เช่นเดียวกับใน C คือประเด็น
Will Ness

2
ตัดสินโดยการสนทนานี้ใน Reddit reddit.com/r/programming/comments/dj4if3/ …ว่ารหัส Haskell นั้นมีข้อผิดพลาดจริง ๆ (เช่นตัวแบ่งในบรรทัดเริ่มต้นหรือสิ้นสุดด้วย whitespace ตัวแบ่งบนà) และคนอื่น ๆ ไม่สามารถทำซ้ำผลการปฏิบัติงานที่อ้างสิทธิ์ได้
Jon Harrop
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.