การไม่เปลี่ยนรูปแบบทำให้ประสิทธิภาพใน JavaScript แย่ลงหรือไม่?


88

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

ปฏิกิริยาเริ่มต้นของฉันคือดูเหมือนว่ามันจะไม่ดีต่อประสิทธิภาพ

แต่แล้วเหมือนห้องสมุดImmutable.jsและRedux.jsนั้นเขียนขึ้นโดยคนที่ฉลาดกว่าฉันและดูเหมือนจะมีความกังวลอย่างมากสำหรับการทำงานจึงทำให้ผมสงสัยว่าความเข้าใจของฉันของขยะ (และผลกระทบต่อประสิทธิภาพของมัน) เป็นสิ่งที่ผิด

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


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

8
จากประสบการณ์ของฉันประสิทธิภาพเป็นเพียงข้อกังวลที่ถูกต้องสำหรับสองสถานการณ์ - หนึ่งเมื่อมีการดำเนินการ 30+ ครั้งในหนึ่งวินาทีและสอง - เมื่อเอฟเฟกต์เพิ่มขึ้นในการดำเนินการแต่ละครั้ง (Windows XP เมื่อพบข้อผิดพลาดO(pow(n, 2))สำหรับการอัปเดตทุกครั้งในประวัติ ) โค้ดอื่น ๆ ส่วนใหญ่เป็นการตอบสนองทันทีต่อเหตุการณ์ การคลิกคำขอ API หรือสิ่งที่คล้ายกันและตราบใดที่เวลาดำเนินการเป็นค่าคงที่การล้างวัตถุจำนวนเท่าใดก็ไม่สำคัญ
Katana314

4
นอกจากนี้ให้พิจารณาว่ามีการใช้งานที่มีประสิทธิภาพของโครงสร้างข้อมูลที่ไม่เปลี่ยนรูป บางทีสิ่งเหล่านี้อาจไม่ได้มีประสิทธิภาพเท่าที่เปลี่ยนแปลงได้ แต่อาจมีประสิทธิภาพมากกว่าการใช้งานแบบไร้เดียงสา ดูโครงสร้างข้อมูลที่ใช้งานได้อย่างแท้จริงโดย Chris Okasaki
Giorgio

1
@ Katana314: 30+ ครั้งสำหรับฉันยังคงไม่เพียงพอที่จะแสดงให้เห็นถึงความกังวลเกี่ยวกับการแสดง ฉันพอร์ตตัวจำลองซีพียูขนาดเล็กฉันเขียนไปที่ node.js และโหนดดำเนินการ CPU เสมือนที่ประมาณ 20MHz (นั่นคือ 20 ล้านครั้งต่อวินาที) ดังนั้นฉันจึงกังวลเกี่ยวกับการแสดงถ้าฉันทำอะไรมากกว่า 1,000 ครั้งต่อวินาที (ถึงตอนนั้นฉันก็ไม่ต้องกังวลจริงๆจนกว่าฉันจะทำ 1000000 ครั้งต่อวินาทีเพราะฉันรู้ว่าฉันสามารถทำได้มากกว่า 10 รายการในครั้งเดียว) .
slebetman

2
@RobertHarvey "ความสามารถในการใช้งานโดยตัวมันเองมีเพียงประโยชน์ด้านประสิทธิภาพในแง่ที่ทำให้การเขียนโค้ดแบบมัลติเธรดง่ายขึ้น" นั่นไม่เป็นความจริงอย่างสิ้นเชิงการเปลี่ยนแปลงไม่ได้ช่วยให้สามารถแบ่งปันได้อย่างแพร่หลายโดยไม่มีผลกระทบที่แท้จริง ซึ่งไม่ปลอดภัยมากในสภาพแวดล้อมที่ไม่แน่นอน นี่ทำให้คุณคิดว่าการO(1)แบ่งอาร์เรย์และO(log n)แทรกเข้าไปในต้นไม้ไบนารีในขณะที่ยังสามารถใช้ต้นเก่าได้อย่างอิสระและอีกตัวอย่างหนึ่งคือการtailsใช้ก้อยทั้งหมดของรายการtails [1, 2] = [[1, 2], [2], []]ใช้O(n)เวลาและพื้นที่ แต่อยู่O(n^2)ในองค์ประกอบนับ
semicolon

คำตอบ:


59

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

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

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

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

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

