ทำไมโปรเซสเซอร์ถึงมี 32 รีจิสเตอร์?


52

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


2
ฉันเดาเกินจุดที่แน่นอนตัวแปรท้องถิ่นของคุณทั้งหมดพอดีกับการลงทะเบียน ข้อมูลจริงที่คุณทำงานอาจมีขนาดใหญ่เกินไปอยู่แล้ว
Niklas B.

14
ผลตอบแทนลดลง เห็นได้ชัดว่าการลงทะเบียนนั้น "แพงกว่า" (ในประสาทสัมผัสต่าง ๆ ) มากกว่า RAM หรือเราแค่มีการลงทะเบียน 8GB
David Richerby

5
หนึ่งในเหตุผลที่มันเร็วมากเพราะมีไม่มาก
stackErr

5
มีความแตกต่างระหว่างจำนวน CPU ที่ลงทะเบียนทั้งหมดและจำนวนที่คุณสามารถใช้พร้อมกันได้
Thorbjørn Ravn Andersen

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

คำตอบ:


82

ประการแรกสถาปัตยกรรมโปรเซสเซอร์ไม่ได้หยุดที่ 32 register สถาปัตยกรรม RISC เกือบทั้งหมดที่มีการลงทะเบียน 32 ชุดที่ปรากฏในชุดคำสั่งนั้นมีการลงทะเบียนจำนวนเต็ม 32 ตัวและการลงทะเบียนจุดลอยตัวอีก 32 จุด (เช่น 64) (จุดลอยตัว "เพิ่ม" ใช้การลงทะเบียนที่แตกต่างจากจำนวนเต็ม "เพิ่ม") สถาปัตยกรรม SPARC มีหน้าต่างการลงทะเบียน. ใน SPARC คุณสามารถเข้าถึงการลงทะเบียนจำนวนเต็ม 32 ครั้งเท่านั้น แต่การลงทะเบียนจะทำหน้าที่เหมือนสแต็กและคุณสามารถกดและรีจิสเตอร์ใหม่ 16 ครั้ง สถาปัตยกรรม Itanium จาก HP / Intel มีจำนวนเต็ม 128 จำนวนเต็มและมีการลงทะเบียนจุดลอยตัว 128 ตัวในชุดคำสั่ง GPU สมัยใหม่จาก NVidia, AMD, Intel, ARM และ Imagination Technologies ล้วนแล้วแต่มีการลงทะเบียนจำนวนมากในไฟล์ลงทะเบียน (ฉันรู้ว่านี่เป็นจริงของสถาปัตยกรรม NVidia และ Intel ฉันไม่คุ้นเคยกับชุดคำสั่ง AMD, ARM และ Imagination แต่ฉันคิดว่าไฟล์ลงทะเบียนมีขนาดใหญ่เช่นกัน)

ประการที่สองไมโครโปรเซสเซอร์ที่ทันสมัยส่วนใหญ่ใช้การเปลี่ยนชื่อรีจิสเตอร์เพื่อกำจัดอนุกรมที่ไม่จำเป็นซึ่งเกิดจากการจำเป็นต้องใช้ทรัพยากรซ้ำดังนั้นไฟล์ลงทะเบียนฟิสิคัลพื้นฐานอาจใหญ่กว่า (96, 128 หรือ 192 รีจิสเตอร์ในบางเครื่อง) ต้องการคอมไพเลอร์เพื่อสร้างชื่อรีจิสเตอร์ที่ไม่ซ้ำกันจำนวนมากในขณะที่ยังคงให้ไฟล์รีจิสเตอร์ขนาดใหญ่ลงในตัวกำหนดตารางเวลา

มีเหตุผลสองประการที่ทำให้เพิ่มจำนวนการลงทะเบียนในชุดคำสั่งได้ยากขึ้น ก่อนอื่นคุณจะต้องสามารถระบุตัวระบุการลงทะเบียนในแต่ละคำสั่ง 32 register ต้องการตัวระบุรีจิสเตอร์ 5 บิตดังนั้นคำแนะนำ 3-address (โดยทั่วไปบนสถาปัตยกรรม RISC) ใช้จ่าย 15 จาก 32 บิตคำสั่งเพื่อระบุรีจิสเตอร์ หากคุณเพิ่มขึ้นเป็น 6 หรือ 7 บิตคุณจะมีพื้นที่น้อยลงในการระบุรหัสและค่าคงที่ GPUs และ Itanium มีมากคำแนะนำที่มีขนาดใหญ่ คำแนะนำขนาดใหญ่มาพร้อมกับค่าใช้จ่าย: คุณต้องใช้หน่วยความจำคำสั่งเพิ่มเติมดังนั้นพฤติกรรมแคชคำสั่งของคุณจึงไม่เหมาะอย่างยิ่ง

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


