seq ใช้บ่อยแค่ไหนในรหัสการผลิต Haskell


23

ฉันมีประสบการณ์ในการเขียนเครื่องมือขนาดเล็กใน Haskell และฉันพบว่ามันใช้งานง่ายมากโดยเฉพาะการเขียนตัวกรอง (โดยใช้interact) ที่ประมวลผลอินพุตมาตรฐานและไพพ์ไปยังเอาต์พุตมาตรฐาน

เมื่อเร็ว ๆ นี้ฉันพยายามใช้ตัวกรองดังกล่าวกับไฟล์ที่มีขนาดใหญ่กว่าปกติประมาณ 10 เท่าและฉันพบStack space overflowข้อผิดพลาด

หลังจากอ่านบางส่วน (เช่นที่นี่และที่นี่ ) ฉันได้ระบุแนวทางสองข้อเพื่อประหยัดพื้นที่สแต็ก (Haskellers ที่มีประสบการณ์โปรดแก้ไขให้ฉันถ้าฉันเขียนสิ่งที่ไม่ถูกต้อง):

  1. หลีกเลี่ยงการเรียกฟังก์ชั่นแบบเรียกซ้ำที่ไม่ใช่แบบเรียกซ้ำ (ใช้ได้กับทุกภาษาที่ใช้งานได้ซึ่งรองรับการปรับให้เหมาะสมแบบหางเรียก)
  2. แนะนำseqให้บังคับให้มีการประเมินผลก่อนของนิพจน์ย่อยเพื่อให้นิพจน์ไม่ใหญ่เกินไปก่อนที่จะถูกลดขนาดลง (นี่เป็นลักษณะเฉพาะของ Haskell หรืออย่างน้อยก็สำหรับภาษาที่ใช้การประเมินแบบขี้เกียจ)

หลังจากแนะนำการseqโทรห้าหรือหกครั้งในรหัสของฉันเครื่องมือของฉันจะทำงานได้อย่างราบรื่นอีกครั้ง (เช่นเดียวกับข้อมูลขนาดใหญ่) อย่างไรก็ตามฉันพบว่ารหัสต้นฉบับอ่านง่ายขึ้นอีกเล็กน้อย

เนื่องจากฉันไม่ใช่โปรแกรมเมอร์ Haskell ที่มีประสบการณ์ฉันต้องการถามว่าการแนะนำseqด้วยวิธีนี้เป็นวิธีปฏิบัติทั่วไปหรือไม่และบ่อยครั้งที่บุคคลทั่วไปจะเห็นseqในรหัสการผลิต Haskell หรือมีเทคนิคใดบ้างที่อนุญาตให้หลีกเลี่ยงการใช้seqบ่อยเกินไปและยังคงใช้พื้นที่สแต็กน้อย?


1
การปรับให้เหมาะสมเช่นที่คุณอธิบายไว้มักจะทำให้โค้ดดูสง่างามน้อยลง
Robert Harvey

@ Robert Harvey: มีเทคนิคอื่นใดที่จะทำให้การใช้งานสแต็คต่ำหรือไม่? ฉันหมายถึงฉันคิดว่าฉันต้องเขียนฟังก์ชั่นของฉันให้แตกต่างออกไป แต่ฉันก็ไม่รู้ว่ามีเทคนิคที่ดีหรือไม่ ความพยายามครั้งแรกของฉันคือการใช้ฟังก์ชั่นหางซ้ำซึ่งช่วย แต่ไม่อนุญาตให้ฉันแก้ปัญหาของฉันอย่างสมบูรณ์
Giorgio

คำตอบ:


17

