ลำดับตามแถวและตามคอลัมน์

ในด้านการคำนวณ การเรียงลำดับตามแถว ( row-major order)และการเรียงลำดับตามคอลัมน์ (column-major order)เป็นวิธีการจัดเก็บอาร์เรย์หลายมิติในหน่วยเก็บข้อมูลเชิงเส้น เช่นหน่วยความจำเข้าถึงแบบสุ่ม (RAM )
ความแตกต่างระหว่างลำดับการจัดเรียงข้อมูลอยู่ที่ว่าองค์ประกอบของอาร์เรย์อยู่ติดกันในหน่วยความจำอย่างไร ในลำดับแบบแถวหลัก (row-major order) องค์ประกอบที่อยู่ติดกันในแถวจะอยู่ติดกัน ในขณะที่ในลำดับแบบคอลัมน์หลัก (column-major order) องค์ประกอบที่อยู่ติดกันในคอลัมน์ก็จะอยู่ติดกันเช่นกัน แม้ว่าคำศัพท์เหล่านี้จะหมายถึงแถวและคอลัมน์ของอาร์เรย์สองมิติ เช่นเมทริกซ์แต่ลำดับเหล่านี้สามารถนำไปใช้กับอาร์เรย์ที่มีมิติใดๆ ก็ได้ โดยสังเกตว่าคำว่าแถวหลักและคอลัมน์หลักนั้นเทียบเท่ากับลำดับแบบ เลกซิโค กราฟิก (lexicographic order) และโคเลกซิโคกราฟิก (colexicographic order)ตามลำดับ เมทริกซ์ซึ่งโดยทั่วไปแล้วจะถูกแสดงเป็นชุดของเวกเตอร์แถวหรือคอลัมน์ การใช้วิธีนี้จะทำให้ข้อมูลถูกจัดเก็บอย่างมีประสิทธิภาพในรูปของเวกเตอร์ที่ติดกันหรือส่วนประกอบของเวกเตอร์ที่ติดกัน วิธีการจัดเก็บข้อมูลแบบนี้เรียกว่าAoS (Array of Storage) และ SoA ( Self-Angagement Order) ตามลำดับ
รูปแบบข้อมูลมีความสำคัญอย่างยิ่งต่อการส่งผ่านอาร์เรย์ระหว่างโปรแกรมที่เขียนด้วยภาษาโปรแกรมที่แตกต่างกันอย่างถูกต้อง นอกจากนี้ยังมีความสำคัญต่อประสิทธิภาพเมื่อวนลูปผ่านอาร์เรย์ เนื่องจากซีพียูสมัยใหม่ประมวลผลข้อมูลแบบลำดับได้มีประสิทธิภาพมากกว่าข้อมูลแบบไม่ลำดับ ซึ่งส่วนใหญ่เป็นผลมาจากการแคชของซีพียูที่ใช้ประโยชน์จากความใกล้เคียงเชิงพื้นที่ ของ การอ้างอิง[ 1 ]ยิ่งไปกว่านั้น การเข้าถึงแบบต่อเนื่องทำให้สามารถใช้ คำสั่ง SIMDที่ทำงานกับเวกเตอร์ของข้อมูลได้ ในสื่อบางประเภท เช่นการจัดเก็บข้อมูลเทปแม่เหล็กการเข้าถึงแบบลำดับจะเร็วกว่าการเข้าถึงแบบไม่ลำดับหลายเท่า
คำอธิบายและตัวอย่าง
คำว่า row-major และ column-major มาจากศัพท์เฉพาะที่เกี่ยวข้องกับการเรียงลำดับวัตถุ วิธีทั่วไปในการเรียงลำดับวัตถุที่มีคุณสมบัติหลายอย่างคือ การจัดกลุ่มและเรียงลำดับวัตถุเหล่านั้นตามคุณสมบัติหนึ่งก่อน จากนั้นภายในแต่ละกลุ่ม ก็จัดกลุ่มและเรียงลำดับวัตถุเหล่านั้นตามคุณสมบัติอื่นต่อไปเรื่อยๆ หากมีคุณสมบัติมากกว่าหนึ่งอย่างที่เกี่ยวข้องกับการเรียงลำดับ คุณสมบัติแรกจะเรียกว่าmajorและคุณสมบัติสุดท้าย จะเรียกว่า minorหากมีคุณสมบัติสองอย่างที่เกี่ยวข้องกับการเรียงลำดับ ก็เพียงพอที่จะตั้งชื่อเฉพาะคุณสมบัติ major เท่านั้น
ในกรณีของอาร์เรย์ คุณลักษณะต่างๆ คือดัชนีตามแต่ละมิติ สำหรับเมทริกซ์ในสัญลักษณ์ทางคณิตศาสตร์ ดัชนีแรกระบุแถวและดัชนีที่สองระบุคอลัมน์เช่น เมทริกซ์ = ...การป้อนอยู่ในแถวแรกและคอลัมน์ที่สอง ธรรมเนียมนี้ถูกนำมาใช้กับไวยากรณ์ในภาษาโปรแกรม[ 2 ] แม้ว่า ดัชนีมัก จะเริ่มต้นที่ 0 แทนที่จะเป็น 1 [ 3 ]
แม้ว่าแถวจะถูกระบุด้วย ดัชนี แรกและคอลัมน์ด้วย ดัชนี ที่สองแต่ก็ไม่ได้หมายความถึงลำดับการจัดกลุ่มระหว่างมิติแต่อย่างใด การเลือกวิธีการจัดกลุ่มและเรียงลำดับดัชนี ไม่ว่าจะเป็นวิธีเรียงตามแถวหรือเรียงตามคอลัมน์ จึงเป็นเรื่องของข้อตกลงร่วมกัน สามารถใช้คำศัพท์เดียวกันนี้กับอาร์เรย์ที่มีมิติสูงกว่าได้เช่นกัน การจัดกลุ่มตามแถวจะเริ่มต้นจาก ดัชนี ซ้ายสุด และการจัดกลุ่มตามคอลัมน์จะเริ่มต้นจากดัชนี ขวาสุดทำให้เกิดลำดับแบบเลกซิโคกราฟิกและโคเลกซิโคกราฟิก (หรือโคเล็กซ์)ตามลำดับ
ตัวอย่างเช่น อาร์เรย์
สามารถจัดเก็บได้สองวิธี:
| ที่อยู่ | ลำดับแถวหลัก | ลำดับคอลัมน์หลัก |
|---|---|---|
| 0 | ||
| 1 | ||
| 2 | ||
| 3 | ||
| 4 | ||
| 5 |
ภาษาโปรแกรมแต่ละภาษามีวิธีการจัดการเรื่องนี้แตกต่างกัน ใน ภาษา Cอาร์เรย์หลายมิติจะถูกจัดเก็บในลำดับตามแถว และดัชนีของอาร์เรย์จะถูกเขียนเรียงตามแถวก่อน (ลำดับการเข้าถึงตามพจนานุกรม):
ที่อยู่x + N_x*y | เข้าถึงA[y][x] | ค่า |
|---|---|---|
| 0 | A[0][0] | |
| 1 | A[0][1] | |
| 2 | A[0][2] | |
| 3 | A[1][0] | |
| 4 | A[1][1] | |
| 5 | A[1][2] |
ในทางกลับกัน ในภาษา Fortranอาร์เรย์จะถูกจัดเก็บในลำดับคอลัมน์หลัก ในขณะที่ดัชนีของอาร์เรย์ยังคงเขียนตามแถวก่อน (ลำดับการเข้าถึงแบบพจนานุกรม):
ที่อยู่y + N_y*(x-1) | เข้าถึงA(y,x) | ค่า |
|---|---|---|
| 1 | A(1,1) | |
| 2 | A(2,1) | |
| 3 | A(1,2) | |
| 4 | A(2,2) | |
| 5 | A(1,3) | |
| 6 | A(2,3) |
โปรดสังเกตว่าการใช้A[i][j]การจัดทำดัชนีแบบหลายขั้นตอนเช่นในภาษา C ซึ่งแตกต่างจากสัญกรณ์ที่เป็นกลางเช่นA(i,j)ในภาษา Fortran เกือบจะหมายความว่าลำดับจะเรียงตามแถวโดยหลีกเลี่ยงไม่ได้ด้วยเหตุผลทางไวยากรณ์ กล่าวคือ เพราะสามารถเขียนใหม่ได้เป็น(A[i])[j]และA[i]ส่วนของแถวยังสามารถกำหนดให้กับตัวแปรตัวกลางซึ่งจะถูกจัดทำดัชนีในนิพจน์แยกต่างหากได้ (ไม่ควรสันนิษฐานถึงนัยยะอื่นใด เช่น Fortran ไม่ใช่ภาษาที่เรียงตามคอลัมน์เพียงเพราะสัญกรณ์ของมัน และแม้แต่นัยยะข้างต้นก็สามารถหลีกเลี่ยงได้โดยเจตนาในภาษาใหม่)
ในการใช้ลำดับแบบคอลัมน์ในสภาพแวดล้อมแบบแถว หรือในทางกลับกัน ไม่ว่าจะด้วยเหตุผลใดก็ตาม วิธีแก้ปัญหาอย่างหนึ่งคือการกำหนดบทบาทที่ไม่เป็นไปตามแบบแผนให้กับดัชนี (โดยใช้ดัชนีแรกสำหรับคอลัมน์และดัชนีที่สองสำหรับแถว) และอีกวิธีหนึ่งคือการหลีกเลี่ยงไวยากรณ์ของภาษาโดยการคำนวณตำแหน่งในอาร์เรย์หนึ่งมิติอย่างชัดเจน แน่นอนว่า การเบี่ยงเบนจากแบบแผนย่อมก่อให้เกิดต้นทุนที่เพิ่มขึ้นตามระดับของการโต้ตอบที่จำเป็นกับคุณสมบัติของภาษาและโค้ดอื่นๆ ไม่เพียงแต่ในรูปแบบของความเสี่ยงต่อข้อผิดพลาดที่เพิ่มขึ้น (เช่น ลืมกลับลำดับการคูณเมทริกซ์ การกลับไปใช้แบบแผนระหว่างการบำรุงรักษาโค้ด ฯลฯ) แต่ยังรวมถึงในรูปแบบของการต้องจัดเรียงองค์ประกอบใหม่ ซึ่งทั้งหมดนี้ต้องชั่งน้ำหนักกับวัตถุประสงค์ดั้งเดิมใดๆ เช่น การเพิ่มประสิทธิภาพ การรันลูปแบบแถวต่อแถวเป็นที่นิยมในภาษาแบบแถว เช่น C และในทางกลับกันสำหรับภาษาแบบคอลัมน์
ภาษาโปรแกรมและไลบรารี
โดยทั่วไปแล้ว ภาษาโปรแกรมหรือไลบรารีมาตรฐานที่รองรับอาร์เรย์หลายมิติจะมีลำดับการจัดเก็บข้อมูลแบบเรียงตามแถวหรือเรียงตามคอลัมน์สำหรับอาร์เรย์เหล่านี้
ลำดับแถวหลักถูกใช้ในC / C++ / Objective-C (สำหรับอาร์เรย์แบบ C), PL/I , [ 4 ] Pascal , [ 5 ] Speakeasy , [ 6 ]และSAS . [ 7 ]
ลำดับคอลัมน์หลักถูกใช้ใน Fortran [ 8 ] [ 9 ] IDL [ 8 ] MATLAB [ 9 ] GNU Octave Julia [ 10 ] S S - PLUS [ 11 ] R [ 12 ] Scilab [ 13 ] YorickและRasdaman [ 14 ]
ไม่ใช่ทั้งแบบเรียงตามแถวหรือเรียงตามคอลัมน์
ทางเลือกทั่วไปสำหรับการจัดเก็บอาร์เรย์หนาแน่นคือการใช้เวกเตอร์ Iliffeซึ่งโดยทั่วไปจะจัดเก็บตัวชี้ไปยังองค์ประกอบในแถวเดียวกันอย่างต่อเนื่อง (เช่น ลำดับแถวหลัก) แต่ไม่ใช่ตัวแถวเอง มีการใช้ใน (เรียงตามอายุ) : Java [ 15 ] C # / CLI / .Net , Scala [ 16 ]และSwift
การใช้ลิสต์ของลิสต์นั้นมีความหนาแน่นน้อยกว่า เช่น ในPython [ 17 ] และในภาษาWolframของWolfram Mathematica [ 18 ]
แนวทางอื่นใช้ ตารางของตาราง เช่น ในLua [ 19 ]
ไลบรารีภายนอก
นอกจากนี้ ไลบรารีภายนอกอาจให้การสนับสนุนอาร์เรย์หลายมิติ และอาจรองรับการเรียงลำดับแบบใดก็ได้ โดยแต่ละมิติจะมีค่าช่วงก้าว และการเรียงลำดับตามแถวหรือตามคอลัมน์เป็นเพียงสองผลลัพธ์ที่เป็นไปได้
ลำดับแถวหลักเป็นค่าเริ่มต้นในNumPy [ 20 ] (สำหรับ Python)
ลำดับคอลัมน์หลักเป็นค่าเริ่มต้นในEigen [ 21 ]และArmadillo (ทั้งคู่สำหรับ C++)
กรณีพิเศษคือOpenGL (และOpenGL ES ) สำหรับการประมวลผลกราฟิก เนื่องจาก "วิธีการทางคณิตศาสตร์ล่าสุดเกี่ยวกับพีชคณิตเชิงเส้นและสาขาที่เกี่ยวข้องมักจะถือว่าเวกเตอร์เป็นคอลัมน์" มาร์ค ซีกัล นักออกแบบจึงตัดสินใจแทนที่ธรรมเนียมในIRIS GL รุ่นก่อนหน้า ซึ่งเขียนเวกเตอร์เป็นแถว เพื่อความเข้ากันได้ เมทริกซ์การแปลงจะยังคงถูกจัดเก็บในลำดับเวกเตอร์หลัก (แถวหลัก) แทนที่จะเป็นลำดับพิกัดหลัก (คอลัมน์หลัก) จากนั้นเขาใช้เทคนิค "[เพื่อ] บอกว่าเมทริกซ์ใน OpenGL ถูกจัดเก็บในลำดับคอลัมน์หลัก" [ 22 ]เรื่องนี้มีความเกี่ยวข้องเฉพาะกับการนำเสนอเท่านั้น เพราะการคูณเมทริกซ์นั้นใช้สแต็กและยังสามารถตีความได้ว่าเป็นการคูณภายหลัง แต่ที่แย่กว่านั้นคือ ความจริงได้รั่วไหลผ่านAPI ที่ใช้ C เพราะองค์ประกอบแต่ละตัวจะถูกเข้าถึงเป็นM[vector][coordinate]หรือ ในทางปฏิบัติคือM[column][row]ซึ่งทำให้ธรรมเนียมที่นักออกแบบต้องการนำมาใช้นั้นสับสน และสิ่งนี้ยังคงถูกรักษาไว้ในOpenGL Shading Languageที่เพิ่มเข้ามาในภายหลัง (ถึงแม้ว่าสิ่งนี้จะทำให้สามารถเข้าถึงพิกัดโดยใช้ชื่อแทนได้ เช่นM[vector].y) ผลก็คือ นักพัฒนาหลายคนจึงประกาศว่าการมีคอลัมน์เป็นดัชนีแรกคือคำจำกัดความของ column-major แม้ว่านี่จะไม่ใช่กรณีที่ชัดเจนในภาษา column-major จริงๆ เช่น Fortran
Torch (สำหรับ Lua) เปลี่ยนจากลำดับเริ่มต้นแบบ column-major [ 23 ]เป็น row-major [ 24 ]
การสลับตำแหน่ง
เนื่องจากการสลับดัชนีของอาร์เรย์เป็นหัวใจสำคัญของการสลับตำแหน่งอาร์เรย์ดังนั้นอาร์เรย์ที่จัดเก็บแบบเรียงตามแถวแต่ถูกอ่านแบบเรียงตามคอลัมน์ (หรือในทางกลับกัน) จะปรากฏเป็นอาร์เรย์ที่สลับตำแหน่งแล้ว เนื่องจากการจัดเรียงใหม่ในหน่วยความจำมักเป็นกระบวนการที่ใช้ทรัพยากรมาก ระบบบางระบบจึงมีตัวเลือกให้ระบุว่าเมทริกซ์แต่ละตัวจะถูกจัดเก็บในรูปแบบที่สลับตำแหน่งแล้ว จากนั้นโปรแกรมเมอร์ต้องตัดสินใจว่าจะจัดเรียงองค์ประกอบในหน่วยความจำใหม่หรือไม่ โดยพิจารณาจากการใช้งานจริง (รวมถึงจำนวนครั้งที่อาร์เรย์ถูกนำมาใช้ซ้ำในการคำนวณ)
ตัวอย่างเช่น ฟังก์ชัน โปรแกรมย่อยพีชคณิตเชิงเส้นพื้นฐานจะส่งแฟล็กที่ระบุว่าอาร์เรย์ใดถูกสลับตำแหน่ง[ 25 ]
การคำนวณที่อยู่โดยทั่วไป
แนวคิดนี้สามารถนำไปใช้กับอาร์เรย์ที่มีมิติมากกว่าสองมิติได้เช่นกัน
สำหรับมิติdอาร์เรย์ที่มีมิติN ( k =1... d ) โดยแต่ละองค์ประกอบของอาร์เรย์นี้ระบุด้วยทูเปิลของ ดัชนี d (ฐานศูนย์).
ในการเรียงลำดับตามแถว มิติ สุดท้ายจะต่อเนื่องกัน ดังนั้นค่าชดเชยหน่วยความจำขององค์ประกอบนี้จึงกำหนดโดย:
ในการเรียงลำดับแบบคอลัมน์ หลัก มิติ แรกจะต่อเนื่องกัน ดังนั้นค่าชดเชยหน่วยความจำขององค์ประกอบนี้จึงกำหนดโดย: โดยที่ผลคูณว่างเปล่าคือองค์ประกอบเอกลักษณ์ การคูณ กล่าว คือ.
สำหรับลำดับที่กำหนดระยะก้าวในมิติkจะได้จากค่าการคูณในวงเล็บหน้าดัชนีn ในผลรวมด้านขวามือข้างต้น
โดยทั่วไปแล้ว อาร์เรย์ที่กำหนดจะมี ลำดับที่เป็นไปได้ d! ลำดับ โดยแต่ละลำดับจะแทน การเรียงสับเปลี่ยนของมิติ (โดยที่การเรียงตามแถวและการเรียงตามคอลัมน์เป็นเพียง 2 กรณีพิเศษ) แม้ว่ารายการของค่า stride จะไม่จำเป็นต้องเป็นการเรียงสับเปลี่ยนซึ่งกันและกันก็ตาม เช่น ในตัวอย่าง 2x3 ข้างต้น ค่า stride คือ (3,1) สำหรับการเรียงตามแถว และ (1,2) สำหรับการเรียงตามคอลัมน์
ดูเพิ่มเติม
- อาร์เรย์ (โครงสร้างข้อมูล)
- การเปรียบเทียบภาษาโปรแกรม (อาร์เรย์)
- ที่มาของดัชนีอีกหนึ่งความแตกต่างระหว่างประเภทของอาร์เรย์ในภาษาโปรแกรมต่างๆ
- การแสดงผลแบบเมทริกซ์
- ลำดับมอร์ตัน (Morton order)เป็นอีกวิธีหนึ่งในการแมปข้อมูลหลายมิติไปยังดัชนีหนึ่งมิติ ซึ่งมีประโยชน์ในโครงสร้างข้อมูลแบบต้นไม้
- รูปแบบ CSRคือเทคนิคสำหรับการจัดเก็บเมทริกซ์แบบสปาร์สในหน่วยความจำ
- การแปลงเมทริกซ์ ให้เป็นเวกเตอร์ (คณิตศาสตร์)เทียบเท่ากับการเปลี่ยนเมทริกซ์ให้เป็นเวกเตอร์แบบเรียงตามคอลัมน์ที่สอดคล้องกัน
แหล่งที่มา
- Donald E. Knuth, ศิลปะแห่งการเขียนโปรแกรมคอมพิวเตอร์เล่ม 1: อัลกอริทึมพื้นฐาน , ฉบับพิมพ์ครั้งที่ 3, ส่วนที่ 2.2.6 (Addison-Wesley: นิวยอร์ก, 1997)