ในความเป็นจริงคนที่เขียน Java ดูเหมือนจะไม่มีส่วนเกี่ยวข้องกับ CPU อย่างมากมันมีบางอย่างเกี่ยวกับวิธีเรียกใช้ CPU และวิธีการตั้งค่าจำนวนเธรดที่เรากล่าวถึงก่อนหน้านี้ อย่างไรก็ตามอัลกอริทึมนั้นเป็นเพียงการอ้างอิง สถานการณ์ที่แตกต่างกันมากมายต้องใช้วิธีการที่เป็นประโยชน์ในการแก้ปัญหา ยิ่งกว่านั้นหลังจากเรียกใช้ CPU เราจะพิจารณาวิธีการทำให้ CPU ไม่เต็ม ฮ่าฮ่ามนุษย์นั่นมัน ฮ่าฮ่าโอเคบทความนี้เกี่ยวกับสิ่งอื่น ๆ บางทีคุณเกือบจะไม่เขียนรหัสใน Java ให้ความสนใจกับ CPU เพราะการสร้างความพึงพอใจให้กับธุรกิจเป็นสิ่งสำคัญอันดับแรก หากคุณต้องการให้บรรลุระดับเฟรมเวิร์กและจัดเตรียมแคชข้อมูลที่ใช้ร่วมกันจำนวนมากจะต้องมีปัญหาการขอข้อมูลจำนวนมากอยู่ตรงกลาง แน่นอน Java ให้บริการแพ็คเกจที่เกิดขึ้นพร้อมกันหลายประเภทและคุณสามารถใช้งานได้ แต่วิธีการทำภายในคุณต้องเข้าใจรายละเอียดเพื่อใช้งานได้ดีกว่าไม่เช่นนั้นจะดีกว่าที่จะไม่ใช้ บทความนี้อาจไม่อธิบายเนื้อหาเหล่านี้ว่าเป็นจุดสนใจเพราะเช่นเดียวกับปาร์ตี้ชื่อ: เราต้องการพูดคุยเกี่ยวกับ CPU, ฮ่าฮ่า
สิ่งเดียวกันนี้ดูเหมือนว่า Java ไม่มีส่วนเกี่ยวข้องกับ CPU ดังนั้นเรามาพูดถึงสิ่งที่เกิดขึ้นในตอนนี้
1. เมื่อเผชิญกับองค์ประกอบที่ใช้ร่วมกันแนวคิดแรกของเราคือเพื่อให้แน่ใจว่าการดำเนินการอ่านที่สอดคล้องกันผ่านความผันผวนนั่นคือการมองเห็นแบบสัมบูรณ์ การมองเห็นที่เรียกว่าหมายความว่าทุกครั้งที่คุณต้องการใช้ข้อมูลนี้ CPU จะไม่ใช้เนื้อหาแคชใด ๆ และจะคว้าข้อมูลจากหน่วยความจำ กระบวนการนี้ยังคงใช้ได้สำหรับ CPU หลายตัวซึ่งหมายความว่า CPU และหน่วยความจำจะถูกซิงโครไนซ์ในเวลานี้ CPU จะออกคำสั่งแอสเซมบลีคล้ายกับล็อค addl 0 เหมือนรถบัส +0 แต่จะไม่ทำอะไรที่เกี่ยวข้องกับสิ่งใด อย่างไรก็ตามเมื่อคำสั่งเสร็จสมบูรณ์การดำเนินการที่ตามมาจะไม่ส่งผลกระทบต่อการเข้าถึงเธรดอื่น ๆ ขององค์ประกอบนี้อีกต่อไปซึ่งเป็นการมองเห็นแบบสัมบูรณ์ที่สามารถทำได้ แต่ไม่สามารถดำเนินการที่สอดคล้องกันได้ กล่าวคือสิ่งที่ผันผวนไม่สามารถบรรลุได้คือความสอดคล้องของการดำเนินงานเช่น i ++ (พร้อมกันภายใต้หลายเธรด) เนื่องจากการดำเนินการของ I ++ จะถูกย่อยสลายเป็น:
int tmp = i; tmp = tmp + 1; i = tmp;
สามขั้นตอนเหล่านี้เสร็จสมบูรณ์ จากจุดนี้คุณยังสามารถดูได้ว่าทำไม I ++ สามารถทำสิ่งอื่น ๆ ก่อนแล้วจึงเพิ่ม 1 ให้กับตัวเองเพราะมันถูกกำหนดค่าให้กับตัวแปรอื่น
2. หากเราต้องการใช้ความสอดคล้องพร้อมกันหลายเธรดเราจำเป็นต้องใช้กลไกการล็อค ในปัจจุบันสิ่งต่าง ๆ เช่นอะตอม* สามารถปฏิบัติตามข้อกำหนดเหล่านี้ได้ มีวิธีการเรียนที่ไม่ปลอดภัยหลายวิธีภายใน โดยการเปรียบเทียบข้อมูลการมองเห็นแบบสัมบูรณ์อย่างต่อเนื่องเราสามารถมั่นใจได้ว่าข้อมูลที่ได้รับนั้นทันสมัย ต่อไปเราจะยังคงพูดคุยเกี่ยวกับเรื่อง CPU อื่น ๆ
3. ในอดีตเราไม่สามารถเรียกใช้ CPU เพื่อเติมเต็มได้ แต่เราไม่พอใจไม่ว่าเราจะเริ่มเพิกเฉยต่อความล่าช้าระหว่างหน่วยความจำและ CPU เนื่องจากเราพูดถึงวันนี้เราจะพูดคุยสั้น ๆ เกี่ยวกับความล่าช้า โดยทั่วไปแล้วซีพียูปัจจุบันมีแคชสามระดับและความล่าช้าแตกต่างกันในยุคที่แตกต่างกันดังนั้นจำนวนเฉพาะสามารถทำได้เพียงประมาณ โดยทั่วไปแล้วซีพียูของวันนี้มีความล่าช้า 1-2NS แคชระดับที่สองโดยทั่วไปจะมี NS ไม่กี่ NS ประมาณสิบ NS และแคชระดับที่สามโดยทั่วไปจะอยู่ระหว่าง 30ns และ 50ns และการเข้าถึงหน่วยความจำโดยทั่วไปจะถึง 70ns หรือมากกว่านั้น (คอมพิวเตอร์กำลังพัฒนาเร็วมาก แม้ว่าความล่าช้านี้มีขนาดเล็กมาก แต่ก็อยู่ในระดับนาโนวินาทีคุณจะพบว่าเมื่อโปรแกรมของคุณถูกแบ่งออกเป็นคำสั่งการดำเนินการจะมีการโต้ตอบกับ CPU จำนวนมาก หากความล่าช้าของการโต้ตอบแต่ละครั้งมีขนาดใหญ่มากประสิทธิภาพของระบบจะเปลี่ยนไปในเวลานี้
4. กลับไปที่ความผันผวนที่กล่าวถึงตอนนี้ ทุกครั้งที่ได้รับข้อมูลจากหน่วยความจำมันจะละทิ้งแคช แน่นอนถ้ามันช้าลงในการดำเนินการเธรดเดี่ยวมันจะช้าลง บางครั้งเราต้องทำสิ่งนี้ แม้แต่การอ่านและการเขียนก็จำเป็นต้องมีความสอดคล้องและแม้แต่บล็อกข้อมูลทั้งหมดก็ถูกซิงโครไนซ์ เราสามารถลดความละเอียดของล็อคได้ในระดับหนึ่งเท่านั้น แต่เราไม่สามารถล็อคได้เลย แม้แต่ระดับ CPU เองก็มีข้อ จำกัด ระดับคำสั่ง
5. การดำเนินการอะตอมในระดับ CPU โดยทั่วไปเรียกว่าอุปสรรคโดยมีอุปสรรคในการอ่านอุปสรรคการเขียน ฯลฯ พวกเขามักจะถูกกระตุ้นโดยจุดหนึ่ง เมื่อมีการส่งคำแนะนำหลายรายการไปยัง CPU คำแนะนำบางอย่างอาจไม่ถูกดำเนินการตามลำดับของโปรแกรมและบางอย่างจะต้องดำเนินการตามลำดับของโปรแกรมตราบใดที่พวกเขาสามารถรับประกันได้ว่าจะสอดคล้องกันในลำดับสุดท้ายของโปรแกรม ในแง่ของการเรียงลำดับ JIT จะเปลี่ยนไปในระหว่างการรันไทม์และระดับคำสั่ง CPU ก็จะเปลี่ยนไปเช่นกัน เหตุผลหลักคือการเพิ่มประสิทธิภาพคำแนะนำรันไทม์เพื่อให้โปรแกรมทำงานได้เร็วขึ้น
6. ระดับ CPU จะทำงานสายแคชบนหน่วยความจำ สายแคชที่เรียกว่าจะอ่านชิ้นส่วนของหน่วยความจำอย่างต่อเนื่องซึ่งโดยทั่วไปเกี่ยวข้องกับโมเดล CPU และสถาปัตยกรรม ทุกวันนี้ซีพียูจำนวนมากมักจะอ่านหน่วยความจำอย่างต่อเนื่องในแต่ละครั้งและช่วงแรกจะมี 32BYTE ดังนั้นมันจะเร็วขึ้นเมื่อข้ามอาร์เรย์บางอย่าง (มันช้ามากตามการเดินทางข้ามคอลัมน์) แต่มันไม่ถูกต้องอย่างสมบูรณ์ ต่อไปนี้จะเปรียบเทียบสถานการณ์ตรงกันข้าม
7. หาก CPU เปลี่ยนข้อมูลเราต้องพูดคุยเกี่ยวกับสถานะของ CPU ที่แก้ไขข้อมูล หากข้อมูลทั้งหมดถูกอ่านสามารถอ่านได้แบบขนานโดยหลายเธรดภายใต้ CPU หลายตัว เมื่อเขียนการดำเนินการบนบล็อกข้อมูลมันจะแตกต่างกัน บล็อกข้อมูลจะมีเอกสิทธิ์เฉพาะบุคคลแก้ไขการทำให้เป็นโมฆะและสถานะอื่น ๆ และข้อมูลจะล้มเหลวตามธรรมชาติหลังจากการแก้ไข เมื่อหลายเธรดกำลังแก้ไขบล็อกข้อมูลเดียวกันภายใต้ CPU หลายตัวสำเนาข้อมูลบัส (QPI) ระหว่าง CPU จะเกิดขึ้น แน่นอนถ้าเราปรับเปลี่ยนเป็นข้อมูลเดียวกันเราไม่มีทางเลือก แต่เมื่อเรากลับไปที่สายแคชที่จุด 6 ปัญหาจะลำบากมากขึ้น หากข้อมูลอยู่ในอาร์เรย์เดียวกันและองค์ประกอบในอาร์เรย์จะถูกแคชไปที่ CPU ในเวลาเดียวกัน QPI ของมัลติเธรดจะบ่อยมาก บางครั้งปัญหานี้จะเกิดขึ้นแม้ว่าวัตถุจะรวมอยู่ในอาร์เรย์รวมกันเช่น:
คลาส inputinteger {ค่า int ส่วนตัว; public inputinteger (int i) {this.value = i;}} inputinteger [] จำนวนเต็ม = ใหม่ inputinteger [ขนาด]; สำหรับ (int i = 0; i <size; i ++) {integers [i] = ใหม่ ในเวลานี้คุณจะเห็นว่าทุกอย่างในจำนวนเต็มเป็นวัตถุและมีเพียงการอ้างอิงถึงวัตถุในอาร์เรย์ แต่การจัดเรียงของวัตถุนั้นเป็นอิสระในทางทฤษฎีและจะไม่ถูกเก็บไว้อย่างต่อเนื่อง อย่างไรก็ตามเมื่อ Java จัดสรรหน่วยความจำวัตถุมันมักจะถูกจัดสรรอย่างต่อเนื่องในพื้นที่ Eden เมื่ออยู่ในลูปหากไม่มีการเข้าถึงเธรดอื่นวัตถุเหล่านี้จะถูกเก็บไว้ด้วยกัน แม้ว่าพวกเขาจะเป็น GC ไปยังพื้นที่เก่า แต่ก็มีแนวโน้มที่จะรวมเข้าด้วยกัน ดังนั้นวิธีการปรับเปลี่ยนอาร์เรย์ทั้งหมดโดยใช้วัตถุง่าย ๆ เพื่อแก้ปัญหาสายแคชดูเหมือนจะไม่น่าเชื่อถือเพราะ int เป็น 4 ไบต์ ถ้าในโหมด 64 ขนาดนี้คือ 24 ไบต์ (เต็ม 4bytes) และการบีบอัดตัวชี้คือ 16 ไบต์; นั่นคือ CPU สามารถจับคู่วัตถุ 3-4 ในแต่ละครั้ง วิธีทำแคช CPU แต่ไม่ส่งผลกระทบต่อ QPI ของระบบ อย่าคิดว่าจะทำมันให้เสร็จโดยแยกวัตถุเพราะกระบวนการคัดลอกหน่วยความจำกระบวนการ GC มีแนวโน้มที่จะถูกคัดลอกเข้าด้วยกัน วิธีที่ดีที่สุดคือเติมเต็ม แม้ว่ามันจะเป็นของเสียหน่วยความจำ แต่นี่เป็นวิธีที่น่าเชื่อถือที่สุดซึ่งก็คือการเติมวัตถุให้กับ 64 ไบต์ หากไม่ได้เปิดใช้งานการบีบอัดตัวชี้มี 24bytes และมี 40 ไบต์ในเวลานี้ คุณจะต้องเพิ่ม 5 longs ภายในวัตถุ
คลาส inputinteger {ค่า INT สาธารณะ; Long Private Long A1, A2, A3, A4, A5;} ฮ่าฮ่าวิธีนี้เรียบง่ายมาก แต่ใช้งานได้ดีมาก บางครั้งเมื่อรวบรวม JVM พบว่าพารามิเตอร์เหล่านี้ยังไม่ได้ทำดังนั้นจึงถูกฆ่าโดยตรงสำหรับคุณ การเพิ่มประสิทธิภาพไม่ถูกต้อง วิธีการรวมถึงวิธีนี้เพียงแค่ใช้พารามิเตอร์ 5 ตัวเหล่านี้ในตัวถังวิธี (ใช้ทั้งหมด) แต่วิธีนี้จะไม่เรียกมัน
8. ในระดับ CPU บางครั้งอาจไม่สามารถทำสิ่งแรกที่ต้องทำ มันคือราชา ในการทำงานของ Atomicintegerfieldupdater ถ้าคุณเรียก Getanteset (TRUE) ในเธรดเดียวคุณจะพบว่ามันทำงานได้ค่อนข้างเร็วและมันเริ่มช้าลงภายใต้ซีพียูหลายคอร์ ทำไมถึงพูดอย่างชัดเจนข้างต้น? เนื่องจาก getandset ได้รับการแก้ไขและเปรียบเทียบจากนั้นเปลี่ยนก่อน qpi จะสูงมากดังนั้นในเวลานี้จึงเป็นการดีกว่าที่จะได้รับการดำเนินการก่อนแล้วจึงปรับเปลี่ยน และมันก็เป็นวิธีที่ดีในการรับมันครั้งเดียว หากไม่สามารถรับได้ให้ให้และปล่อยให้เธรดอื่นทำสิ่งอื่น ๆ
9. บางครั้งเพื่อแก้ปัญหาของ CPU บางส่วนยุ่งและไม่ยุ่งจะมีอัลกอริทึมมากมายที่จะแก้ไข ตัวอย่างเช่น NUMA เป็นหนึ่งในโซลูชั่น อย่างไรก็ตามไม่ว่าสถาปัตยกรรมใดจะมีประโยชน์มากกว่าในบางสถานการณ์มันอาจไม่ได้ผลสำหรับทุกสถานการณ์ มีกลไกการล็อคคิวเพื่อให้การจัดการรัฐ CPU เสร็จสมบูรณ์ แต่สิ่งนี้ยังมีปัญหาของสายแคชเนื่องจากรัฐเปลี่ยนแปลงบ่อยครั้งและแกนของแอปพลิเคชันต่าง ๆ จะสร้างอัลกอริทึมบางอย่างเพื่อให้ความร่วมมือกับ CPU
มีรายละเอียดมากมายเกี่ยวกับเรื่องนี้เช่นการซ้อนทับของลูปตัวแปรทั่วไปประเภทผันผวนและชุดอะตอม* ซึ่งแตกต่างอย่างสิ้นเชิง ลูปอาร์เรย์หลายมิติวนวนตามลำดับย้อนหลังในละติจูดที่แตกต่างกันและมีรายละเอียดมากมายและฉันเข้าใจว่าทำไมมีแรงบันดาลใจในกระบวนการเพิ่มประสิทธิภาพที่แท้จริง รายละเอียดของการล็อคนั้นบางและเวียนหัวและที่ระดับล่างสุดของระบบมีการผ่าตัดอะตอมที่มีน้ำหนักเบาอยู่เสมอ ไม่ว่าใครจะบอกว่ารหัสของเขาไม่จำเป็นต้องล็อคสิ่งที่ดีที่สุดอาจเป็นเรื่องง่ายเหมือน CPU สามารถดำเนินการคำสั่งหนึ่งคำสั่งได้ในแต่ละช่วงเวลาเท่านั้น ซีพียูหลายคอร์จะมีพื้นที่ที่ใช้ร่วมกันเพื่อควบคุมเนื้อหาบางอย่างที่ระดับบัสรวมถึงระดับการอ่านระดับการเขียนระดับหน่วยความจำ ฯลฯ ในสถานการณ์ที่แตกต่างกันความละเอียดของล็อคจะลดลงมากที่สุด ประสิทธิภาพของระบบชัดเจนในตัวเองและเป็นผลลัพธ์ปกติ