ข้าม​ไป​ยัง​เนื้อหา

เอกสาร​สอง​หน้า​ของ Epic ให้​คำ​ตอบ​คนละ​อย่าง​เรื่อง TObjectPtr และ​ทั้ง​คู่​ถูก

คุณ​เปิด​โปร​เจ​กต์ UE5 แล้ว​เจอ UPROPERTY ที่​เป็น raw pointer อยู่​สอง​ร้อย​จุด

คำถาม​คือ​ควร​แปลง​เป็น TObjectPtr ไหม และ​เอกสาร​ทางการ​ของ Epic ตอบ​คำถาม​นี้​ไว้​สอง​แบบ บน​หน้า​คนละ​หน้า

Migration Guide อธิบาย​ว่าการ​เลือก​เป็น​เรื่อง​ที่​ทำ​หรือ​ไม่​ทำ​ก็ได้ เพราะ TObjectPtr จะ​กลาย​เป็น raw pointer ใน​บิ​ลด์​ที่​ไม่ใช่ editor ซึ่ง​จะ

not impact the behavior or performance of a shipped product

หน้า incremental garbage collection เขียน​ถึง​เรื่อง​เดียวกัน​ว่า​มัน​เป็น​เรื่อง

critical to convert all UPROPERTY instances to use TObjectPtr

และ​บอก​ผล​ของ​การ​ไม่​ทำ​ไว้​ตรง ๆ ว่า GC อาจ

reclaim some of UObjects’ memory too early

อ่าน​เรียง​กัน​สาม​ประโยค​แล้ว​มัน​ไป​คนละ​ทาง ประโยค​แรก​บอก​ว่า​ไม่มี​ผล ประโยค​ที่​สาม​บอก​ว่า หน่วย​ความ​จำหาย​ไป​ตอน​ที่​ยัง​ใช้​อยู่

ทั้ง​สาม​ประโยค​จริง และ​หน้า​นี้​อธิบาย​ว่า​ทำไม

ประโยค​ของ Migration Guide ตอบ​คำถาม​ว่า “ตัวแทน​ของ pointer ใน​หน่วย​ความ​จำ​ต่าง​กัน​ไหม”

คำ​ตอบ​คือ​ไม่​ต่าง ใน​บิ​ลด์​ที่​ไม่ใช่ editor TObjectPtr ถูก​ลด​รูป​เป็น raw pointer จริง ๆ ขนาด​เท่า​กัน การ​อ่าน​ค่า​เท่า​กัน ไม่มี​ชั้น​อ้อม​เพิ่ม

ประโยค​ของ​หน้า GC ตอบ​คำถาม​อีก​ข้อ​หนึ่ง​ที่ ยัง​ไม่มี​อยู่​ตอน​ที่​ประโยค​แรก​ถูก​เขียน: “ตอน GC กำลัง​เดิน​อยู่ อะไร​บอก​มัน​ว่า​คุณ​เพิ่ง​ผูก reference ใหม่”

กลไก​ที่​ตัดสิน​คือ write barrier ไม่ใช่​ตัวแทน​ของ pointer

เอกสาร​เขียน​ไว้​ว่า การ​กำหนด​ค่า​ให้ UPROPERTY ที่​เป็น TObjectPtr นั้น

immediately marks the object as reachable

นั่น​คือ write barrier — จุด​ที่ code ของ​เอน​จิน​ได้​แทรก​ตัว​เข้าไป​ใน​จังหวะ​ที่​คุณ​เขียน​ค่า

raw pointer ไม่มี​จังหวะ​นั้น การ​กำหนด​ค่า​ให้ raw pointer คือ​การ​ย้าย byte แปด​ตัว ไม่มี​ฟังก์ชัน​ไหน​ถูก​เรียก จึง​ไม่มี​ที่​ให้​เอน​จิน​แทรก​อะไร​ลง​ไป​ได้​เลย