กฎง่ายๆสำหรับฉันในสถานการณ์นี้คือ:

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

สำหรับสถานการณ์ระหว่างสองขั้วนี้ให้ใช้วิจารณญาณของคุณ แต่ YMMV


8
That will leave a lot more garbage than in your case.และเพื่อให้เรื่องแย่ลงรันไทม์ของคุณอาจไม่สามารถตรวจจับการทำซ้ำได้อย่างไม่มีจุดหมายและดังนั้น (ซึ่งแตกต่างจากวัตถุที่ไม่เปลี่ยนรูปที่หมดอายุซึ่งไม่มีใครใช้) มันจะไม่ได้รับสิทธิ์ในการรวบรวม
Jacob Raihle

37

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

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

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

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


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

1
"ท้อใจในการสร้างตัวแปร" นั่นไม่ถูกต้องสำหรับภาษาที่พฤติกรรมเริ่มต้นถูกคัดลอกในการสร้างที่ได้รับมอบหมาย / โดยนัย? ใน JavaScript ตัวแปรเป็นเพียงตัวระบุ ไม่ใช่วัตถุในสิทธิ์ของตนเอง มันยังคงใช้พื้นที่บางแห่ง แต่ไม่สำคัญ (โดยเฉพาะอย่างยิ่งการใช้งานจาวาสคริปต์สำหรับความรู้ของฉันยังคงใช้สแต็กสำหรับการเรียกใช้ฟังก์ชั่นความหมายเว้นแต่ว่าคุณมีการเรียกซ้ำจำนวนมาก ตัวแปรชั่วคราว) การเปลี่ยนไม่ได้ไม่มีความสัมพันธ์กับด้านนั้น
JAB

33

ผู้มาสายในการตอบคำถามและคำตอบที่ยอดเยี่ยมนี้แล้ว แต่ฉันต้องการที่จะก้าวก่ายในฐานะชาวต่างชาติที่เคยมองสิ่งต่าง ๆ จากจุดยืนระดับล่างของบิตและไบต์ในหน่วยความจำ

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

ช้า / เร็วขึ้น

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

maybeแต่คำตอบที่ปฏิบัติคือ ประสิทธิภาพการทำงานยังคงเป็นตัวชี้วัดประสิทธิภาพการผลิตส่วนใหญ่ใน codebase ที่ไม่สำคัญ โดยทั่วไปแล้วเราไม่พบว่ามีโค้ดฐานที่น่ากลัวที่จะรักษาสภาพการแข่งขันที่มีประสิทธิภาพมากที่สุดแม้ว่าเราจะไม่สนใจข้อบกพร่องก็ตาม ประสิทธิภาพมักเป็นหน้าที่ของความสง่างามและความเรียบง่าย จุดสูงสุดของการเพิ่มประสิทธิภาพขนาดเล็กอาจขัดแย้งกันบ้าง แต่ส่วนใหญ่จะถูกสงวนไว้สำหรับส่วนที่เล็กที่สุดและสำคัญที่สุดของรหัส

การแปลงบิตและไบต์ที่ไม่เปลี่ยนรูป

มาจากจุดยืนระดับต่ำหากเรามีแนวคิดเอ็กซ์เรย์เช่นobjectsและstringsอื่น ๆ หัวใจของมันคือบิตและไบต์ในหน่วยความจำรูปแบบต่าง ๆ ที่มีลักษณะความเร็ว / ขนาดต่างกัน (ความเร็วและขนาดของฮาร์ดแวร์หน่วยความจำโดยทั่วไป พิเศษร่วมกัน)

ป้อนคำอธิบายรูปภาพที่นี่

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

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

ป้อนคำอธิบายรูปภาพที่นี่

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

อย่างไรก็ตามมีวิธีการลดขนาดนี้อย่างไรก็ตามการใช้พูลสำรองของหน่วยความจำที่จัดสรรล่วงหน้าได้ถูกแตะไปแล้ว

มวลรวมขนาดใหญ่

อีกประเด็นทางความคิดที่เกิดขึ้นจากมุมมองระดับสูงขึ้นเล็กน้อยคือการทำสำเนาที่ไม่จำเป็นของมวลรวมที่มีขนาดใหญ่จริงๆ

เพื่อหลีกเลี่ยงไดอะแกรมที่ซับซ้อนเกินไปลองนึกภาพว่าบล็อกหน่วยความจำง่าย ๆ นี้มีราคาแพง (อาจเป็นอักขระ UTF-32 บนฮาร์ดแวร์ที่ จำกัด อย่างไม่น่าเชื่อ)