1
ฉันจะกล่าวถึง 256 FPRs ของ SPARC64 VIIIfx และ GPR ที่ไม่ใช่หน้าต่างพิเศษ 32 รายการทำได้โดยเพิ่มคำสั่ง Set XAR ซึ่งให้ 13 บิตสำหรับคำสั่งหนึ่งหรือสองถัดไป มันมีเป้าหมายที่ HPC ดังนั้นการนับการลงทะเบียนจึงเป็นที่เข้าใจได้มากขึ้น ฉันก็ถูกล่อลวงให้อธิบายเกี่ยวกับการแลกเปลี่ยนและเทคนิคบางอย่างที่เกี่ยวข้องกับการลงทะเบียนมากขึ้น แต่คุณแสดงให้เห็นถึงภูมิปัญญาในการหลีกเลี่ยงการตอบคำถามที่เหนื่อยล้ามากกว่าเดิม
Paul A. Clayton

2
การเพิ่มประโยชน์จากการลงทะเบียนเล็กน้อยสำหรับรหัส "วัตถุประสงค์ทั่วไป" อาจคุ้มค่า แต่การค้นหาการวัดที่มีความหมายไม่ใช่เรื่องง่าย ฉันคิดว่า Mitch Alsup พูดถึง comp.arch ที่ขยาย x86 ถึง 32 การลงทะเบียนมากกว่า 16 จะได้รับประสิทธิภาพประมาณ 3% เมื่อเทียบกับ (ISTR) 10-15% สำหรับการขยายการลงทะเบียน 8 ถึง 16 ที่ถูกเลือก แม้สำหรับ ISA ที่เก็บแบบโหลดไปที่ 64 อาจให้ประโยชน์น้อย (อย่างน้อยสำหรับรหัส GP ปัจจุบัน) (BTW, GPU มักจะแบ่งปันการลงทะเบียนกับเธรด: เช่นหนึ่งเธรดที่มี 250 การปล่อยให้รวม 16 เธรดสำหรับกระทู้อื่น ๆ )
Paul A. Clayton

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

@ PaulA.Clayton ฉันมักจะคิดว่า IA-64 เป็นสถาปัตยกรรมที่มีการลงทะเบียน ISA จำนวนมากที่สุด
phuclv

@ LưuVĩnhPhúc SPARC64 VIIIfx เป็นเฉพาะ HPC FYI, Am29k (แนะนำรอบ1987-8 ) มี 64 global และ 128 GPRs แบบหน้าต่างซึ่งเป็น GPRs มากกว่า Itanium (ซึ่งมี 8 register register สาขาและ register count loop ซึ่งฟังก์ชันจะอยู่ใน GPRs ใน ISAs อื่น ๆ )
Paul A. Clayton

16

อีกสองเหตุผลในการ จำกัด จำนวนการลงทะเบียน:

  • คาดว่าจะได้รับเล็กน้อย: CPU เช่น Intel / AMD x64 รุ่นปัจจุบันมีแคชขนาด 32kByte และ L1-D มากกว่าและการเข้าถึงแคช L1 มักใช้เวลาหนึ่งรอบนาฬิกาเท่านั้น (เมื่อเทียบกับวงจรนาฬิกานับร้อยรอบสำหรับ RAM เดี่ยวสมบูรณ์ เข้าไป). ดังนั้นจึงมีน้อยที่จะได้รับจากการมีข้อมูลมากขึ้นในการลงทะเบียนเมื่อเทียบกับการมีข้อมูลในแคช L1
  • ค่าใช้จ่ายในการคำนวณเพิ่มเติม: การมีการลงทะเบียนเพิ่มจะทำให้เกิดโอเวอร์เฮดซึ่งอาจทำให้คอมพิวเตอร์ทำงานช้าลง:
    • ในสภาพแวดล้อมแบบมัลติทาสกิ้งมักจะมีการสลับงานเพื่อบันทึกเนื้อหาของการลงทะเบียนทั้งหมดของกระบวนการที่เหลืออยู่ในหน่วยความจำและต้องโหลดกระบวนการที่จะเข้าสู่กระบวนการ ยิ่งคุณลงทะเบียนมากเท่าไหร่ก็ยิ่งใช้เวลานานเท่านั้น
    • ในทำนองเดียวกันในสถาปัตยกรรมที่ไม่มี windows register การเรียกใช้ฟังก์ชันแบบเรียงซ้อนใช้ชุดรีจิสเตอร์เดียวกัน ดังนั้นฟังก์ชั่น A การเรียกใช้ฟังก์ชัน B ใช้ชุดรีจิสเตอร์เดียวกับตัว B ดังนั้น B ต้องบันทึกเนื้อหาของการลงทะเบียนทั้งหมดที่ใช้ (ซึ่งยังคงมีค่าของ A) และจะต้องเขียนพวกเขากลับมาก่อนที่จะกลับมา (ในบางอนุสัญญาการเรียกมันเป็นงานของ A เพื่อบันทึกเนื้อหาลงทะเบียนก่อนที่จะโทร B แต่ ค่าใช้จ่ายคล้ายกัน) ยิ่งคุณมีการลงทะเบียนมากเท่าไหร่การประหยัดยิ่งใช้เวลานานขึ้นเท่านั้นและทำให้การเรียกใช้ฟังก์ชันมีราคาสูงขึ้น