ทำไม GC แบบ​เดิม​ไม่​ต้องการ barrier แต่​แบบ​ใหม่​ต้องการ

หัวข้อ​ที่​มีชื่อ​ว่า “ทำไม GC แบบ​เดิม​ไม่​ต้องการ barrier แต่​แบบ​ใหม่​ต้องการ”

GC แบบ​เดิม​ของ Unreal เดิน​หา reference ให้​จบ​ภายใน​จังหวะ​เดียว โลก​ไม่​ขยับ​ระหว่าง​นั้น สิ่ง​ที่​มัน​เห็น​ตอน​เริ่ม​จึง​เป็น​สิ่ง​เดียว​กับ​ตอน​จบ

incremental reachability แบ่ง​งาน​นั้น​ออก​เป็น​หลาย​รอบ​คั่น​ด้วย​เฟรม​ของ​เกม · code ของ​คุณ​รัน​อยู่​ระหว่าง​รอบ และ​ผูก reference ใหม่​ได้

สมมติ​ว่า GC เดิน​ผ่า​นอ็​อบ​เจ​กต์ A ไป​แล้ว​ใน​รอบ​แรก โดย​ตอน​นั้น A ยัง​ไม่​ชี้​ไป​หา B · เฟรม​ถัด​มา code ของ​คุณ​สั่ง A->Target = B · GC ไม่​ได้​ย้อน​กลับ​ไป​ดู A อีก มัน​จึง​ไม่​เคย​เห็น​เส้น​ทาง​ที่​ไป​ถึง B และ​ถ้า​ไม่มี​ใคร​อื่น​ถือ B ไว้ B จะ​ถูก​เก็บ​กวาด ทั้ง​ที่ A กำลัง​ชี้​อยู่

barrier คือ​สิ่ง​ที่​ทำให้​บรรทัด A->Target = B บอก GC ได้​ว่า B ยัง​เข้า​ถึง​ได้​นะ · และ​มัน​จะ​บอก​ได้​ก็​ต่อ​เมื่อ Target เป็น TObjectPtr

เงื่อนไข​ข้อ​เดียว​ที่​ตัดสิน​ว่า​ประโยค​ไหน​ใช้​กับ​คุณ

หัวข้อ​ที่​มีชื่อ​ว่า “เงื่อนไข​ข้อ​เดียว​ที่​ตัดสิน​ว่า​ประโยค​ไหน​ใช้​กับ​คุณ”

ประโยค​ของ​หน้า GC ผูก​กับ​ฟีเจอร์​ที่ ต้อง​เปิด​เอง ไม่ใช่​ค่า​ตั้ง​ต้น · เอกสาร​ระบุ​ว่า Epic

initially releasing this feature as experimental

การ​เปิด​ใช้​ทำ​ผ่าน​ค่า​ใน DefaultEngine.ini คือ gc.AllowIncrementalReachability กับ gc.AllowIncrementalGather

โปร​เจ​กต์​ของ​คุณประโยค​ที่​ใช้
ไม่​ได้​เปิด incremental reachabilityMigration Guide ถูก การ​เลือก​ไม่​กระทบ shipped product
เปิด incremental reachabilityหน้า GC ถูก UPROPERTY ที่​เป็น raw pointer กลาย​เป็น​บั๊ก​หน่วย​ความ​จำ
ข้อ​ที่​อันตราย​คือ​ลำดับ​เวลา ไม่ใช่​ตัว​เลือก

คน​ที่​ตัดสิน​ใจ​ว่า “ไม่​ต้อง​แปลง​ก็ได้” มัก​ตัดสิน​ตอน​ย้าย​โปร​เจ​กต์​จาก UE4 ซึ่ง​เป็น​ตอน​ที่ Migration Guide ถูก​อ่าน​มาก​ที่สุด · ส่วน​คน​ที่​เปิด incremental reachability คือ​คน​ที่​กำลัง ไล่​แก้ hitch ของ GC ซึ่ง​เกิด​ทีหลัง​เป็น​ปี และ​มัก​ไม่ใช่​คน​เดียวกัน

