หมายเหตุของนักแปล: ฉันเป็นครั้งแรกที่ฉันแปลภาษาต่างประเทศและคำพูดของฉันไม่ชัดเจนเล็กน้อย แต่ฉันพยายามอย่างเต็มที่เพื่อแสดงความตั้งใจดั้งเดิมของผู้เขียนและไม่ได้ขัดเกลามากนัก ยินดีต้อนรับการวิจารณ์และการแก้ไข นอกจากนี้บทความนี้มีความยาวและมีข้อมูลจำนวนมากซึ่งอาจย่อยได้ยาก กรุณาฝากข้อความเพื่อหารือเกี่ยวกับรายละเอียด บทความนี้ส่วนใหญ่มุ่งเน้นไปที่การเพิ่มประสิทธิภาพประสิทธิภาพของ V8 และเนื้อหาบางส่วนไม่สามารถใช้ได้กับเครื่องยนต์ JS ทั้งหมด ในที่สุดโปรดระบุแหล่งที่มาเมื่อพิมพ์ซ้ำ :)
-
เครื่องยนต์จาวาสคริปต์จำนวนมากเช่นเครื่องยนต์ V8 ของ Google (ใช้โดย Chrome และ Node) ได้รับการออกแบบมาโดยเฉพาะสำหรับแอปพลิเคชัน JavaScript ขนาดใหญ่ที่ต้องการการดำเนินการอย่างรวดเร็ว หากคุณเป็นนักพัฒนาและมีความกังวลเกี่ยวกับการใช้หน่วยความจำและประสิทธิภาพของหน้าคุณควรเข้าใจว่าเอ็นจิ้น JavaScript ในเบราว์เซอร์ของคุณทำงานอย่างไร ไม่ว่าจะเป็น V8, Spidermonkey (Firefox) Carakan (Opera), Chakra (เช่น) หรือเครื่องยนต์อื่น ๆ การทำเช่นนั้นสามารถช่วยให้คุณ เพิ่มประสิทธิภาพแอปพลิเคชันของคุณได้ดีขึ้น นี่ไม่ได้หมายความว่าคุณควรเพิ่มประสิทธิภาพโดยเฉพาะสำหรับเบราว์เซอร์หรือเครื่องยนต์บางอย่างและไม่เคยทำเช่นนี้
อย่างไรก็ตามคุณควรถามตัวเองสองสามคำถาม:
เว็บไซต์ ที่โหลด อย่างรวดเร็ว เป็นเหมือนรถสปอร์ตที่รวดเร็วซึ่งต้องการชิ้นส่วนที่ปรับแต่งเป็นพิเศษ แหล่งที่มาของภาพ: Dhybridcars
มีข้อผิดพลาดทั่วไปเมื่อเขียนรหัสประสิทธิภาพสูงและในบทความนี้เราจะแสดงวิธีการเขียนที่พิสูจน์แล้วและดีกว่า
หากคุณไม่มีความเข้าใจอย่างลึกซึ้งเกี่ยวกับเครื่องยนต์ JS ไม่มีปัญหาในการพัฒนาเว็บแอปพลิเคชั่นขนาดใหญ่เช่นเดียวกับคนที่สามารถขับได้เพียงแค่เห็นฮูด แต่ไม่ใช่เครื่องยนต์ภายในฝากระโปรง เนื่องจาก Chrome เป็นตัวเลือกแรกของเบราว์เซอร์ของฉันมาพูดคุยเกี่ยวกับเอ็นจิ้น JavaScript ของมัน V8 ประกอบด้วยชิ้นส่วนหลักต่อไปนี้:
การรวบรวมขยะเป็น รูปแบบหนึ่งของการจัดการหน่วยความจำ ซึ่งจริง ๆ แล้วเป็นแนวคิดของนักสะสมพยายามที่จะรีไซเคิลหน่วยความจำที่ครอบครองโดยวัตถุที่ไม่ได้ใช้อีกต่อไป ในภาษาคอลเลกชันขยะเช่น JavaScript วัตถุที่ยังคงอ้างอิงในแอปพลิเคชันจะไม่ถูกล้าง
การกำจัดการอ้างอิงวัตถุด้วยตนเองไม่จำเป็นในกรณีส่วนใหญ่ ทุกอย่างจะทำงานได้ดีโดยเพียงแค่วางตัวแปรที่จำเป็น (ตามหลักการตามขอบเขตในพื้นที่มากที่สุดเท่าที่จะเป็นไปได้เช่นฟังก์ชั่นที่ใช้แทนชั้นนอกของฟังก์ชั่น)
Garbage Collector พยายามรีไซเคิลหน่วยความจำ แหล่งที่มาของภาพ: Valtteri Mäki
ใน JavaScript มันเป็นไปไม่ได้ที่จะบังคับให้เก็บขยะ คุณไม่ควรทำเช่นนี้เพราะกระบวนการรวบรวมขยะถูกควบคุมโดยรันไทม์และรู้ว่าเวลาที่ดีที่สุดในการทำความสะอาดคืออะไร
มีการอภิปรายมากมายเกี่ยวกับการรีไซเคิลหน่วยความจำ JavaScript บนอินเทอร์เน็ตเกี่ยวกับการลบคำหลัก แม้ว่าจะสามารถใช้ในการลบแอตทริบิวต์ (ปุ่ม) ในวัตถุ (แผนที่) นักพัฒนาบางคนเชื่อว่าสามารถใช้บังคับ "dereferences" ขอแนะนำให้หลีกเลี่ยงการใช้ลบทุกครั้งที่ทำได้ ในตัวอย่างด้านล่าง delete ox 的弊大于利,因为它改变了o的隐藏类,并使它成为一个"慢对象"。
var o = {x: 1}; ลบวัว; // true ox; // ไม่ได้กำหนดคุณจะพบการลบการอ้างอิงในห้องสมุด JS ยอดนิยมได้อย่างง่ายดาย - นี่คือจุดมุ่งหมายทางภาษา ควรสังเกตที่นี่เพื่อหลีกเลี่ยงการปรับเปลี่ยนโครงสร้างของวัตถุ "ร้อน" ที่รันไทม์ เครื่องยนต์ JavaScript สามารถตรวจจับวัตถุ "ร้อน" ดังกล่าวและพยายามปรับให้เหมาะสม หากโครงสร้างของวัตถุไม่เปลี่ยนแปลงอย่างมีนัยสำคัญในช่วงวงจรชีวิตเครื่องยนต์จะง่ายขึ้นสำหรับการเพิ่มประสิทธิภาพของวัตถุและการดำเนินการลบจะทำให้เกิดการเปลี่ยนแปลงโครงสร้างที่ใหญ่ขึ้นนี้ซึ่งไม่เอื้อต่อการเพิ่มประสิทธิภาพของเครื่องยนต์
นอกจากนี้ยังมีความเข้าใจผิดเกี่ยวกับวิธีการทำงานของ NULL การตั้งค่าการอ้างอิงวัตถุเป็น null ไม่ได้ทำให้วัตถุ "ว่าง" มันเพิ่งตั้งค่าการอ้างอิงเป็นว่าง การใช้ ox = null ดีกว่าการใช้ลบ แต่อาจไม่จำเป็น
var o = {x: 1}; o = null; o; // nullo.x // typeerrorหากการอ้างอิงนี้เป็นการอ้างอิงล่าสุดไปยังวัตถุปัจจุบันวัตถุจะถูกเก็บรวบรวมขยะ หากการอ้างอิงนี้ไม่ใช่การอ้างอิงล่าสุดไปยังวัตถุปัจจุบันวัตถุสามารถเข้าถึงได้และจะไม่ถูกเก็บรวบรวมขยะ
ควรสังเกตว่าตัวแปรระดับโลกไม่ได้ถูกทำความสะอาดโดยนักสะสมขยะในช่วงวงจรชีวิตของหน้า ไม่ว่าหน้าจะเปิดนานแค่ไหนตัวแปรในขอบเขตของวัตถุส่วนกลางจะมีอยู่เสมอเมื่อ JavaScript ทำงาน
var myglobalnamespace = {};วัตถุทั่วโลกจะได้รับการทำความสะอาดเมื่อรีเฟรชหน้านำทางไปยังหน้าอื่นปิดแท็บหรือออกจากเบราว์เซอร์ ตัวแปรในขอบเขตการทำงานจะถูกทำความสะอาดเมื่ออยู่นอกขอบเขตนั่นคือเมื่อออกจากฟังก์ชันไม่มีการอ้างอิงและตัวแปรดังกล่าวจะถูกทำความสะอาด
เพื่อให้นักสะสมขยะเก็บรวบรวมวัตถุให้ได้มากที่สุดเท่าที่จะเป็นไปได้ อย่าถือวัตถุที่ไม่ได้ใช้อีกต่อไป นี่คือบางสิ่งที่ต้องจำไว้:
ต่อไปเรามาพูดถึงฟังก์ชั่น ดังที่เราได้กล่าวไปแล้วคอลเลกชันขยะทำงานโดยการรีไซเคิลบล็อกของหน่วยความจำ (วัตถุ) ที่ไม่สามารถเข้าถึงได้อีกต่อไป เพื่อแสดงให้เห็นถึงสิ่งนี้ที่ดีกว่านี่คือตัวอย่างบางส่วน
function foo () {var bar = new LargeObject (); bar.somecall ();}เมื่อฟูกลับมาวัตถุที่ชี้ไปที่บาร์จะถูกรีไซเคิลโดยอัตโนมัติโดยนักสะสมขยะเพราะไม่มีการอ้างอิงที่มีอยู่
เปรียบเทียบ:
function foo () {var bar = new LargeObject (); bar.somecall (); Bar return;} // ที่ไหนสักแห่ง elsevar b = foo ();ตอนนี้เรามีการอ้างอิงที่ชี้ไปที่วัตถุแท่งเพื่อให้วัฏจักรชีวิตของวัตถุแท่งต่อจากการโทรไปยัง Foo จนกว่าผู้โทรจะระบุตัวแปรอื่น B (หรือ B อยู่นอกขอบเขต)
เมื่อคุณเห็นฟังก์ชั่นให้ส่งคืนฟังก์ชั่นภายในซึ่งจะออกจากการเข้าถึงขอบเขตแม้หลังจากฟังก์ชั่นภายนอกถูกดำเนินการ นี่คือการปิดพื้นฐาน - การแสดงออกของตัวแปรที่สามารถตั้งค่าในบริบทเฉพาะ ตัวอย่างเช่น:
ฟังก์ชั่นผลรวม (x) {ฟังก์ชัน sumit (y) {return x + y; - return sumit;} // usagevar suma = sum (4); var sumb = suma (3); console.log (sumb); // ส่งคืน 7วัตถุฟังก์ชั่น (sumit) ที่สร้างขึ้นในบริบทการเรียกผลรวมไม่สามารถรีไซเคิลได้ มันถูกอ้างอิงโดยตัวแปรทั่วโลก (SUMA) และสามารถเรียกผ่าน suma (n)
ลองมาดูตัวอย่างอื่นกันเถอะเราจะเข้าถึงตัวแปรที่ใหญ่ที่สุดได้ที่ไหน
var a = function () {var largestr = new Array (10,00000) .Join ('x'); ฟังก์ชั่น return () {return larsestr; -ใช่เราสามารถเข้าถึง LARSTERR ผ่าน () ดังนั้นจึงไม่ได้รีไซเคิล แล้วสิ่งต่อไปนี้ล่ะ?
var a = function () {var smallstr = 'x'; var largestr = อาร์เรย์ใหม่ (10000000) .Join ('x'); ฟังก์ชั่น return (n) {return smallstr; -เราไม่สามารถเข้าถึง LARSTRR ได้อีกต่อไปมันเป็นตัวเลือกการรวบรวมขยะแล้ว [หมายเหตุของนักแปล: เนื่องจาก LarStR ไม่มีการอ้างอิงภายนอกอีกต่อไป]
หนึ่งในสถานที่ที่เลวร้ายที่สุดในการรั่วไหลของหน่วยความจำอยู่ในลูปหรือใน settimeout ()/setInterval () แต่นี่เป็นเรื่องธรรมดา คิดเกี่ยวกับตัวอย่างต่อไปนี้:
var myobj = {callMemaybe: function () {var myref = this; var val = settimeout (function () {console.log ('เวลาหมดแล้ว!'); myref.callmeMaybe ();}, 1,000); - ถ้าเราเรียกใช้ myobj.callmemaybe (); ในการเริ่มจับเวลาเราจะเห็นว่าคอนโซลพิมพ์ "เวลาหมดแล้ว!" ทุกวินาที หาก myObj = null,定时器依旧处于激活状态。为了能够持续执行,闭包将myObj传递给setTimeout,这样myObj是无法被回收的。相反,它引用到myObj的因为它捕获了myRef。这跟我们为了保持引用将闭包传给其他的函数是一样的。
นอกจากนี้ยังเป็นสิ่งที่ควรค่าแก่การจดจำว่าการอ้างอิงในการโทร SetTimeOut/SetInterval (เช่นฟังก์ชั่น) จะต้องดำเนินการและเสร็จสิ้นก่อนที่พวกเขาจะสามารถเก็บขยะได้
อย่าปรับรหัสให้เหมาะสมจนกว่าคุณจะต้องการมันจริงๆ ตอนนี้คุณมักจะเห็นเกณฑ์มาตรฐานบางอย่างที่แสดงว่า N ได้รับการปรับให้เหมาะสมใน V8 มากกว่า M แต่ถ้าคุณทดสอบในรหัสโมดูลหรือแอปพลิเคชันคุณจะพบว่าการเพิ่มประสิทธิภาพเหล่านี้มีขนาดเล็กกว่าที่คุณคาดไว้มาก
เป็นการดีกว่าที่จะไม่ทำอะไรมากไปกว่าการทำมากเกินไป แหล่งที่มาของภาพ: Tim Sheerman-Chase
ตัวอย่างเช่นเราต้องการสร้างโมดูลดังกล่าว:
มีหลายปัจจัยที่แตกต่างกันในปัญหานี้แม้ว่ามันจะง่ายต่อการแก้ปัญหา เราจะจัดเก็บข้อมูลได้อย่างไรเราจะวาดตารางอย่างมีประสิทธิภาพและผนวกเข้ากับ DOM ได้อย่างไรและเราจะจัดการกับกิจกรรมบนตารางได้อย่างไร
วิธีการเริ่มต้น (ไร้เดียงสา) ในการเผชิญกับปัญหาเหล่านี้คือการใช้วัตถุเพื่อจัดเก็บข้อมูลและนำไปใช้ในอาร์เรย์ใช้ jQuery เพื่อสำรวจข้อมูลเพื่อวาดตารางและต่อท้ายกับ DOM และในที่สุดก็ใช้เหตุการณ์ที่มีผลผูกพันกับพฤติกรรมการคลิกที่เราคาดหวัง
หมายเหตุ: นี่ไม่ใช่สิ่งที่คุณควรทำ
var modulea = function () {return {data: dataarrayObject, init: function () {this.addtable (); this.addevents (); }, addTable: function () {สำหรับ (var i = 0; i <rows; i ++) {$ tr = $ ('<tr> </tr>'); สำหรับ (var j = 0; j <this.data.length; j ++) {$ tr.append ('<td>' + this.data [j] ['id'] + '</td>'); } $ tr.appendto ($ tbody); }}, addEvents: function () {$ ('Table TD'). on ('คลิก', function () {$ (นี่) .togGleClass ('active');}); -รหัสนี้เพียงแค่เสร็จสิ้นงาน
แต่ในกรณีนี้ข้อมูลที่เราสำรวจเป็นเพียงรหัสคุณสมบัติตัวเลขที่ควรเก็บไว้ในอาร์เรย์ ที่น่าสนใจคือการใช้ DocumentFragment และวิธี DOM ในท้องถิ่นโดยตรงมากกว่าการสร้างตารางโดยใช้ jQuery (ด้วยวิธีนี้) และแน่นอนเหตุการณ์พร็อกซีมีประสิทธิภาพสูงกว่าการผูกแต่ละ TD เพียงอย่างเดียว
โปรดทราบว่าถึงแม้ว่า jQuery จะใช้ DocumentFragment ภายในในตัวอย่างของเราการเรียกรหัสต่อท้ายภายในลูปและการโทรเหล่านี้เกี่ยวข้องกับความรู้เล็ก ๆ น้อย ๆ อื่น ๆ ดังนั้นเอฟเฟกต์การเพิ่มประสิทธิภาพที่นี่จึงไม่ดีมาก หวังว่านี่จะไม่ใช่จุดปวด แต่ต้องแน่ใจว่าได้ทำเกณฑ์มาตรฐานเพื่อให้แน่ใจว่ารหัสของคุณโอเค
สำหรับตัวอย่างของเราแนวทางปฏิบัติข้างต้นนำการปรับปรุงประสิทธิภาพ (ต้องการ) พร็อกซิงอีเวนต์เป็นการปรับปรุงการผูกมัดอย่างง่ายและเอกสารเสริมที่เป็นตัวเลือกยังช่วยได้เช่นกัน
var moduled = function () {return {data: dataarray, init: function () {this.addtable (); this.addevents (); }, addTable: function () {var td, tr; var fragment = document.createdocumentFragment (); var fragment2 = document.createdocumentFragment (); สำหรับ (var i = 0; i <rows; i ++) {tr = document.createElement ('tr'); สำหรับ (var j = 0; j <this.data.length; j ++) {td = document.createElement ('td'); td.AppendChild (document.createtextNode (this.data [j])); Frag2.AppendChild (TD); } tr.appendchild (frag2); Frag.AppendChild (TR); } tbody.AppendChild (frag); }, addEvents: function () {$ ('ตาราง'). on ('คลิก', 'td', function () {$ (นี่) .togGleclass ('active');}); -มาดูวิธีอื่น ๆ เพื่อปรับปรุงประสิทธิภาพ คุณอาจอ่านว่าการใช้โหมดต้นแบบดีกว่าโหมดโมดูลหรือคุณเคยได้ยินว่าการใช้เฟรมเวิร์ก JS เทมเพลตทำงานได้ดีขึ้น บางครั้งสิ่งนี้เป็นจริง แต่พวกเขาจะใช้เพื่อให้รหัสอ่านได้มากขึ้น โดยวิธีการที่มี precompilation! มาดูกันว่ามันทำงานอย่างไรในทางปฏิบัติ?
moduleg = function () {}; moduleg.prototype.data = dataarray; moduleg.prototype.init = function () {this.addtable (); this.addevents ();}; moduleg.prototype.addtable = function () {var template = _.template ($ ('#template'). text ()); var html = เทมเพลต ({'data': this.data}); $ tbody.append (html);}; moduleg.prototype.addevents = function () {$ ('table'). on ('คลิก', 'td', ฟังก์ชัน () {$ (นี่) .toggleclass ('active');ปรากฎว่าการปรับปรุงประสิทธิภาพที่เกิดขึ้นในสถานการณ์นี้เล็กน้อย การเลือกเทมเพลตและต้นแบบไม่ได้มีอะไรเพิ่มเติม กล่าวคือประสิทธิภาพไม่ใช่เหตุผลที่นักพัฒนาใช้พวกเขาและความสามารถในการอ่านรูปแบบการสืบทอดและการบำรุงรักษาที่นำมาสู่รหัสเป็นเหตุผลที่แท้จริง
ปัญหาที่ซับซ้อนมากขึ้นรวมถึงการวาดภาพบนผืนผ้าใบอย่างมีประสิทธิภาพและจัดการข้อมูลพิกเซลที่มีหรือไม่มีอาร์เรย์ของประเภท
ก่อนที่จะใช้วิธีการบางอย่างสำหรับแอปพลิเคชันของคุณเองอย่าลืมเรียนรู้เพิ่มเติมเกี่ยวกับการเปรียบเทียบการแก้ปัญหาเหล่านี้ บางทีบางคนยังคงจำการถ่ายภาพและส่วนขยายที่ตามมาของเทมเพลต JS คุณต้องคิดว่าการเปรียบเทียบไม่มีอยู่ในแอปพลิเคชันเสมือนที่คุณไม่สามารถมองเห็นได้ แต่ควรทดสอบการเพิ่มประสิทธิภาพที่นำมาใช้โดยรหัสจริงของคุณ
จุดเพิ่มประสิทธิภาพของเครื่องยนต์ V8 แต่ละเครื่องจะถูกนำเสนอโดยละเอียดนอกขอบเขตของบทความนี้ แน่นอนว่ามีเคล็ดลับมากมายที่ควรกล่าวถึงที่นี่ โปรดจำไว้ว่าเคล็ดลับเหล่านี้และคุณสามารถลดรหัสที่มีประสิทธิภาพไม่ดี
ฟังก์ชั่นเพิ่ม (x, y) {return x+y;} เพิ่ม (1, 2); เพิ่ม ('A', 'B'); เพิ่ม (my_custom_object, ไม่ได้กำหนด);สำหรับเนื้อหาเพิ่มเติมโปรดดูการแบ่งปันของ Daniel Clifford บน Google I/O ทำลายขีด จำกัด ความเร็ว JavaScript ด้วย V8 การเพิ่มประสิทธิภาพสำหรับ V8 - ซีรีส์ก็คุ้มค่าที่จะอ่าน
มีความแตกต่างหลักเพียงอย่างเดียวระหว่างวัตถุและอาร์เรย์ใน JavaScript นั่นคือคุณสมบัติความยาวเวทมนตร์ของอาร์เรย์ หากคุณรักษาคุณสมบัตินี้ด้วยตัวคุณเองวัตถุและอาร์เรย์ใน V8 นั้นเร็วเท่ากับในอาร์เรย์
การโคลนนิ่งวัตถุเป็นปัญหาที่พบบ่อยสำหรับนักพัฒนาแอปพลิเคชัน ในขณะที่มาตรฐานต่าง ๆ สามารถพิสูจน์ได้ว่า V8 จัดการปัญหานี้ได้ดีระวัง การคัดลอกสิ่งที่ยิ่งใหญ่มักจะช้าลง - อย่าทำอย่างนั้น สำหรับ .. ในลูปใน JS นั้นไม่ดีโดยเฉพาะอย่างยิ่งเพราะมีข้อกำหนดปีศาจและอาจไม่เร็วกว่าวัตถุใด ๆ ในเครื่องยนต์ใด ๆ
เมื่อคุณแน่ใจว่าจะคัดลอกวัตถุบนเส้นทางรหัสประสิทธิภาพที่สำคัญให้ใช้อาร์เรย์หรือฟังก์ชั่น "ตัวสร้างสำเนา" ที่กำหนดเองเพื่อคัดลอกแต่ละคุณสมบัติอย่างชัดเจน นี่อาจเป็นวิธีที่เร็วที่สุด:
ฟังก์ชั่นโคลน (ต้นฉบับ) {this.foo = Original.foo; this.bar = Original.bar;} var copy = new clone (ต้นฉบับ);ฟังก์ชั่นการแคชเมื่อใช้โหมดโมดูลอาจนำไปสู่การปรับปรุงประสิทธิภาพ ดูตัวอย่างด้านล่างเพราะมันจะสร้างสำเนาใหม่ของฟังก์ชั่นสมาชิกการเปลี่ยนแปลงที่คุณเห็นอาจช้าลง
โปรดทราบว่าการใช้วิธีนี้ดีกว่าอย่างชัดเจนไม่ใช่แค่พึ่งพาโหมดต้นแบบ (ยืนยันโดยการทดสอบ JSPERF)
การปรับปรุงประสิทธิภาพเมื่อใช้โหมดโมดูลหรือโหมดต้นแบบ
นี่คือการทดสอบการเปรียบเทียบประสิทธิภาพของโหมดต้นแบบและโหมดโมดูล:
// รูปแบบต้นแบบ klass1 = function () {} klass1.prototype.foo = function () {log ('foo'); } klass1.prototype.bar = function () {log ('bar'); } // โมดูลรูปแบบ klass2 = function () {var foo = function () {log ('foo'); }, bar = function () {log ('bar'); - return {foo: foo, bar: bar}} // รูปแบบโมดูลที่มีฟังก์ชั่นแคช var foofunction = function () {log ('foo'); - var barfunction = function () {log ('bar'); - klass3 = function () {return {foo: foofunction, bar: barfunction}} // การทดสอบซ้ำ // ต้นแบบ var i = 1000, objs = []; ในขณะที่ (i--) {var o = new klass1 () objs.push (ใหม่ klass1 ()); O.Bar; O.foo; } // โมดูลรูปแบบ var i = 1000, objs = []; ในขณะที่ (i--) {var o = new klass1 () objs.push (ใหม่ klass1 ()); O.Bar; O.foo; } // โมดูลรูปแบบ var i = 1000, objs = []; ในขณะที่ (i--) {var o = klass2 () objs.push (klass2 ()); O.Bar; O.foo; } // รูปแบบโมดูลที่มีฟังก์ชั่นแคช var i = 1000, objs = []; ในขณะที่ (i--) {var o = klass3 () objs.push (klass3 ()); O.Bar; O.foo; } // ดูการทดสอบสำหรับรายละเอียดทั้งหมดถัดไปพูดคุยเกี่ยวกับเทคนิคที่เกี่ยวข้องกับอาร์เรย์ โดยทั่วไป อย่าลบองค์ประกอบอาเรย์ ซึ่งจะทำให้อาเรย์เปลี่ยนไปเป็นตัวแทนภายในที่ช้าลง เมื่อดัชนีกระจัดกระจาย V8 จะเปลี่ยนองค์ประกอบเป็นรูปแบบพจนานุกรมที่ช้าลง
ตัวอักษรอาร์เรย์มีประโยชน์มากและสามารถบอกใบ้ถึงขนาดและประเภทของอาร์เรย์ VM มันมักจะใช้ในอาร์เรย์ที่มีขนาดเล็ก
// ที่นี่ v8 สามารถเห็นได้ว่าคุณต้องการอาร์เรย์ 4 องค์ประกอบที่มีตัวเลข: var a = [1, 2, 3, 4]; // อย่าทำสิ่งนี้: a = []; // ที่นี่ v8 ไม่รู้อะไรเลยเกี่ยวกับอาร์เรย์ (var i = 1; i <= 4; i ++) {a.push (i);}มันไม่ได้เป็นความคิดที่ดีในการจัดเก็บข้อมูลประเภทผสม (เช่นตัวเลข, สตริง, ไม่ได้กำหนด, จริง/เท็จ) ในอาร์เรย์ ตัวอย่างเช่น var arr = [1,“ 1”, ไม่ได้กำหนด, จริง,“ จริง”]
การทดสอบประสิทธิภาพของการอนุมานประเภท
อย่างที่เราได้เห็นอาร์เรย์ของจำนวนเต็มนั้นเร็วที่สุด
เมื่อคุณใช้อาร์เรย์ที่กระจัดกระจายระวังการเข้าถึงองค์ประกอบจะช้ากว่าอาร์เรย์เต็มรูปแบบมาก เนื่องจาก V8 จะไม่จัดสรรพื้นที่ทั้งหมดให้กับอาร์เรย์ที่ใช้เฉพาะส่วนหนึ่งของพื้นที่ แต่มันได้รับการจัดการในพจนานุกรมช่วยประหยัดพื้นที่ทั้งสอง แต่ใช้เวลาในการเข้าถึง
การทดสอบอาร์เรย์เบาบางและอาร์เรย์เต็มรูปแบบ
อย่าจัดสรรอาร์เรย์ขนาดใหญ่ล่วงหน้า (เช่นองค์ประกอบที่มีขนาดใหญ่กว่า 64K) ขนาดสูงสุดของพวกเขา แต่ควรได้รับการจัดสรรแบบไดนามิก ก่อนที่จะทดสอบประสิทธิภาพของเราในบทความนี้โปรดจำไว้ว่าสิ่งนี้ใช้กับเครื่องยนต์จาวาสคริปต์เท่านั้น
ตัวอักษรที่ว่างเปล่าและอาร์เรย์ที่จัดสรรไว้ล่วงหน้าจะถูกทดสอบในเบราว์เซอร์ที่แตกต่างกัน
Nitro (Safari) มีประโยชน์มากกว่าสำหรับอาร์เรย์ที่จัดสรรไว้ล่วงหน้า ในเครื่องยนต์อื่น ๆ (V8, Spidermonkey) การจัดสรรล่วงหน้าไม่มีประสิทธิภาพ
การทดสอบอาร์เรย์ preelocated
// arrayvar arrayvar arr = []; สำหรับ (var i = 0; i <1000000; i ++) {arr [i] = i;} // arrayvar arr = อาร์เรย์ใหม่ (10,00000); สำหรับ (var i = 0;ในโลกของเว็บแอปพลิเคชันความเร็วคือทุกอย่าง ผู้ใช้ไม่ต้องการใช้แอปพลิเคชันตารางที่ใช้เวลาไม่กี่วินาทีในการคำนวณจำนวนทั้งหมดของคอลัมน์หรือเพื่อสรุปข้อมูล นี่คือเหตุผลสำคัญที่คุณต้องการบีบประสิทธิภาพทุกบิตในรหัสของคุณ
แหล่งที่มาของภาพ: ต่อ Olof Forsberg
การทำความเข้าใจและปรับปรุงประสิทธิภาพของแอปพลิเคชันของคุณนั้นมีประโยชน์มาก แต่ก็ยากเช่นกัน เราขอแนะนำขั้นตอนต่อไปนี้เพื่อแก้ปัญหาอาการปวดประสิทธิภาพ:
เครื่องมือและเทคนิคบางอย่างที่แนะนำด้านล่างสามารถช่วยคุณได้
มีหลายวิธีในการเรียกใช้เกณฑ์มาตรฐานสำหรับตัวอย่างรหัส JavaScript เพื่อทดสอบประสิทธิภาพ - ข้อสันนิษฐานทั่วไปคือมาตรฐานเพียงเปรียบเทียบการประทับเวลาสองครั้ง รูปแบบนี้ชี้ให้เห็นโดยทีม JSPERF และใช้ในชุดมาตรฐานของ Sunspider และ Kraken:
var totaltime, start = วันที่ใหม่, การวนซ้ำ = 1000; ในขณะที่ (การวนซ้ำ-) {// ตัวอย่างโค้ดไปที่นี่} // totaltime →จำนวนล้านที่นำมา // เพื่อเรียกใช้รหัสตัวอย่าง 1,000 timestotaltime = วันที่ใหม่-เริ่มต้น;ที่นี่รหัสที่จะทดสอบจะถูกวางไว้ในลูปและเรียกใช้จำนวนครั้งที่กำหนด (ตัวอย่างเช่น 6 ครั้ง) หลังจากนี้วันที่เริ่มต้นจะถูกลบออกจากวันที่สิ้นสุดและเวลาที่ใช้ในการดำเนินการในลูปจะได้รับ
อย่างไรก็ตามการเปรียบเทียบนี้ทำสิ่งที่ง่ายเกินไปโดยเฉพาะอย่างยิ่งถ้าคุณต้องการเรียกใช้การเปรียบเทียบบนเบราว์เซอร์และสภาพแวดล้อมหลายแห่ง ตัวเก็บขยะนั้นมีผลกระทบต่อผลลัพธ์บางอย่าง แม้ว่าคุณจะใช้โซลูชันเช่นหน้าต่างประสิทธิภาพการทำงานข้อบกพร่องเหล่านี้จะต้องนำมาพิจารณา
ไม่ว่าคุณจะเรียกใช้ส่วนเกณฑ์มาตรฐานของรหัสเท่านั้นให้เขียนชุดทดสอบหรือรหัสห้องสมุดเกณฑ์มาตรฐานเกณฑ์มาตรฐาน JavaScript นั้นเป็นมากกว่าที่คุณคิด สำหรับเกณฑ์มาตรฐานคู่มือรายละเอียดเพิ่มเติมฉันขอแนะนำให้คุณอ่านเกณฑ์มาตรฐาน JavaScript ที่จัดทำโดย Mathias Bynens และ John-David Dalton
เครื่องมือนักพัฒนา Chrome มีการสนับสนุนที่ดีสำหรับ JavaScript Analytics คุณสามารถใช้คุณสมบัตินี้เพื่อตรวจสอบว่าฟังก์ชั่นใดที่ใช้เวลาส่วนใหญ่เพื่อให้คุณสามารถปรับให้เหมาะสม นี่เป็นสิ่งสำคัญแม้แต่การเปลี่ยนแปลงเล็กน้อยในรหัสอาจมีผลกระทบอย่างมีนัยสำคัญต่อประสิทธิภาพโดยรวม
แผงการวิเคราะห์เครื่องมือนักพัฒนา Chrome
กระบวนการวิเคราะห์เริ่มได้รับพื้นฐานประสิทธิภาพของรหัสแล้วปรากฏในรูปแบบของไทม์ไลน์ สิ่งนี้จะบอกเราว่ารหัสจะใช้เวลานานเท่าใด แท็บโปรไฟล์ทำให้เรามีมุมมองที่ดีขึ้นเกี่ยวกับสิ่งที่เกิดขึ้นในแอปพลิเคชัน ไฟล์การวิเคราะห์ CPU JavaScript แสดงเวลา CPU ที่ใช้ในรหัสของเราไฟล์การวิเคราะห์ตัวเลือก CSS แสดงเวลาที่ใช้เวลาในการประมวลผลตัวเลือกและสแนปชอตฮีปแสดงจำนวนหน่วยความจำที่ใช้ในวัตถุของเรา
ด้วยเครื่องมือเหล่านี้เราสามารถแยกปรับและวิเคราะห์ใหม่เพื่อวัดว่าการเพิ่มประสิทธิภาพการทำงานหรือประสิทธิภาพการทำงานของเรานั้นมีประสิทธิภาพจริงหรือไม่
แท็บโปรไฟล์แสดงข้อมูลประสิทธิภาพของรหัส
การแนะนำที่ดีในการวิเคราะห์อ่านโปรไฟล์ JavaScript ของ Zack Grossbart ด้วยเครื่องมือนักพัฒนา Chrome
เคล็ดลับ: เป็นการดีถ้าคุณต้องการตรวจสอบให้แน่ใจว่าการวิเคราะห์ของคุณไม่ได้รับผลกระทบจากแอปพลิเคชันหรือส่วนขยายที่ติดตั้งใด ๆ คุณสามารถใช้ --user-data-dir <empty_directory> ธงเพื่อเริ่มต้น Chrome ในกรณีส่วนใหญ่การทดสอบการเพิ่มประสิทธิภาพวิธีนี้ควรเพียงพอ แต่ก็ต้องใช้เวลามากขึ้นสำหรับคุณ นี่คือสิ่งที่โลโก้ V8 สามารถช่วยได้
ภายใน Google เครื่องมือนักพัฒนา Chrome ถูกใช้อย่างกว้างขวางโดยทีมงานเช่น Gmail เพื่อช่วยตรวจจับและแก้ไขปัญหาการรั่วไหลของหน่วยความจำ
สถิติหน่วยความจำในเครื่องมือนักพัฒนา Chrome
หน่วยความจำนับการใช้หน่วยความจำส่วนตัวขนาดของกอง JavaScript จำนวนโหนด DOM การทำความสะอาดที่เก็บข้อมูลเคาน์เตอร์ฟังเหตุการณ์และนักสะสมขยะที่ทีมของเรามีความกังวล แนะนำการอ่านเทคโนโลยี "3 Snapshot" ของ Loreena Lee จุดสำคัญของเทคนิคนี้คือการบันทึกพฤติกรรมบางอย่างในแอปพลิเคชันของคุณบังคับคอลเลกชันขยะตรวจสอบว่าจำนวนโหนด DOM ได้รับการกู้คืนไปยังพื้นฐานที่คาดหวังหรือไม่จากนั้นวิเคราะห์สแน็ปช็อตของสามกองเพื่อตรวจสอบว่ามีการรั่วไหลของหน่วยความจำ
การจัดการหน่วยความจำของแอปพลิเคชันหน้าเดียว (เช่น AngularJs, Backbone, Ember) มีความสำคัญมากพวกเขาแทบจะไม่เคยรีเฟรชหน้า ซึ่งหมายความว่าการรั่วไหลของหน่วยความจำอาจค่อนข้างชัดเจน แอปพลิเคชันหน้าเดียวบนขั้วมือถือเต็มไปด้วยข้อผิดพลาดเนื่องจากอุปกรณ์มีหน่วยความจำ จำกัด และเรียกใช้แอปพลิเคชันเช่นไคลเอนต์อีเมลหรือเครือข่ายสังคมออนไลน์เป็นเวลานาน ยิ่งความสามารถมากขึ้นความรับผิดชอบที่หนักขึ้น
มีหลายวิธีในการแก้ปัญหานี้ ใน Backbone ตรวจสอบให้แน่ใจว่าใช้ dispose () เพื่อจัดการกับมุมมองและการอ้างอิงเก่า ๆ (ปัจจุบันมีอยู่ใน backbone (ขอบ) ฟังก์ชั่นนี้ถูกเพิ่มเมื่อเร็ว ๆ นี้ลบตัวจัดการที่เพิ่มเข้ามาใน "เหตุการณ์" ของมุมมองและผู้ฟังเหตุการณ์ผ่านแบบจำลองหรือคอลเลกชันของพารามิเตอร์ที่สาม ผู้ฟังเพื่อหลีกเลี่ยงการรั่วไหลของหน่วยความจำเมื่อตรวจพบว่าองค์ประกอบจะถูกลบออก
คำแนะนำที่ชาญฉลาดจาก Derick Bailey:
แทนที่จะเข้าใจว่าเหตุการณ์และการอ้างอิงทำงานอย่างไรให้ทำตามกฎมาตรฐานเพื่อจัดการหน่วยความจำใน JavaScript หากคุณต้องการโหลดข้อมูลลงในคอลเลกชัน Backbone ที่เต็มไปด้วยวัตถุผู้ใช้คุณต้องการล้างคอลเลกชันเพื่อไม่ให้ใช้หน่วยความจำอีกต่อไปแล้วการอ้างอิงทั้งหมดไปยังคอลเลกชันและการอ้างอิงไปยังวัตถุในคอลเลกชัน เมื่อการอ้างอิงที่ใช้ชัดเจนทรัพยากรจะถูกรีไซเคิล นี่คือกฎการรวบรวมขยะ JavaScript มาตรฐาน
ในบทความ Derick ครอบคลุมข้อบกพร่องของหน่วยความจำทั่วไปจำนวนมากเมื่อใช้ backbone.js และวิธีการแก้ปัญหาเหล่านี้
การสอนเกี่ยวกับการดีบักการรั่วไหลของหน่วยความจำในโหนดโดย Felix Geisendörferก็คุ้มค่าที่จะอ่านโดยเฉพาะอย่างยิ่งเมื่อมันเป็นส่วนหนึ่งของสปาสปาที่กว้างขึ้น
เมื่อเบราว์เซอร์แสดงองค์ประกอบอีกครั้งในเอกสารพวกเขาจะต้องคำนวณใหม่และตำแหน่งและเรขาคณิตของพวกเขาซึ่งเราเรียกว่า reflow บล็อกการทำงานของผู้ใช้ในเบราว์เซอร์ดังนั้นจึงเป็นประโยชน์อย่างมากที่จะเข้าใจว่าการปรับปรุงเวลาของการรีมอนนั้นดีขึ้น
แผนภูมิเวลารีมอน
คุณควรทริกเกอร์ reflow หรือวาดใหม่เป็นแบทช์ แต่ใช้วิธีการเหล่านี้ในการกลั่นกรอง นอกจากนี้ยังเป็นสิ่งสำคัญที่จะพยายามไม่จัดการกับ DOM คุณสามารถใช้ DocumentFragment ซึ่งเป็นวัตถุเอกสารที่มีน้ำหนักเบา คุณสามารถใช้เป็นวิธีในการแยกส่วนของแผนผังเอกสารหรือสร้างเอกสารใหม่ "แฟรกเมนต์" แทนที่จะเพิ่มโหนด DOM อย่างต่อเนื่องควรดำเนินการแทรก DOM เพียงครั้งเดียวหลังจากใช้ชิ้นส่วนเอกสารเพื่อหลีกเลี่ยงการไหลย้อนที่มากเกินไป
ตัวอย่างเช่นเราเขียนฟังก์ชั่นเพื่อเพิ่ม 20 divs ลงในองค์ประกอบ หากคุณเพียงแค่ผนวก DIV เข้ากับองค์ประกอบในแต่ละครั้งสิ่งนี้จะทำให้เกิดการไหลย้อนกลับ 20 ครั้ง
ฟังก์ชั่น adddivs (องค์ประกอบ) {var div; สำหรับ (var i = 0; i <20; i ++) {div = document.createElement ('div'); div.innerhtml = 'heya!'; Element.AppendChild (div); -ในการแก้ปัญหานี้เราสามารถใช้ DocumentFragment แทนเราสามารถเพิ่ม div ใหม่ลงในแต่ละครั้ง การเพิ่ม DocumentFragment ลงใน DOM หลังจากเสร็จสิ้นจะทำให้เกิดการรีมอนเพียงครั้งเดียว
ฟังก์ชั่น adddivs (องค์ประกอบ) {var div; // สร้างเอกสารที่ว่างเปล่าใหม่ var fragment = document.createdocumentFragment (); สำหรับ (var i = 0; i <20; i ++) {div = document.createElement ('a'); div.innerhtml = 'heya!'; Fragment.appendchild (div); } Element.AppendChild (แฟรกเมนต์);}ดูทำให้เว็บเร็วขึ้นการเพิ่มประสิทธิภาพหน่วยความจำ JavaScript และการค้นหาการรั่วไหลของหน่วยความจำ
เพื่อช่วยค้นพบการรั่วไหลของหน่วยความจำ JavaScript ผู้พัฒนา Google (Marja Hölttäและ Jochen Eisinger) ได้พัฒนาเครื่องมือที่ทำงานร่วมกับเครื่องมือนักพัฒนา Chrome เพื่อดึงสแน็ปช็อตของกองและตรวจจับสิ่งที่วัตถุทำให้เกิดการรั่วไหลของหน่วยความจำ
เครื่องมือตรวจจับการรั่วไหลของหน่วยความจำ JavaScript
มีบทความที่สมบูรณ์เกี่ยวกับวิธีการใช้เครื่องมือนี้ ขอแนะนำให้คุณไปที่หน้าโครงการตรวจจับการรั่วไหลของหน่วยความจำด้วยตัวคุณเอง
หากคุณต้องการทราบว่าทำไมเครื่องมือดังกล่าวจึงไม่ได้ถูกรวมเข้ากับเครื่องมือการพัฒนาของเรามีสองเหตุผล เดิมทีมันถูกออกแบบมาเพื่อช่วยให้เราจับภาพสถานการณ์หน่วยความจำเฉพาะบางอย่างในไลบรารีปิดซึ่งเหมาะสมกว่าเป็นเครื่องมือภายนอก
Chrome รองรับการส่งธงบางส่วนโดยตรงไปยัง V8 เพื่อให้ได้ผลลัพธ์การเพิ่มประสิทธิภาพของเครื่องยนต์โดยละเอียด ตัวอย่างเช่นสิ่งนี้สามารถติดตามการเพิ่มประสิทธิภาพของ V8:
"/แอปพลิเคชัน/Google Chrome/Google Chrome"-Js-flags = "-Trace-Opt-Trace-Deopt"
ผู้ใช้ Windows สามารถเรียกใช้ chrome.exe js-flags = "trace-opt trace-deopt"
เมื่อพัฒนาแอปพลิเคชันโลโก้ V8 ด้านล่างสามารถใช้งานได้
สคริปต์การประมวลผลของ V8 ใช้ * (เครื่องหมายดอกจัน) เพื่อระบุฟังก์ชั่นที่ได้รับการปรับปรุงและใช้ ~ (หยัก) เพื่อแสดงฟังก์ชั่นที่ไม่ได้รับการปรับ
หากคุณสนใจที่จะเรียนรู้เพิ่มเติมเกี่ยวกับโลโก้ของ V8 และวิธีการทำงานภายในของ V8 ขอแนะนำให้อ่านโพสต์ที่ยอดเยี่ยมของ Vyacheslav Egorov ใน V8 Internals
High Precision Time (HRT) เป็นอินเทอร์เฟซเวลาที่มีความแม่นยำสูงในระดับสูงซึ่งไม่ส่งผลกระทบต่อเวลาของระบบและการปรับเปลี่ยนผู้ใช้ มันถือได้ว่าเป็นวิธีการวัดที่แม่นยำกว่าวันที่และวันที่ใหม่ตอนนี้ () สิ่งนี้ช่วยเราได้มากในการเขียนมาตรฐาน
เวลาที่มีความแม่นยำสูง (HRT) ให้ความแม่นยำในช่วงเวลาย่อย
ปัจจุบัน HRT ถูกใช้ใน Chrome (เวอร์ชันเสถียร) ใน Window.performance.webkitNow () แต่คำนำหน้าจะถูกทิ้งใน Chrome Canary ซึ่งทำให้สามารถโทรผ่าน window.perforce.now () Paul Irish โพสต์เพิ่มเติมเกี่ยวกับ HRT ใน html5rocks
ตอนนี้เรารู้เวลาที่แม่นยำในปัจจุบันแล้วมี API ที่สามารถวัดประสิทธิภาพของหน้าได้อย่างแม่นยำหรือไม่? ตอนนี้มี API Timing Timing ที่ให้วิธีง่ายๆในการรับบันทึกการวัดเวลาที่ถูกต้องและมีรายละเอียดเมื่อโหลดหน้าเว็บและนำเสนอต่อผู้ใช้ You can use window.performance.timing in console to get time information:
显示在控制台中的时间信息
我们可以从上面的数据获取很多有用的信息,例如网络延时为responseEnd fetchStart,页面加载时间为loadEventEnd responseEnd,处理导航和页面加载的时间为loadEventEnd navigationStart。
正如你所看到的,perfomance.memory的属性也能显示JavaScript的内存数据使用情况,如总的堆大小。
更多Navigation Timing API的细节,阅读Sam Dutton的Measuring Page Load Speed With Navigation Timing。
Chrome中的about:tracing提供了浏览器的性能视图,记录了Chrome的所有线程、tab页和进程。
About:Tracing提供了浏览器的性能视图
这个工具的真正用处是允许你捕获Chrome的运行数据,这样你就可以适当地调整JavaScript执行,或优化资源加载。
Lilli Thompson有一篇写给游戏开发者的使用about:tracing分析WebGL游戏的文章,同时也适合JavaScript的开发者。
在Chrome的导航栏里可以输入about:memory,同样十分实用,可以获得每个tab页的内存使用情况,对定位内存泄漏很有帮助。
我们看到, JavaScript的世界中有很多隐藏的陷阱,且并没有提升性能的银弹。只有把一些优化方案综合使用到(现实世界)测试环境,才能获得最大的性能收益。即便如此,了解引擎是如何解释和优化代码,可以帮助你调整应用程序。
测量,理解,修复。不断重复这个过程。
图片来源: Sally Hunter
谨记关注优化,但为了便利可以舍弃一些很小的优化。例如,有些开发者选择.forEach和Object.keys代替for和for..in循环,尽管这会更慢但使用更方便。要保证清醒的头脑,知道什么优化是需要的,什么优化是不需要的。
同时注意,虽然JavaScript引擎越来越快,但下一个真正的瓶颈是DOM。回流和重绘的减少也是重要的,所以必要时再去动DOM。还有就是要关注网络,HTTP请求是珍贵的,特别是移动终端上,因此要使用HTTP的缓存去减少资源的加载。
Remembering these points can ensure that you have obtained most of the information in this article. ฉันหวังว่ามันจะเป็นประโยชน์กับคุณ!
原文:http://coding.smashingmagazine.com/2012/11/05/writing-fast-memory-efficient-javascript/
作者:Addy Osmani