ฉันเพิ่งเรียนรู้ว่าการประเมินผลแบบขี้เกียจทำงานได้อย่างไรและฉันสงสัยว่า: ทำไมจึงไม่ใช้การประเมินแบบขี้เกียจในทุกซอฟต์แวร์ที่ผลิตในปัจจุบัน ทำไมยังใช้การประเมินความกระตือรือร้น
ฉันเพิ่งเรียนรู้ว่าการประเมินผลแบบขี้เกียจทำงานได้อย่างไรและฉันสงสัยว่า: ทำไมจึงไม่ใช้การประเมินแบบขี้เกียจในทุกซอฟต์แวร์ที่ผลิตในปัจจุบัน ทำไมยังใช้การประเมินความกระตือรือร้น
คำตอบ:
การประเมินที่ขี้เกียจจำเป็นต้องมีค่าใช้จ่ายในการทำหนังสือ - คุณต้องรู้ว่ามันถูกประเมินและสิ่งต่าง ๆ หรือไม่ การประเมินความกระตือรือร้นได้รับการประเมินเสมอดังนั้นคุณไม่จำเป็นต้องรู้ โดยเฉพาะอย่างยิ่งในบริบทที่เกิดขึ้นพร้อมกัน
ประการที่สองมันเป็นเรื่องเล็กน้อยที่จะแปลงการประเมินความกระตือรือร้นเป็นการประเมินแบบสันหลังยาวโดยการบรรจุลงในวัตถุฟังก์ชันที่จะเรียกใช้ในภายหลังถ้าคุณต้องการ
ประการที่สามการประเมินผลที่ขี้เกียจหมายถึงการสูญเสียการควบคุม หากฉันประเมินการอ่านไฟล์จากดิสก์อย่างเกียจคร้าน หรือรับเวลา? ไม่เป็นที่ยอมรับ
การประเมินความกระตือรือร้นสามารถมีประสิทธิภาพมากขึ้นและสามารถควบคุมได้มากขึ้นและถูกแปลงเป็นการประเมินที่ขี้เกียจ ทำไมคุณถึงต้องการประเมินผลแบบขี้เกียจ
readFile
เป็นว่าสิ่งที่ฉันต้องการ นอกจากนี้การแปลงจากความขี้เกียจไปเป็นการประเมินความกระตือรือร้นก็เป็นเรื่องเล็กน้อย
head [1 ..]
ทำให้คุณในภาษาที่บริสุทธิ์ประเมินกระหายเพราะใน Haskell จะให้1
?
สาเหตุหลักมาจากรหัสและสถานะที่ขี้เกียจสามารถผสมกันได้ไม่ดีและทำให้เกิดข้อผิดพลาดบางอย่างในการค้นหา หากสถานะของวัตถุที่อ้างถึงเปลี่ยนแปลงค่าของวัตถุขี้เกียจของคุณอาจผิดเมื่อประเมิน มันจะดีกว่ามากถ้าให้โปรแกรมเมอร์เขียนโค้ดให้ชัดเจนเพื่อให้ขี้เกียจเมื่อเขา / เธอรู้สถานการณ์ที่เหมาะสม
ในหมายเหตุด้าน Haskell ใช้การประเมิน Lazy สำหรับทุกสิ่ง สิ่งนี้เป็นไปได้เพราะเป็นภาษาที่ใช้งานได้และไม่ได้ใช้สถานะ (ยกเว้นในกรณีพิเศษบางประการที่มีการทำเครื่องหมายไว้อย่างชัดเจน)
set!
ล่าม Scheme ที่ขี้เกียจ > :(
การประเมิน Lazy นั้นไม่ได้ดีกว่าเสมอไป
ประโยชน์ด้านประสิทธิภาพของการประเมินแบบขี้เกียจนั้นดีมาก แต่ก็ไม่ยากที่จะหลีกเลี่ยงการประเมินที่ไม่จำเป็นที่สุดในสภาพแวดล้อมที่กระตือรือร้น - ขี้เกียจทำให้มันง่ายและสมบูรณ์ แต่ไม่ค่อยเป็นการประเมินที่ไม่จำเป็นในรหัสเป็นปัญหาหลัก
ข้อดีของการประเมินแบบสันหลังยาวคือเมื่อมันช่วยให้คุณเขียนโค้ดที่ชัดเจนขึ้น รับนายกที่ 10 โดยการกรองรายการหมายเลขธรรมชาติที่ไม่มีที่สิ้นสุดและการรับองค์ประกอบที่ 10 ของรายการนั้นเป็นหนึ่งในวิธีที่รัดกุมและชัดเจนที่สุดของการดำเนินการ: (pseudocode)
let numbers = [1,2...]
fun is_prime x = none (map (y-> x mod y == 0) [2..x-1])
let primes = filter is_prime numbers
let tenth_prime = first (take primes 10)
ฉันเชื่อว่ามันค่อนข้างยากที่จะแสดงสิ่งต่าง ๆ อย่างรัดกุมโดยไม่มีความขี้เกียจ
แต่ความขี้เกียจไม่ใช่คำตอบสำหรับทุกสิ่ง สำหรับ starters ความเกียจคร้านไม่สามารถนำมาใช้อย่างโปร่งใสในที่ที่มีสถานะและฉันเชื่อว่า statefulness ไม่สามารถตรวจพบโดยอัตโนมัติได้ ดังนั้นในภาษาส่วนใหญ่ความขี้เกียจจำเป็นต้องทำด้วยตนเองซึ่งทำให้สิ่งต่าง ๆ ชัดเจนน้อยลงและกำจัดข้อดีที่สำคัญอย่างหนึ่งของ eval ขี้เกียจออกไป
นอกจากนี้ความขี้เกียจมีข้อเสียด้านประสิทธิภาพเนื่องจากมีค่าใช้จ่ายสูงในการรักษานิพจน์ที่ไม่ได้รับการประเมิน พวกเขาใช้พื้นที่เก็บข้อมูลมากขึ้นและทำงานได้ช้ากว่าค่าที่เรียบง่าย ไม่ใช่เรื่องแปลกที่จะพบว่าคุณต้องมีรหัสกระตือรือร้นเพราะรุ่นขี้เกียจเป็นสุนัขช้าและบางครั้งก็ยากที่จะให้เหตุผลเกี่ยวกับประสิทธิภาพ
มีแนวโน้มที่จะเกิดขึ้นไม่มีกลยุทธ์ที่ดีที่สุดแน่นอน Lazy นั้นยอดเยี่ยมถ้าคุณสามารถเขียนโค้ดได้ดีกว่าโดยใช้ประโยชน์จากโครงสร้างข้อมูลที่ไม่มีที่สิ้นสุดหรือกลยุทธ์อื่น ๆ ที่อนุญาตให้คุณใช้
นี่คือการเปรียบเทียบสั้น ๆ ของข้อดีข้อเสียของการประเมินผลความกระตือรือร้นและขี้เกียจ:
การประเมินผลกระตือรือร้น:
ค่าโสหุ้ยในการประเมินสิ่งของ
การประเมินผลไม่ จำกัด และรวดเร็ว
การประเมินผลขี้เกียจ:
ไม่มีการประเมินผลที่ไม่จำเป็น
ค่าโสหุ้ยการทำบัญชีที่การใช้งานค่า
ดังนั้นหากคุณมีการแสดงออกหลายอย่างที่ไม่เคยได้รับการประเมินขี้เกียจดีกว่า แต่ถ้าคุณไม่เคยมีการแสดงออกที่ไม่จำเป็นต้องได้รับการประเมินขี้เกียจเป็นค่าใช้จ่ายบริสุทธิ์
ตอนนี้ลองมาดูซอฟต์แวร์แห่งความเป็นจริง: ฟังก์ชั่นที่คุณเขียนไม่จำเป็นต้องประเมินข้อโต้แย้งทั้งหมด โดยเฉพาะอย่างยิ่งกับฟังก์ชั่นสั้นที่ทันสมัยที่ทำสิ่งเดียวเท่านั้นเปอร์เซ็นต์ของฟังก์ชันที่อยู่ในหมวดหมู่นี้จึงต่ำมาก ดังนั้นการประเมินผลที่ขี้เกียจจะแนะนำค่าใช้จ่ายการทำบัญชีเป็นส่วนใหญ่โดยไม่มีโอกาสที่จะบันทึกอะไรเลย
ดังนั้นการประเมินแบบเกียจคร้านก็ไม่ได้จ่ายเงินโดยเฉลี่ยการประเมินความกระตือรือร้นเป็นสิ่งที่ดีกว่าสำหรับโค้ดสมัยใหม่
ในฐานะที่เป็น @DeadMG ตั้งข้อสังเกตการประเมินผล Lazy ต้องใช้ค่าใช้จ่ายในการรักษาหนังสือ ซึ่งอาจมีราคาแพงเมื่อเทียบกับการประเมินผลความกระตือรือร้น พิจารณาข้อความนี้:
i = (243 * 414 + 6562 / 435.0 ) ^ 0.5 ** 3
การคำนวณนี้จะใช้เวลาเล็กน้อยในการคำนวณ หากฉันใช้การประเมินแบบขี้เกียจฉันต้องตรวจสอบว่าได้รับการประเมินทุกครั้งที่ใช้หรือไม่ หากสิ่งนี้อยู่ภายในลูปที่มีการใช้งานอย่างหนักมากค่าใช้จ่ายจะเพิ่มขึ้นอย่างมาก แต่ก็ไม่มีประโยชน์
ด้วยการประเมินความกระตือรือร้นและคอมไพเลอร์ที่เหมาะสมสูตรจะถูกคำนวณ ณ เวลารวบรวม เครื่องมือเพิ่มประสิทธิภาพส่วนใหญ่จะย้ายการบ้านออกจากลูปใด ๆ ที่เกิดขึ้นหากเหมาะสม
การประเมิน Lazy นั้นเหมาะสมที่สุดสำหรับการโหลดข้อมูลซึ่งจะเข้าถึงได้ไม่บ่อยนักและมีค่าใช้จ่ายในการเรียกคืนสูง ดังนั้นจึงเหมาะสมกว่าในการพิจารณาคดีขอบมากกว่าฟังก์ชั่นหลัก
โดยทั่วไปเป็นวิธีปฏิบัติที่ดีในการประเมินสิ่งต่าง ๆ ที่เข้าถึงได้บ่อยที่สุดเท่าที่จะทำได้ การประเมิน Lazy ไม่สามารถใช้ได้กับการฝึกนี้ หากคุณจะเข้าถึงบางสิ่งการประเมินที่ขี้เกียจจะทำคือเพิ่มค่าใช้จ่าย ค่าใช้จ่าย / ผลประโยชน์ของการใช้การประเมินแบบขี้เกียจลดลงเนื่องจากรายการที่เข้าถึงนั้นมีโอกาสน้อยที่จะเข้าถึง
การใช้การประเมินแบบเกียจคร้านมักจะหมายถึงการปรับให้เหมาะสมก่อนเสมอ นี่เป็นวิธีปฏิบัติที่ไม่ดีซึ่งมักส่งผลให้เกิดโค้ดซึ่งมีความซับซ้อนและมีราคาแพงกว่าซึ่งอาจเป็นกรณีนี้ น่าเสียดายที่การปรับให้เหมาะสมก่อนวัยอันควรส่งผลให้เกิดโค้ดที่ทำงานช้ากว่าโค้ดที่ง่ายกว่า จนกว่าคุณจะสามารถวัดผลของการเพิ่มประสิทธิภาพได้เป็นความคิดที่ไม่ดีที่จะเพิ่มประสิทธิภาพโค้ดของคุณ
การหลีกเลี่ยงการปรับให้เหมาะสมก่อนวัยอันควรไม่ขัดแย้งกับแนวทางการเขียนโปรแกรมที่ดี หากการปฏิบัติที่ดีไม่ได้ถูกนำไปใช้การเพิ่มประสิทธิภาพเริ่มต้นอาจประกอบด้วยการใช้วิธีการเข้ารหัสที่ดีเช่นการคำนวณการย้ายออกจากลูป
หากเราอาจต้องประเมินการแสดงออกอย่างเต็มที่เพื่อพิจารณาว่ามันคุ้มค่าการประเมินที่ขี้เกียจอาจเป็นข้อเสีย สมมติว่าเรามีรายการค่าบูลีนที่ยาวและเราต้องการทราบว่าทั้งหมดนั้นเป็นจริงหรือไม่:
[True, True, True, ... False]
เพื่อที่จะทำสิ่งนี้เราต้องดูทุกองค์ประกอบในรายการไม่ว่าจะเป็นอะไรดังนั้นจึงไม่มีความเป็นไปได้ที่จะตัดการประเมินผลอย่างขี้เกียจ เราสามารถใช้ fold เพื่อพิจารณาว่าค่าบูลีนทั้งหมดในรายการเป็นจริงหรือไม่ หากเราใช้การพับแบบถูกทางซึ่งใช้การประเมินแบบขี้เกียจเราจะไม่ได้รับประโยชน์ใด ๆ จากการประเมินแบบเกียจคร้านเพราะเราต้องดูทุกองค์ประกอบในรายการ:
foldr (&&) True [True, True, True, ... False]
> 0.27 secs
ในกรณีนี้การพับที่ด้านขวาจะช้ากว่าการพับแบบ จำกัด ทางซ้ายซึ่งไม่ได้ใช้การประเมินแบบขี้เกียจ:
foldl' (&&) True [True, True, True, ... False]
> 0.09 secs
เหตุผลก็คือการพับแบบด้านซ้ายอย่างเข้มงวดใช้การเรียกซ้ำแบบหางซึ่งหมายความว่ามันจะเก็บค่าส่งคืนและไม่ได้สร้างขึ้นและเก็บไว้ในหน่วยความจำซึ่งเป็นสายปฏิบัติการขนาดใหญ่ มันเร็วกว่าขี้เกียจมาก ๆ เพราะทั้งสองฟังก์ชั่นจะต้องดูรายการทั้งหมดและการพับขวาไม่สามารถใช้การเรียกซ้ำหางได้ ดังนั้นประเด็นคือคุณควรใช้สิ่งที่ดีที่สุดสำหรับงานในมือ