6.5. Shape และ stride

ข้อมูลภายใน ndarray คือบล็อกตัวเลขแบบ packed เดี่ยว descriptor ที่อยู่ด้านหน้าบล็อกนั้นจะกำหนดวิธีอ่านบล็อก flat นั้นออกมาเป็น tensor

6.5.1. สิ่งที่ descriptor บันทึก

ค่าห้าค่าอธิบายวิธีอ่านบล็อกข้อมูลเป็น tensor:

a = np.array([[1, 2, 3], [4, 5, 6]], dtype=np.uint8)

a.ndim       # 2     - number of dimensions
a.shape      # (2, 3)- length along each dimension
a.itemsize   # 1     - bytes per element (from dtype)
a.size       # 6     - total number of elements
a.strides    # (3, 1)- step pattern through the buffer

helper ndinfo() พิมพ์ค่าทั้งหมดพร้อมตำแหน่งของ buffer พื้นฐานในการเรียกครั้งเดียว array สอง ตัวที่ตำแหน่ง buffer ตรงกันจะแบ่งปันหน่วยความจำ:

np.ndinfo(a)
# class: ndarray
# shape: (2, 3)
# strides: (3, 1)
# itemsize: 1
# data pointer: 0x...
# type: uint8

6.5.2. อธิบาย stride

stride คือจำนวนไบต์ที่ต้องก้าวในบล็อกข้อมูลเพื่อเคลื่อนที่ไปหนึ่ง element ตามแกนที่กำหนด สำหรับ array uint8 ขนาด 2x3 ด้านบน stride คือ (3, 1): การเคลื่อนที่ลงหนึ่งแถวจะข้าม 3 ไบต์ การเคลื่อนที่ไปขวาหนึ่งคอลัมน์จะข้าม 1 ไบต์ นั่นก็คือแถวถูกเก็บต่อเนื่องกัน จากซ้ายไปขวา:

memory: [ 1 ][ 2 ][ 3 ][ 4 ][ 5 ][ 6 ]
          ^ row 0          ^ row 1
          <------- 3 bytes ---->

ในการอ่าน a[i, j], numpy คำนวณ i * strides[0] + j * strides[1] จากจุดเริ่มต้นของบล็อกข้อมูลและอ่าน itemsize ไบต์จากตรงนั้น สูตรเดียวกันนี้ขยายไปยังมิติใดก็ได้

layout นี้ -- แถวถูกเก็บต่อเนื่องกัน โดยแกนสุดท้ายแปรผันเร็วที่สุดตามหน่วยความจำ -- เรียกว่าลำดับ row-major ทุก array ที่ numpy จัดสรรบน camera ใช้ layout นี้

6.5.3. ผลกระทบของ Row-major

สองสิ่งที่เกิดจาก "แถวถูกเก็บต่อเนื่องกัน" ที่สำคัญเมื่อจัดรูปแบบ buffer บน camera

แกนสุดท้ายมีความต่อเนื่อง การเดินจาก a[0, 0] ไป a[0, 1] แตะไบต์ถัดไปทันที การเดินจาก a[0, 0] ไป a[1, 0] ข้ามทั้งแถว

แกนสุดท้ายคือแกนเร็วสำหรับการคำนวณ whole-array numpy บน camera จะวนซ้ำตามแกนสุดท้ายเสมอในชั้นในสุด โดยไม่คำนึงว่าแกนไหนยาวกว่า ไลบรารี numpy บน desktop จะเรียงลำดับลูปใหม่อย่างเงียบเพื่อให้แกนที่ยาวที่สุดอยู่ชั้นในสุด; camera ไม่ทำเช่นนั้น ดังนั้นการเลือก layout ที่ desktop numpy จะจัดการแทนยังคงมีต้นทุนเวลาที่นี่ np.sum(m, axis=1) พับแกนสุดท้ายและทำงานในทิศทางต่อเนื่อง; np.sum(m, axis=0) ไม่ทำ เมื่อแอปพลิเคชันมีทางเลือกในการวาง layout ของ buffer ให้วางแกนยาวไว้สุดท้ายเพื่อให้การดำเนินการตามแกนนั้นอยู่ในลูปชั้นใน

หาก layout เริ่มต้นผิด transpose() (หรือทางลัด .T) จะแก้ไขโดยไม่คัดลอกข้อมูล -- มันเพียงสลับ stride:

a = b.T            # now iterates fast

ประสิทธิภาพ มีการอภิปรายประสิทธิภาพเต็มรูปแบบ

6.5.4. Reshape, transpose, slicing -- การแก้ไข descriptor

การดำเนินการที่เพียงแค่เขียน descriptor ใหม่ไม่มีต้นทุน reshape สลับ shape และ strides ใหม่ผ่านบล็อกข้อมูลเดิม transpose กลับ stride a[::2] คูณ stride เป็นสองเท่า แต่ละอย่างคืนค่า view ของ buffer พื้นฐานเดิม

สิ่งใดก็ตามที่ต้องเดินผ่านข้อมูลและเขียน buffer ใหม่คือ copy กฎสำหรับตอนนี้คือ การแก้ไข descriptor ไม่มีต้นทุน แต่การเดินผ่านข้อมูลมีต้นทุน

6.5.5. หมายเหตุเกี่ยวกับ ndim

numpy บน camera ถูกสร้างขึ้นด้วย ndim สูงสุดที่รองรับ 4 การดำเนินการที่จะสร้าง array rank สูงกว่าจะยก ValueError งานส่วนใหญ่ที่ทำบน camera เป็น 1 มิติหรือ 2 มิติ ดังนั้นขีดจำกัดนี้แทบไม่เป็นปัญหา