มันทำงานอย่างไรกับแคช L1 เพื่อที่เราจะได้ไม่มีปัญหาเหมือนกันกับรีจิสเตอร์?
babou

4
สำหรับโปรเซสเซอร์ประสิทธิภาพสูง L1 Dcache latency นั้นโดยทั่วไปจะเป็น 3 หรือ 4 รอบ (รวมถึงการสร้างที่อยู่) เช่น Haswell ของ Intel มี 4 latency รอบ (ไม่ต้องมีการแฝงเรจิสเตอร์ของการพึ่งพาข้อมูล Dcache มีแนวโน้มที่จะรองรับการเข้าถึงน้อยลงต่อรอบ (เช่น 2 อ่าน, 1 เขียนสำหรับ Haswell) กว่าไฟล์ลงทะเบียน (เช่น 4 อ่าน, 6 เขียนสำหรับอัลฟ่า 21264 ซึ่งจำลองแบบไฟล์ 2 ไฟล์ 4 อ่านเร็วกว่า 1 ด้วย 8)
Paul A. Clayton

@ PaulA.Clayton: หากแคช L1 มีเวลาแฝง 3-4 รอบซึ่งจะแนะนำว่าอาจมีประโยชน์ในการมีเช่นบางชุดคำ 64 คำของหน่วยความจำรอบเดียวที่มีพื้นที่ที่อยู่ 64 คำและ คำแนะนำ "โหลด / จัดเก็บโดยตรง" โดยเฉพาะอย่างยิ่งหากมีวิธีที่จะผลักดันค่าที่ไม่เป็นศูนย์ทั้งหมดตามด้วยคำที่บอกว่าคำใดที่ไม่ใช่ศูนย์จากนั้นวิธีที่จะป๊อปอัปพวกเขากลับมา . วิธีการหลายอย่างมีความยาวระหว่าง 16 ถึง 60 คำของตัวแปรท้องถิ่นดังนั้นการลดเวลาในการเข้าถึงสำหรับผู้ที่มี 3-4 รอบต่อหนึ่งอาจดูเหมือนเป็นประโยชน์
supercat

@supercat สแต็คต่างๆ (และ global / TLS [เช่น Knapsack]) ความคิดแคชได้ถูกนำเสนอในเอกสารทางวิชาการรวมถึงกลไกเช่นบัฟเฟอร์ลายเซ็น ( PDF ) การใช้งานจริงไม่มาก (ดูเหมือน) นี่กำลังได้รับช่างพูด (ดังนั้นน่าจะจบหรือไปที่อื่น)
Paul A. Clayton

4

รหัสจำนวนมากมีการเข้าถึงหน่วยความจำมากมาย (30% เป็นตัวเลขทั่วไป) จากนั้นโดยทั่วไปประมาณ 2 / 3rds คือการเข้าถึงแบบอ่านและ 1 / 3rds เป็นสิทธิ์การเขียน สิ่งนี้ไม่ได้เกิดจากการหมดเรจิสเตอร์เท่าที่เข้าถึงอาเรย์การเข้าถึงตัวแปรสมาชิกของวัตถุเป็นต้น

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

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

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

หากซีพียูของคุณเป็นไปตามลำดับและไม่ทำการเปลี่ยนชื่อรีจิสเตอร์และคุณกำลังพยายามดำเนินการหลายอย่างต่อรอบ (มากกว่า 3) ในทางทฤษฎีแล้วคุณต้องลงทะเบียนมากขึ้นเนื่องจากจำนวน ops ต่อรอบเพิ่มขึ้น นี่คือสาเหตุที่ Itanium มีการลงทะเบียนมากมาย! แต่ในทางปฏิบัตินอกจากโค้ดเชิงตัวเลขหรือ floating-point หรือ SIMD (ซึ่ง Itanium ทำได้ดีมาก) โค้ดส่วนใหญ่จะมีหน่วยความจำอ่าน / เขียนและกระโดดจำนวนมากซึ่งทำให้ความฝันของ ops มากกว่า-3 ต่อวงจรเป็นไปไม่ได้ (โดยเฉพาะในซอฟต์แวร์ที่มุ่งเน้นเซิร์ฟเวอร์เช่นฐานข้อมูลคอมไพเลอร์การใช้ภาษาระดับสูงเช่นจาวาสคริปต์การจำลอง ฯลฯ ... ) นี่คือสิ่งที่ Itanium จมลง

ทุกอย่างลงมาเพื่อความแตกต่างระหว่างการคำนวณและการดำเนินการ!


2

ใครบอกคุณว่าโปรเซสเซอร์มีการลงทะเบียน 32 ครั้งเสมอ ? x86 มี 8, ARM 32 บิตและ x86_64 มี 16, IA-64 มี 128 และตัวเลขอื่น ๆ อีกมากมาย คุณสามารถดูได้ที่นี่ แม้แต่ MIPS, PPC หรือสถาปัตยกรรมใด ๆ ที่มีการลงทะเบียนวัตถุประสงค์ทั่วไป 32 รายการในชุดคำสั่งนั้นมีขนาดใหญ่กว่า 32 เนื่องจากยังคงมีการลงทะเบียน (ถ้ามี) เสมอ, การลงทะเบียนควบคุม ... ไม่รวมถึงการลงทะเบียนเปลี่ยนชื่อและการลงทะเบียนฮาร์ดแวร์

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

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


2

ตัวอย่างเช่นโปรเซสเซอร์ Intel ทั่วไปมี "เป็นทางการ" 16 จำนวนเต็มและ 16 เวกเตอร์ลงทะเบียน แต่ในความเป็นจริงแล้วยังมีอีกมากมาย: โปรเซสเซอร์ใช้ "register renaming" หากคุณมีคำสั่ง reg3 = reg1 + reg2 คุณจะมีปัญหาหากคำสั่งอื่นที่ใช้ reg3 ยังไม่เสร็จ - คุณไม่สามารถเรียกใช้คำสั่งใหม่ในกรณีที่มันเขียนทับ reg3 ก่อนที่คำสั่งก่อนหน้าจะถูกอ่าน

ดังนั้นจึงมีผู้ลงทะเบียนจริงประมาณ 160 คน ดังนั้นคำสั่งง่ายๆข้างต้นจึงเปลี่ยนเป็น "regX = reg1 + reg2 และโปรดจำไว้ว่า regX มี reg3" หากไม่มีการลงทะเบียนเปลี่ยนชื่อการดำเนินการตามคำสั่งจะตายอย่างแน่นอนในน้ำ


1

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

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

ฉันมีความคิดสำหรับคำตอบนี้จากการดูบางคำพูดของ Ivan Godard เกี่ยวกับ Mill CPU ส่วนหนึ่งของนวัตกรรมของ Mill CPU คือคุณไม่สามารถส่งออกไปยังรีจิสเตอร์โดยพลการเอาท์พุททั้งหมดจะถูกส่งไปยัง register stack หรือ "belt" ซึ่งจะช่วยลดปัญหาการจัดเส้นทางเนื่องจากคุณรู้ว่าจะไปที่ใด โปรดทราบว่าพวกเขายังมีปัญหาการกำหนดเส้นทางสำหรับการรับอินพุตลงในหน่วยเลขคณิต

โปรดดูThe Mill CPU Architecture - Belt (2 of 9)สำหรับคำแถลงปัญหาและแนวทางแก้ไขของ Mill


"พวกเขาจะต้องสามารถป้อนข้อมูลจากทุกการลงทะเบียนและส่งออกไปยังทุกการลงทะเบียน" - ฉันคาดหวังว่านี่จะถูกนำไปใช้กับรถบัสโดยทั่วไปไม่จำเป็นต้องมีการเชื่อมต่อแยกต่างหากกับ ALU (s) สำหรับการลงทะเบียนทุกครั้ง
253751

1
@immibis: หากคุณต้องการย้ายข้อมูลใน 300 picoseconds รถบัสจะไม่ทำ และถ้าคุณต้องการย้ายข้อมูลจำนวนมากรอบ ๆ (ตัวอย่างเช่นเพื่อดำเนินการสามคำสั่งด้วยสองตัวถูกดำเนินการและหนึ่งผลลัพธ์ในรอบเดียวกัน) บัสจะไม่ทำงานอย่างแน่นอน
gnasher729

0

สำหรับ MIPS ISA, Hennessy และ Patterson, องค์กรคอมพิวเตอร์และการออกแบบรุ่นที่ 4 หน้า 176, ตอบคำถามเฉพาะนี้โดยตรง:

เล็กกว่าเร็วกว่า ความปรารถนาด้านความเร็วคือเหตุผลที่ MIPS มีผู้ลงทะเบียนมากกว่า 32 คน

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