สวิตช์​สอง​บรรทัด​ใน DefaultEngine.ini จึง​เปลี่ยน​สถานะ​ของ UPROPERTY ทุก​ตัว​ใน​โปร​เจ​กต์ ย้อน​หลัง โดย​ไม่มี​อะไร​เตือน เพราะ code ที่​เขียน​ไว้​เดิม​ยัง​คอม​ไพล์​ผ่าน​เหมือน​เดิม​ทุก​บรรทัด

มี​คำ​อธิบาย​ที่​แพร่​หลาย​ว่า TObjectPtr “ไม่​ได้​ถือ ownership มัน​เป็น​แค่​ตัว​ช่วย​ตอน editor”

ครึ่ง​หลัง​พอใช้​ได้ ครึ่ง​แรก​ผิด · หน้า Object Pointers เรียก TObjectPtr ที่​เป็น UPROPERTY ว่า​เป็น

strong reference to an object

สิ่ง​ที่​ทำให้​มัน​เป็น strong reference คือ​การ​ที่​มัน​เป็น UPROPERTY และ​ถูก reflection มอง​เห็น ไม่ใช่​ตัว TObjectPtr เอง · raw pointer ที่​เป็น UPROPERTY ก็​ถือ reference เหมือน​กัน ความ​ต่าง​อยู่​ที่ barrier ไม่​ได้​อยู่​ที่ ownership

ทุก​ข้อ​อ้าง​ข้าง​บน​อยู่​บน 3 หน้า​ของ dev.epicgames.com และ​เปิด​อ่าน​ได้​ฟรี ไม่​ต้อง​มี​สิทธิ์​เข้า​ซอร์ส

  1. เปิด​สอง​หน้า​แรก แล้ว​ค้น​ประโยค​ที่​ยก​มา​ใน​หน้า​นั้น​ตรง ๆ
  2. ใน​โปร​เจ​กต์​ของ​คุณ ค้น DefaultEngine.ini ด้วย​คำ​ว่า AllowIncrementalReachability ถ้า​ไม่มี แปล​ว่า​คุณ​อยู่​แถว​แรก​ของ​ตาราง
  3. เปิด file .generated.h ของ class ที่​มี UPROPERTY แล้ว​ดู​ว่า reflection เก็บ​อะไร​ไว้​บ้าง
  • ไม่มี​บรรทัด​ไหน​ของ​หน้า​นี้​รัน Unreal ทุก​ข้อ​อ้าง​เป็นการ​ยก​เอกสาร​ของ Epic ต่าง​จาก​หน้า​อื่น​ของ​เว็บ​นี้​ที่​ตัวเลข​มา​จาก​การ​รัน​จริง
  • ไม่​ได้​วัด​ว่า GC เก็บ​ของ​เร็ว​เกิน​ไป​จริง​ไหม เอกสาร​บอก​ว่า​เป็น​ไป​ได้ หน้า​นี้​ไม่​ได้​ทำให้​มัน​เกิด
  • ไม่​ได้​วัด​ต้นทุน​ของ barrier เอกสาร​ไม่​ได้​ให้​ตัวเลข​นั้น และ​เว็บ​นี้​ไม่​เดา​ตัวเลข

สิ่ง​ที่​ทำ​แทน​คือ ตัว​วัด​ที่​ยิง​ทั้ง 3 หน้า​แล้ว​ยืนยัน​ว่า​ประโยค​ที่​ยก​มายัง​อยู่​จริง · อยู่​ที่ scripts/unreal-citations/ รัน​ด้วย npm run verify:unreal-citations · วัน​ที่ Epic เขียน​ประโยค​ใหม่ ด่าน​นี้​แดง และ​หน้า​นี้​จะ​ถูก​ทบทวน​ก่อน​ที่​คุณ​จะ​เจอ​คำ​ที่​ไม่มี​อยู่​แล้ว