อะไรที่แย่เกี่ยวกับ Lazy I / O?


89

ฉันได้ยินมาโดยทั่วไปว่ารหัสการผลิตควรหลีกเลี่ยงการใช้ Lazy I / O คำถามของฉันคือทำไม? เป็นเรื่องปกติหรือไม่ที่จะใช้ Lazy I / O นอกเหนือจากการเล่นเฉยๆ? และอะไรทำให้ทางเลือกอื่น (เช่นตัวนับ) ดีกว่า?

คำตอบ:


81

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

เลซี่สตรีมเป็นรูปแบบที่สะดวกมากในการตั้งโปรแกรมด้วยเหตุนี้ท่อหอยจึงสนุกและเป็นที่นิยม

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

บางครั้งคุณต้องปล่อยทรัพยากรอย่างกระตือรือร้นเพื่อปรับปรุงความสามารถในการปรับขนาด

แล้วทางเลือกอื่นสำหรับ IO ที่ขี้เกียจซึ่งไม่ได้หมายความว่าจะยอมแพ้กับการประมวลผลแบบเพิ่มหน่วย (ซึ่งจะใช้ทรัพยากรมากเกินไป)? เรามีfoldlพื้นฐานการประมวลผลหรือที่รู้จักกันในชื่อวนซ้ำหรือตัวนับซึ่งแนะนำโดยโอเล็กคิลิซอฟในช่วงปลายยุค 2000และนับตั้งแต่ได้รับความนิยมจากโครงการที่ใช้ระบบเครือข่ายจำนวนมาก

แทนที่จะประมวลผลข้อมูลเป็นสตรีมที่เกียจคร้านหรือเป็นกลุ่มใหญ่ ๆ เราแทนที่จะใช้การประมวลผลที่เข้มงวดแบบเป็นกลุ่มโดยรับประกันการสรุปทรัพยากรเมื่ออ่านข้อมูลครั้งสุดท้ายแล้ว นั่นคือสาระสำคัญของการเขียนโปรแกรมแบบวนซ้ำและข้อ จำกัด ด้านทรัพยากรที่ดีมาก

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


22
ตั้งแต่ผมเพียงตามลิงค์ไปยังคำถามเดิมนี้จากการอภิปรายของขี้เกียจ I / O ที่ฉันคิดว่าฉันต้องการเพิ่มบันทึกว่าตั้งแต่นั้นมามากของความอึดอัดของ iteratees ได้รับ superseeded ห้องสมุดสตรีมมิ่งใหม่เช่นท่อและท่อร้อยสาย
Ørjan Johansen

40

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

average :: [Float] -> Float
average xs = sum xs / length xs

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

average2 :: [Float] -> Float
average2 xs = uncurry (/) <$> foldl (\(sumT, n) x -> (sumT+x, n+1)) (0,0) xs
-- N.B. this will build up thunks as written, use a strict pair and foldl'

แต่ค่อนข้างไม่สะดวกที่จะต้องทำสิ่งนี้กับโปรเซสเซอร์สตรีมทุกตัว มีการสรุปบางอย่าง ( Conal Elliott - Beautiful Fold Zipping ) แต่ดูเหมือนจะไม่ติด อย่างไรก็ตามการวนซ้ำจะช่วยให้คุณมีระดับการแสดงออกที่ใกล้เคียงกัน

aveIter = uncurry (/) <$> I.zip I.sum I.length

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

badAveIter = (\xs -> sum xs / length xs) <$> I.stream2list

สถานะของการวนซ้ำในรูปแบบการเขียนโปรแกรมเป็นงานที่อยู่ระหว่างดำเนินการอย่างไรก็ตามมันดีกว่าปีที่แล้วมาก เรากำลังเรียนรู้สิ่งที่ combinators มีประโยชน์ (เช่นzip, breakE,enumWith ) และที่น้อยดังนั้นมีผลว่าในตัว iteratees และ combinators ให้ expressivity อย่างต่อเนื่องมากขึ้น

ที่กล่าวว่าดอนส์ถูกต้องว่าเป็นเทคนิคขั้นสูง แน่นอนฉันจะไม่ใช้มันสำหรับทุกปัญหา I / O


25