ป้อนคำอธิบายรูปภาพที่นี่

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

ป้อนคำอธิบายรูปภาพที่นี่

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

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

วิธีหนึ่งในการลดงานคัดลอกซ้ำซ้อนนี้คือการทำให้บล็อกหน่วยความจำเหล่านี้เป็นชุดของพอยน์เตอร์ (หรือการอ้างอิง) กับตัวอักษรเช่น:

ขอโทษฉันไม่ได้ตระหนักว่าเราไม่จำเป็นต้องสร้างความLแปลกใหม่ในขณะที่ทำแผนภาพ

สีน้ำเงินหมายถึงข้อมูลที่ถูกคัดลอกตื้น

ป้อนคำอธิบายรูปภาพที่นี่

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

แม้ว่าเราจะระมัดระวังในการจัดสรรอักขระเหล่านี้อย่างต่อเนื่องบอกว่าเครื่องสามารถโหลด 8 อักขระและ 8 พอยน์เตอร์เป็นอักขระในบรรทัดแคช เราจะโหลดหน่วยความจำแบบนี้เพื่อสำรวจสตริงใหม่:

ป้อนคำอธิบายรูปภาพที่นี่

ในกรณีนี้เราจะต้องโหลดแคชของหน่วยความจำที่ต่อเนื่องกัน 7 เส้นเพื่อโหลดสตริงนี้เมื่อเราต้องการเพียง 3

รับรู้ข้อมูล

เพื่อบรรเทาปัญหาดังกล่าวข้างต้นเราสามารถใช้กลยุทธ์พื้นฐานเดียวกัน แต่ในระดับที่หยาบกว่า 8 ตัวอักษรเช่น

ป้อนคำอธิบายรูปภาพที่นี่

ผลลัพธ์ต้องมีแคช 4 บรรทัดที่มีมูลค่าของข้อมูล (1 สำหรับ 3 พอยน์เตอร์และ 3 สำหรับตัวอักษร) เพื่อโหลดผ่านสายอักขระนี้ซึ่งสั้นเพียง 1 ของทฤษฎีที่เหมาะสมที่สุด

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

ความเร็ว

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

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

การเก็บข้อมูลทั้งเก่าและใหม่

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

ตัวอย่าง: เลิกทำระบบ

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

อินเทอร์เฟซระดับสูง

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

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

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

โครงสร้างข้อมูล

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

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

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

ประสิทธิภาพ

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

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

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


11

ImmutableJS นั้นค่อนข้างมีประสิทธิภาพจริงๆ หากเรานำตัวอย่าง:

var x = {
    Foo: 1,
    Bar: { Baz: 2 }
    Qux: { AnotherVal: 3 }
}

หากวัตถุข้างต้นไม่สามารถเปลี่ยนแปลงได้คุณต้องแก้ไขค่าของคุณสมบัติ 'Baz' ที่คุณจะได้รับคือ:

var y = x.setIn('/Bar/Baz', 3);
y !== x; // Different object instance
y.Bar !== x.Bar // As the Baz property was changed, the Bar object is a diff instance
y.Qux === y.Qux // Qux is the same object instance

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

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


4

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

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


4

ในการเพิ่มคำถามนี้ (ตอบแล้วยอดเยี่ยม):

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


แต่คำตอบที่ยาวไม่ได้จริงๆ

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

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

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

มีข้อยกเว้นโครงสร้างข้อมูลที่สะดุดตาที่สุดตามที่ระบุไว้ข้างต้น ฉันไม่ค่อยเจอสถานการณ์ที่ฉันต้องการแก้ไขแผนที่หลังจากการสร้าง (แม้ว่าเป็นที่ยอมรับว่าฉันกำลังพูดถึงแผนที่หลอกตัวอักษร - วัตถุ - ตัวอักษรมากกว่าแผนที่ ES6) เหมือนกันสำหรับอาร์เรย์ เมื่อคุณจัดการกับโครงสร้างข้อมูลที่ใหญ่กว่าความไม่แน่นอนอาจจะจ่ายออกไป โปรดจำไว้ว่าทุกวัตถุใน JavaScript ถูกส่งผ่านเป็นการอ้างอิงมากกว่าค่า


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


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

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