น่าเสียดายที่มีบางกรณีที่ต้องใช้seqเพื่อให้ได้โปรแกรมที่มีประสิทธิภาพ / ใช้งานได้ดีสำหรับข้อมูลขนาดใหญ่ ดังนั้นในหลายกรณีคุณไม่สามารถทำได้หากไม่มีรหัสการผลิต คุณสามารถหาข้อมูลเพิ่มเติมได้ในโลกแห่งความจริง Haskell, บทที่ 25 Profiling และการเพิ่มประสิทธิภาพ

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

  1. ใช้ท่อ , ท่อหรือiterateesinteractแทน Lazy IO เป็นที่ทราบกันดีว่ามีปัญหาเกี่ยวกับการจัดการทรัพยากร (ไม่ใช่แค่หน่วยความจำ) และได้รับการออกแบบซ้ำเพื่อเอาชนะสิ่งนี้ (ฉันขอแนะนำให้หลีกเลี่ยง lazy IO โดยรวมไม่ว่าข้อมูลของคุณจะมีขนาดใหญ่เพียงใด - ดูปัญหาเกี่ยวกับ lazy I / O )
  2. แทนที่จะใช้seqโดยตรง (หรือออกแบบของคุณเอง) combinators เช่นfoldl 'หรือfoldr'หรือเวอร์ชันที่เข้มงวดของไลบรารี (เช่นData.Map.StrictหรือControl.Monad.State.Strict ) ที่ออกแบบมาเพื่อการคำนวณที่เข้มงวด
  3. ใช้ส่วนขยายของBangPatterns มันช่วยให้แทนที่seqด้วยการจับคู่รูปแบบที่เข้มงวด การประกาศฟิลด์ตัวสร้างที่เข้มงวดอาจมีประโยชน์ในบางกรณี
  4. อาจเป็นไปได้ที่จะใช้กลยุทธ์ในการบังคับให้ประเมินผล ห้องสมุดกลยุทธ์ส่วนใหญ่มุ่งเป้าไปที่การคำนวณแบบขนาน แต่มีวิธีการในการบังคับค่าให้WHNF ( rseq) หรือNFเต็ม( rdeepseq) เช่นกัน มีวิธีอรรถประโยชน์มากมายสำหรับการทำงานกับคอลเลกชันรวมกลยุทธ์ ฯลฯ

+1: ขอบคุณสำหรับคำแนะนำและลิงก์ที่มีประโยชน์ จุดที่ 3 ดูน่าสนใจมาก (และวิธีแก้ปัญหาที่ง่ายที่สุดสำหรับฉันที่จะใช้ตอนนี้) เกี่ยวกับข้อเสนอแนะ 1 ฉันไม่เห็นว่าการหลีกเลี่ยง lazy IO สามารถปรับปรุงสิ่งต่าง ๆ ได้: เท่าที่ฉันเข้าใจว่า lazy IO น่าจะดีกว่าสำหรับตัวกรองที่ควรประมวลผลสตรีมข้อมูล (อาจยาวมาก)
Giorgio

2
@Giorgio ฉันได้เพิ่มลิงก์ไปยัง Haskell Wiki เกี่ยวกับปัญหากับ Lazy IO ด้วย lazy IO คุณสามารถจัดการกับทรัพยากรได้ยากมาก ตัวอย่างเช่นหากคุณไม่ได้อ่านอินพุต (เช่นเนื่องจากการประเมินผลที่ขี้เกียจ) ตัวจัดการไฟล์จะยังคงเปิดอยู่ และถ้าคุณไปและปิดที่จับไฟล์ด้วยตนเองก็มักจะเกิดขึ้นเนื่องจากการประเมินผลที่ขี้เกียจอ่านมันถูกเลื่อนออกไปและคุณปิดที่จับก่อนที่จะอ่านอินพุตทั้งหมด และบ่อยครั้งที่ค่อนข้างยากที่จะหลีกเลี่ยงปัญหาหน่วยความจำด้วย lazy IO
Petr Pudlák

ฉันเพิ่งมีปัญหานี้และโปรแกรมของฉันไม่มีตัวอธิบายไฟล์ ดังนั้นผมจึงแทนที่ขี้เกียจ IO IO ByteStringกับเข้มงวดการใช้อย่างเข้มงวด
Giorgio
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.