ฉันใช้ I / O ที่ขี้เกียจในรหัสการผลิตตลอดเวลา เป็นเพียงปัญหาในบางสถานการณ์เช่นที่ดอนกล่าวถึง แต่สำหรับการอ่านเพียงไม่กี่ไฟล์ก็ใช้ได้ดี


ฉันใช้ I / O ที่ขี้เกียจเกินไป ฉันหันไปทำซ้ำเมื่อฉันต้องการควบคุมการจัดการทรัพยากรมากขึ้น
John L

20

อัปเดต:เมื่อเร็ว ๆ นี้ใน haskell-cafe Oleg Kiseljov แสดงให้เห็นว่าunsafeInterleaveST(ซึ่งใช้สำหรับการใช้งาน IO ที่ขี้เกียจภายใน ST monad) นั้นไม่ปลอดภัยมาก - มันทำลายเหตุผลที่เท่าเทียมกัน เขาแสดงให้เห็นว่าอนุญาตให้สร้างbad_ctx :: ((Bool,Bool) -> Bool) -> Bool เช่นนั้นได้

> bad_ctx (\(x,y) -> x == y)
True
> bad_ctx (\(x,y) -> y == x)
False

แม้ว่าจะ==เป็นการสับเปลี่ยน


ปัญหาอื่นเกี่ยวกับ IO ที่ขี้เกียจ: การดำเนินการ IO จริงอาจถูกเลื่อนออกไปจนกว่าจะสายเกินไปเช่นหลังจากปิดไฟล์แล้ว อ้างจากHaskell Wiki - ปัญหาเกี่ยวกับ IO ที่ขี้เกียจ :

ตัวอย่างเช่นข้อผิดพลาดทั่วไปของผู้เริ่มต้นคือการปิดไฟล์ก่อนที่จะอ่านเสร็จ:

wrong = do
    fileData <- withFile "test.txt" ReadMode hGetContents
    putStr fileData

ปัญหาคือด้วย File ปิดจุดจับก่อนที่ fileData จะถูกบังคับ วิธีที่ถูกต้องคือส่งรหัสทั้งหมดไปที่ withFile:

right = withFile "test.txt" ReadMode $ \handle -> do
    fileData <- hGetContents handle
    putStr fileData

ที่นี่ข้อมูลจะถูกใช้ก่อนที่ไฟล์จะเสร็จสิ้น

ซึ่งมักจะเกิดขึ้นโดยไม่คาดคิดและเป็นข้อผิดพลาดที่ง่าย


ดูเพิ่มเติม: สามตัวอย่างของปัญหาเกี่ยวกับการขี้เกียจ I / O


จริงๆแล้วรวมเข้าด้วยกันhGetContentsและwithFileไม่มีจุดหมายเพราะในอดีตทำให้แฮนเดิลอยู่ในสถานะ "ปิดหลอก" และจะจัดการปิดให้คุณ (อย่างเกียจคร้าน) ดังนั้นรหัสจึงเทียบเท่ากันทุกประการreadFileหรือแม้กระทั่งopenFileไม่มี hCloseนั่นเป็นสิ่งที่ขี้เกียจ I / O มี หากคุณไม่ได้ใช้readFile, getContentsหรือhGetContentsคุณไม่ได้ใช้ขี้เกียจ I / O ตัวอย่างเช่นใช้line <- withFile "test.txt" ReadMode hGetLineงานได้ดี
Dag

1
@Dag: แม้ว่าhGetContentsจะจัดการปิดไฟล์ให้คุณ แต่ก็ยังอนุญาตให้ปิดได้ด้วยตัวเอง "ก่อนกำหนด" และช่วยให้มั่นใจได้ว่าทรัพยากรจะถูกปล่อยออกมาอย่างคาดเดาได้
Ben Millwood

17

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

ตัวอย่างเช่นนี่คือคำถามเกี่ยวกับโค้ดที่ดูสมเหตุสมผล แต่ทำให้สับสนมากขึ้นโดย IO ที่รอการตัดบัญชี: withFile กับ openFile

ปัญหาเหล่านี้ไม่ได้ร้ายแรงเสมอไป แต่เป็นอีกสิ่งหนึ่งที่ต้องนึกถึงและอาการปวดหัวที่รุนแรงพอสมควรที่ฉันเองจะหลีกเลี่ยง IO ที่ขี้เกียจเว้นแต่จะมีปัญหาจริงในการทำงานทั้งหมดล่วงหน้า

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