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 มิติ ดังนั้นขีดจำกัดนี้แทบไม่เป็นปัญหา