B-Tree ใน Factorio
(razberry.substack.com)- หลังจากอ่านบท B-Tree ในชมรมอ่านหนังสือ Database Internals ผู้เขียนได้นำโครงสร้างข้อมูลไปสร้างขึ้นไม่ใช่ด้วยโค้ด แต่ด้วยโครงสร้างโรงงานใน Factorio เพื่อยืนยันแนวคิดในเชิงภาพ
- BST จะแยกกิ่งซ้าย·ขวาได้ก็ต่อเมื่อคีย์สามารถเรียงลำดับได้ และหากค่ากระจุกไปด้านใดด้านหนึ่ง ประสิทธิภาพการค้นหาอาจตกลงมาเหลือระดับเดียวกับลิสต์เชิงเส้น
- สำหรับการจัดเก็บข้อมูลบนดิสก์ ค่าใช้จ่ายในการปรับสมดุลใหม่ ของ BST และการอ่านหลายเพจเป็นภาระ ส่วน B-Tree เป็นโครงสร้างที่ลดปัญหานี้โดยเก็บหลายคีย์ไว้ในโหนดเดียว
- การสร้างใน Factorio ใช้หีบไม้และแขนกรองสีม่วงแทนโหนดกับการเปรียบเทียบ กำหนดลำดับการเรียงของไอเท็มขึ้นเองเพื่อสร้างเส้นทางค้นหา
- เวอร์ชัน B-Tree ใช้ 3 คีย์และ 4 พอยน์เตอร์ ต่อโหนด ทำให้ใน 2 ระดับเก็บคีย์ได้มากกว่า BST อย่างมาก แต่ยังเหลือปัญหาเรื่องการแทนค่าและการเรียงลำดับด้วยมือ
ความแตกต่างระหว่าง BST และ B-Tree
- Binary Search Tree (BST) คือแต่ละโหนดเก็บคีย์หนึ่งตัว คีย์ที่ต่ำกว่าจะถูกส่งไปยังโหนดซ้าย และคีย์ที่สูงกว่าจะถูกส่งไปยังโหนดขวา
- ตัวอย่างเริ่มจากคีย์ราก
8, ซ้าย3, ขวา10 - ทำงานได้เฉพาะกับ ค่าที่เรียงลำดับได้ ซึ่งสามารถเปรียบเทียบได้ว่าค่าสูงหรือต่ำ
- ตัวอย่างเริ่มจากคีย์ราก
- หากมีการเพิ่มค่าไปด้านใดด้านหนึ่งมากเกินไป สมดุลของ BST จะเสีย
- ในกรณีแย่ที่สุด จะเกือบเหมือน ลิสต์เรียงลำดับเชิงเส้น เช่น
8 -> 10 -> 14 - สามารถแก้ความไม่สมดุลได้โดยใช้
10เป็น pivot วางไว้เป็นราก แล้วจัด8,14ไว้สองฝั่ง
- ในกรณีแย่ที่สุด จะเกือบเหมือน ลิสต์เรียงลำดับเชิงเส้น เช่น
- สำหรับการจัดเก็บข้อมูลบนดิสก์ BST เสียเปรียบ
- หากต้องคอยรักษาสมดุลใหม่อย่างต่อเนื่อง จะต้องอัปเดตดิสก์และพอยน์เตอร์บ่อยครั้ง
- โหนดข้างเคียงอาจถูกเก็บอยู่คนละเพจ ทำให้การค้นหาเพียงครั้งเดียวอาจต้องอ่านหลายเพจ
- B-Tree เก็บหลายคีย์ไว้ในโหนดเดียว และใช้พอยน์เตอร์จำนวน
จำนวนคีย์ + 1ชี้ไปยังโหนดลูก- โหนดตัวอย่าง
[17 | 24]จะแยกไปยังโหนดลูกสามกลุ่ม ได้แก่คีย์ที่น้อยกว่า17, คีย์ที่อยู่ระหว่าง17กับ24, และคีย์ที่มากกว่า24
- โหนดตัวอย่าง
ต้นไม้ค้นหาที่สร้างไว้ใน Factorio
- Factorio เป็น เกมสร้างโรงงาน และในการสร้างนี้ แต่ละโหนดของต้นไม้ถูกแทนด้วยโครงสร้างในเกม
- เริ่มจากการสร้าง BST แบบง่ายก่อน
- แต่ละโหนดมี หีบไม้ ที่เก็บคีย์หนึ่งตัว และมีสองเส้นทางที่เชื่อมไปยังโหนดอื่น
- เนื่องจากไม่มีวิธีเปรียบเทียบพื้นฐานระหว่างวัตถุดิบ จึงกำหนดเกณฑ์การเรียงลำดับขึ้นเองเป็น
wood, coal, stone, brick, copper, iron, steel - แขนกรองสีม่วงทำหน้าที่ตรวจสอบการเปรียบเทียบ
- ที่โหนดแรก แขนหนึ่งตรวจว่าไอเท็มเท่ากับ
brickหรือไม่ - แขนที่สองตรวจว่าน้อยกว่า
brickหรือไม่ เช่นwood, coal, stone - แขนที่สามกรองค่าที่มากกว่า เช่น
copper, iron, steel
- ที่โหนดแรก แขนหนึ่งตรวจว่าไอเท็มเท่ากับ
- ด้านขวาบนยังมี garbage collector สำหรับนำไอเท็มที่เข้ามาผิดบนสายพานลำเลียงออกไปด้วย
- การสร้าง B-Tree ต้องใช้โครงสร้างมากขึ้นในหนึ่งโหนด
- แต่ละโหนดมี 3 คีย์, แขนกรอง 3 อัน, หีบไม้ 3 ใบ และพอยน์เตอร์ไปยังลูก 4 ตัว
- สามารถบรรจุข้อมูลได้มากขึ้นที่ความลึกเท่ากัน
- ใน 2 ระดับ BST เก็บได้ 2 คีย์ แต่ B-Tree เก็บได้ 12 คีย์
- ใน 3 ระดับ B-Tree เพิ่มขึ้นได้ถึง 48 คีย์
- เนื่องจากไม่อยากเลือกและเรียงไอเท็ม 48 ชิ้นใน Factorio ด้วยมือ จึงปล่อย B-Tree ว่างไว้จนกว่าจะหาวิธีแทนค่าที่ดีกว่าได้
- มีการเปรียบเทียบ BST กับ B-Tree แบบวางข้างกัน และมีวิดีโอ YouTube ประกอบด้วย
1 ความคิดเห็น
ความคิดเห็นใน Hacker News
เป็นการออกแบบที่ไม่มีประสิทธิภาพนัก แต่การนำ ทฤษฎีวิทยาการคอมพิวเตอร์ มาใช้ใน Factorio ก็ย่อมหมายถึงการเล่นในแบบที่ไม่ใช่วิธีที่เหมาะสมที่สุดอยู่แล้ว
Factorio ไม่ได้ถูกสร้างมาเพื่อให้เอา B-Tree มาอวด และท้ายที่สุดเครื่องมือต่าง ๆ ก็ถูกออกแบบมาให้เล่น Factorio นั่นเอง
เมตาที่น่าค้นหาใน Factorio น่าจะเป็นการออกแบบ “mixed belt”
บางแบบแค่รับไอเท็มใหม่เข้ามาตามอัตราส่วนที่กำหนด บางแบบเมื่อเสียสมดุลแล้วก็ปรับกลับให้สมดุลจริง ๆ ด้วย โดยส่วนตัวแล้วชอบอันนี้ที่สุด: https://www.youtube.com/watch?v=7Gt5Zx0bsOQ
ตัวอย่างนี้ใช้ตรรกะวงจรในเกม แต่ในฟอรัม Factorio ก็มีหมวดที่ไม่ใช้วงจรด้วย: https://forums.factorio.com/viewforum.php?f=202
สิ่งที่น่าสนใจคืออ็อบเจกต์ “fish” ใน Factorio เป็นไอเท็มมุกตลกที่ไม่มีประโยชน์ และเพราะมันไม่ได้ถูกใช้ที่ไหน บางครั้งจึงถูกใช้เป็นค่า null, แฟล็กบอกว่า belt วิ่งครบหนึ่งรอบแล้ว หรือเครื่องมือดีบัก: https://forums.factorio.com/viewtopic.php?p=544302#p544302
แบบนั้นไม่ใช่แค่อ็อบเจกต์ที่จะ insert/search เท่านั้น แต่ตัว B-Tree เองก็สามารถย้ายไปมาด้วย conveyor belt และ inserter ได้
อาจเขียนฟังก์ชันค้นหาแบบ recursive ด้วยลูป conveyor belt ที่วิ่งผ่านโรงงาน โดยค่อย ๆ แกะต้นไม้ออกทีละระดับจนถึง leaf แล้วตัดลูปเพื่อส่งผลลัพธ์ออกมาได้
นี่เป็นโมเดลการรันที่น่าสนใจซึ่งใกล้กับ data flow มากกว่า JavaScript มาตรฐาน ควรอนุญาตให้ conveyor belt, inserter และโรงงานต่าง ๆ ชี้ไปยังอ็อบเจกต์ JSON พื้นฐานเดียวกันด้วยหลาย reference เพื่อให้เกิด “quantum tunneling” หรือ “action at a distance” ไหม? มันอาจมีประโยชน์ แต่ตามธรรมเนียม Factorio ถือว่าไอเท็มทางกายภาพแต่ละชิ้นมีอัตลักษณ์เฉพาะตัว ดังนั้นการไม่รองรับหลาย reference อาจจะ “สมจริง” กว่า หรือไม่ก็อาจให้วิจัยเทคโนโลยี “Quantum Tunneling JSON” ก่อน แล้วอนุญาตให้สร้างหลาย reference ได้เฉพาะใน “JSON Reference Entangler Factory” เท่านั้น
น่าจะเป็นไปได้ที่จะปรับเอาต์พุตด้วยการให้น้ำหนักกับความหนาแน่นของทรัพยากรที่ไปถึงตำแหน่งหนึ่ง เมื่อดูจากกลไกตรงนี้ [2] ก็น่าจะใช้การรวม·แยก และความเร็ว belt สามระดับ เพื่อสร้างการตัดสินใจแบบถ่วงน้ำหนักด้วยความหนาแน่นได้
[1] https://www.asimovinstitute.org/wp-content/uploads/2019/04/N...
[2] https://wiki.factorio.com/Belt_transport_system#Splitters
เกมที่เรียกร้องสมองเพื่อแลกกับตัวเลขบนหน้าจออยู่ท้ายสุดในลิสต์ของฉัน ฉันอยากเรียนรู้อะไรใหม่ ๆ
มันอาจมีองค์ประกอบแบบปริศนา และเราก็อาจกำหนดว่ามันสนุกได้ แต่การเรียนก็น่าจะกำหนดให้สนุกได้เหมือนกันไม่ใช่หรือ
งานเจ๋งมาก
มีคนบอกว่ากำลังอ่าน “Database Internals” ในบุ๊กคลับ และสัปดาห์นี้เป็นบทที่ 2 ว่าด้วย B-Tree
หมายเหตุ แม้ปิดรับสมัครแล้ว แต่ถ้าต้องการก็หา Database Internals มาอ่าน แล้วตามตารางและโน้ตที่นี่แบบ “อ่านอย่างเดียว” ไปด้วยได้: https://eatonphil.com/2023-database-internals.html
เหตุผลที่ว่า “binary search tree ไม่เหมาะกับสตอเรจบนดิสก์” นั้นใช้ได้กับ สตอเรจในหน่วยความจำ ด้วย
การค้นหาในโหนด B-Tree หนึ่งโหนดมักเร็วกว่าการไล่ตามพอยน์เตอร์จำนวนเท่ากันใน binary tree แน่นอนว่าความซับซ้อนในการ implement จะสูงขึ้น แต่ถ้าไม่ได้เขียน C โดยปกติก็คงไม่ลงมือ implement map แบบ tree-based เองอยู่แล้ว
ยังดัดแปลงได้ด้วย เช่น ใส่รายการใน internal node ให้มากขึ้น แล้วเก็บค่าไว้เฉพาะที่ leaf เท่านั้น ถ้าไม่ได้ทำแค่ set แต่ทำ map ด้วยน่ะนะ ถ้าเชื่อมไปถึงโหนดข้างเคียงด้วย ก็แทบจะใกล้เคียงกับ skip list แล้ว
[0] https://abseil.io/blog/20190812-btree
[1] https://opensource.googleblog.com/2013/01/c-containers-that-...
ไม่รู้ทำไมต้องมี คอนเทนต์ Factorio โผล่มาตรงนี้ ทำให้อยากกลับไปจมกับมันอีกสัก 100 ชั่วโมง ปีนี้ก็มีเกมดี ๆ ที่น่าเล่นเยอะเกินพออยู่แล้ว
ทั้งหมดนี้น่าจะทำได้ด้วย splitter และดูเหมือนไม่จำเป็นต้องใช้หีบหรือ filter inserter คำอธิบายดีนะ
มันไม่ใช่แค่การแยกเอาต์พุตออกเป็นหลายสาย หีบในที่นี้แทนไอเท็มที่เก็บอยู่ใน “โหนด” นั้นของ B-Tree ที่จัดวางแบบสองมิติ
ยังไม่มีเวลาดูวิดีโอ แต่จากบทความและภาพหน้าจอ ดูเหมือนว่าตรรกะที่เกี่ยวข้องจะอยู่กับ inserter เพื่อรักษาคุณสมบัติ “เรียงลำดับ” ของ tree โดยส่งไอเท็มไปตามเส้นทางของโหนดลูกที่เหมาะสม
ดูจากการเลือกค่า key ในต้นฉบับ การแยกด้วย splitter ก็น่าจะทำได้ แต่เท่าที่จำได้ splitter รับ filter ได้แค่อย่างเดียว ดังนั้นแต่ละจุดแตกแขนงต้องใช้หลายตัว หมายความว่าต้องใช้เท่ากับจำนวนไอเท็มของจุดแตกแขนงนั้น ส่วน filter inserter อนุญาตให้มีหลาย filter ได้ จึงเหมาะกว่าในกรณีนี้ และก็เห็นได้ในภาพหน้าจอแรกด้วย
แน่นอนว่าจะเลิกใช้ดีไซน์ B-Tree ทั้งหมด แล้วใช้ splitter n ตัวเพื่อจัดเรียงลงหีบ n ใบก็ได้ แต่นั่นไม่สนุก และดูไม่ใช่สิ่งที่ต้นฉบับตั้งใจ
filter ของ splitter จะส่งไอเท็มชนิดเดียวไปด้านหนึ่ง และส่งที่เหลือไปอีกด้าน แต่ตัวอย่างนี้ต่างออกไป เพราะมีหลายชนิดไปด้านหนึ่ง และหลายชนิดไปอีกด้าน
สงสัยว่า Factorio เป็นเกมที่ดีขนาดนั้นจริงไหม ทุกคนบอกว่าดี แต่ธีม สร้างโรงงาน ดูน่าเบื่อนิด ๆ และกังวลว่าเกมจะซ้ำซากเกินไป
เจ๋งมากจริง ๆ แต่ขอพูดในฐานะคนที่อยากเขียนบทความเหมือนกันว่า การไม่ใช้ ตัวพิมพ์ใหญ่ ตอนขึ้นต้นประโยคทำให้รู้สึกเสียสมาธิพอสมควร
นึกว่าจะ implement ด้วย ระบบวงจร ของ Factorio