SoA Vektor di SPU


8

Saya telah membaca banyak tentang manfaat pengorganisasian data ke dalam 'Structs of Arrays' (SoA) alih-alih 'Array of Structs' (AoS) untuk mendapatkan hasil yang lebih baik saat menggunakan instruksi SIMD . Sementara 'mengapa' masuk akal bagi saya, saya tidak yakin berapa banyak yang harus dilakukan ketika bekerja dengan hal-hal seperti vektor.

Vektor sendiri dapat dianggap sebagai struct dari array (ukuran tetap) data, sehingga Anda dapat mengkonversi array ini menjadi struct dari array X, Y dan Z. Melalui ini, Anda dapat bekerja pada 4 vektor sekaligus sebagai lawan satu pada satu waktu.

Sekarang, untuk alasan spesifik saya memposting ini di GameDev:

Apakah ini masuk akal untuk bekerja dengan vektor di SPU? Lebih khusus, apakah masuk akal untuk beberapa array DMA hanya untuk satu vektor? Atau lebih baik tetap menggunakan DMA array array dan membuka gulungannya ke komponen yang berbeda untuk bekerja dengan?

Saya bisa melihat manfaat dari memotong membuka gulungan (jika Anda melakukannya 'AoS'), tetapi sepertinya Anda bisa dengan cepat kehabisan saluran DMA jika Anda mengambil rute ini dan sedang bekerja dengan beberapa set vektor sekaligus.

(Catatan: belum ada pengalaman profesional dengan Cell, tetapi telah bermain-main di OtherOS untuk sementara waktu)

Jawaban:


5

Salah satu pendekatan adalah dengan menggunakan pendekatan AoSoA (baca: Array of Struct of Array) yang merupakan hibrida dari AoS dan SoA. Idenya adalah untuk menyimpan nilai N structs data dalam potongan yang berdekatan dalam bentuk SoA, kemudian N struct berikutnya bernilai dalam bentuk SoA.

Formulir AoS Anda untuk 16 vektor (berlabel 0,1,2 ... F), di-granularity dari 4 struct adalah:

000111222333444555666777888999AAABBBCCCDDDEEEFFF
XYZXYZXYZXYZXYZXYZXYZXYZXYZXYZXYZXYZXYZXYZXYZXYZ

untuk SoA, ini adalah:

0123456789ABCDEF
XXXXXXXXXXXXXXXX

0123456789ABCDEF
YYYYYYYYYYYYYYYY

0123456789ABCDEF
ZZZZZZZZZZZZZZZZ

untuk AoSoA, ini menjadi:

01230123012345674567456789AB89AB89ABCDEFCDEFCDEF
XXXXYYYYZZZXXXXYYYYZZZZXXXXYYYYZZZZXXXXYYYYZZZZZZZ

Pendekatan AoSoA memiliki manfaat AoS berikut:

  • Hanya satu transfer DMA yang diperlukan untuk mentransfer sejumlah struct ke memori lokal SPU.
  • struct masih memiliki kemungkinan semua data pas dalam cacheline.
  • Preferensi blok masih sangat mudah.

Pendekatan AoSoA juga memiliki manfaat dari bentuk SoA ini:

  • Anda dapat memuat data dari memori lokal SPU langsung ke register vektor 128-bit tanpa harus membengkak data Anda.
  • Anda masih dapat beroperasi pada 4 struct sekaligus.
  • Anda dapat sepenuhnya memanfaatkan SIMD'ness prosesor vektor Anda jika tidak ada percabangan dasar (mis. Tidak ada jalur yang tidak digunakan dalam aritmatika vektor Anda)

Pendekatan AoSoA masih memiliki beberapa kelemahan dari bentuk SoA ini:

  • manajemen objek harus dilakukan pada granularity swizzling.
  • akses acak menulis dari struct penuh sekarang perlu menyentuh memori yang tersebar.
  • (ini bisa berubah menjadi tidak masalah tergantung pada bagaimana Anda mengatur / mengelola struct Anda dan masa berlaku mereka)

BTW, konsep-konsep AoSoA ini berlaku sangat baik untuk SSE / AVX / LRBni, serta GPU yang dapat disamakan dengan prosesor SIMD yang sangat luas misalnya. Lebar 32/48/64 tergantung pada vendor / arsitektur.


Saya tidak melihat bagaimana ini menawarkan keuntungan dibandingkan tidak mengemasnya per komponen kecuali jika Anda mengemas data non-vektor yang benar-benar Anda gunakan sebagai pelampung - walaupun saya melihat AoS Anda tidak termasuk W, yang sepertinya tidak terlalu ramah akses memori, saya tebak dalam hal itu ada kemenangan. Perhatikan juga bahwa SPU tidak memiliki garis cache kecuali untuk berkomunikasi dengan memori utama.
Kaj

2
1. Seperti halnya semua hal, jarak tempuh Anda dapat bervariasi tergantung pada data / algoritma / prosesor Anda. Dalam register yang dibatasi kasus, menghindari kebutuhan untuk 4 register temp sebelum Anda dapat mengocok semua bidang X Anda ke register yang sama dapat berguna. Tapi sekali lagi, YMMV. 2. Jawaban saya lebih umum karena konsep transfer baik dalam bidang pemrograman paralel data; pertimbangan jalur cache lebih berkaitan dengan GPU / SSE tapi saya merasa saya harus menyebutkan semuanya sama :)
jpaver

1
Cukup adil, saya berdiri tercerahkan dan akan belajar mengkritik dengan lebih halus! Terima kasih telah berbagi wawasan Anda: o)
Kaj

3

SPU sebenarnya merupakan kasus khusus yang menarik ketika datang ke kode vektor. Instruksi dibagi menjadi keluarga "aritmatika" dan "memuat / menyimpan", dan kedua keluarga berjalan pada saluran pipa terpisah. SPU dapat mengeluarkan satu dari setiap jenis per siklus.

Kode matematika jelas sangat terikat oleh instruksi matematika - jadi biasanya loop matematika pada SPU akan memiliki banyak dan banyak siklus terbuka pada pipa load / store. Karena shuffles terjadi pada pipa load / store, Anda sering memiliki cukup instruksi load / store gratis untuk merombak bentuk xyzxyzxyzxyz ke dalam bentuk xxxxyyyyzzzz tanpa overhead sama sekali.

Teknik ini paling tidak digunakan di Naughty Dog - lihat presentasi perakitan SPU mereka ( bagian 1 dan bagian 2 ) untuk detailnya.

Sayangnya kompiler sering kali tidak cukup pintar untuk melakukan ini secara otomatis - jika Anda memutuskan untuk pergi rute ini Anda harus menulis sendiri perakitan atau membuka gulungan Anda menggunakan intrinsik dan memeriksa assembler untuk memastikan itu yang Anda inginkan. Jadi jika Anda ingin menulis kode lintas platform umum yang berjalan dengan baik di SPU, Anda mungkin ingin menggunakan SoA atau AoSoA (seperti yang disarankan jpaver.)


Ah, bagaimanapun juga kita setuju: o) Memukul-mukul SPU jika Anda membutuhkannya, cukup waktu untuk melakukannya di sana.
Kaj

1

Seperti halnya optimasi, profil! Keterbacaan menjadi prioritas utama, dan seharusnya hanya dikorbankan ketika pembuatan profil mengidentifikasi kemacetan tertentu dan Anda telah kehabisan semua pilihan Anda untuk menyetel algoritma tingkat tinggi (cara tercepat untuk melakukan pekerjaan adalah tidak harus melakukan pekerjaan!) Anda harus selalu membuat profil ulang mengikuti setiap optimasi tingkat rendah untuk memastikan bahwa Anda benar-benar telah membuat segala sesuatunya lebih cepat daripada sebaliknya, terutama dengan saluran pipa yang seanik Sel.

Teknik apa yang Anda gunakan akan tergantung pada rincian kemacetan. Secara umum, ketika bekerja dengan tipe vektor, komponen vektor yang Anda abaikan dalam suatu hasil merepresentasikan kerja yang sia-sia. Mengalihkan SoA / AoS tidak masuk akal kecuali memungkinkan Anda untuk melakukan pekerjaan yang lebih bermanfaat dengan mengisi komponen yang tidak digunakan tersebut (misalnya produk satu titik pada PPU PS3 vs produk empat titik secara paralel dalam jumlah waktu yang sama). Untuk menjawab pertanyaan Anda, menghabiskan waktu mengocok komponen hanya untuk melakukan satu operasi pada satu vektor terdengar seperti pesimis kepada saya!

Sisi lain pada SPU adalah bahwa sebagian besar biaya transfer DMA kecil dalam pengaturan; apa pun yang kurang dari 128 byte akan memerlukan jumlah siklus yang sama untuk ditransfer, dan apa pun yang kurang dari sekitar satu kilobita hanya beberapa siklus lagi. Jadi jangan khawatir tentang DMA data lebih dari yang Anda butuhkan; mengurangi jumlah transfer DMA berurutan yang dipicu, dan melakukan pekerjaan saat transfer DMA terjadi - dan karenanya membuka loop prolog dan epilog untuk membentuk jaringan perangkat lunak - adalah kunci untuk kinerja SPU yang baik, dan paling mudah untuk menangani kasus sudut dengan mengambil data tambahan • buang hasil yang dihitung sebagian daripada melompat-lompat untuk mencoba mengatur jumlah data yang tepat yang perlu dibaca dan diproses.


Jika Anda akhirnya membongkar mereka, sesuai pendekatan AOSAO, setidaknya menarik beberapa vektor sekaligus. Anda juga ingin menarik batch, dan saat memproses tarik itu dalam batch berikutnya. Saat mengirim batch pertama, Anda memproses yang kedua dan menarik yang ketiga. Dengan begitu Anda menyembunyikan latensi sebanyak mungkin.
Kaj

0

Tidak, itu tidak masuk akal secara umum karena kebanyakan opcodes vektor beroperasi pada vektor secara keseluruhan dan bukan pada komponen yang terpisah. Jadi Anda sudah dapat mengalikan vektor dalam 1 instruksi, sedangkan dengan memisahkan komponen yang terpisah Anda akan menghabiskan 4 instruksi di atasnya. Jadi karena pada dasarnya Anda melakukan banyak operasi secara umum pada bagian struct, Anda lebih baik mengemasnya dalam sebuah array, tetapi Anda hampir tidak pernah melakukan hal-hal hanya pada satu komponen vektor, atau sangat berbeda pada setiap komponen sehingga mematahkannya. keluar tidak akan berhasil.
Tentu saja, jika Anda menemukan situasi di mana Anda harus melakukan sesuatu untuk hanya (katakanlah) x komponen vektor itu mungkin berhasil, namun hukuman dari swizzling semuanya kembali ketika Anda membutuhkan vektor yang sebenarnya tidak akan murah sehingga Anda bisa bertanya-tanya apakah Anda tidak harus menggunakan vektor untuk memulai tetapi hanya sebuah array float yang memungkinkan opcode vektor untuk melakukan perhitungan spesifik mereka.


2
Anda kehilangan titik SoA untuk matematika vektor. Anda jarang memiliki hanya satu objek yang sedang Anda kerjakan - dalam praktiknya Anda mengulangi array dan melakukan hal yang sama untuk banyak objek. Pertimbangkan untuk melakukan produk 4 titik. Jika Anda menyimpan vektor sebagai AoS dalam bentuk xyz0, mengambil titik dari dua vektor membutuhkan instruksi multiply-shuffle-add-shuffle-add - 5. Melakukan produk 4 titik membutuhkan 20 instruksi. Di sisi lain, jika Anda memiliki 8 vektor yang tersimpan mode SoA (xxxx, yyyy, zzzz, xxxx, yyyy, zzzz) Anda dapat melakukan 4 titik produk dengan hanya 3 instruksi (mul, madd, madd) - itu lebih dari 6 kali lebih cepat.
Charlie

Titik adil. Namun, dua pengamatan. Saya akan selalu menjaga W hadir sehingga saya tidak memerlukan 20 instruksi, kedua, sebagian besar overhead yang tersisa dapat disembunyikan dalam latensi instruksi lainnya - loop ketat Anda akan menderita dari kios pipa yang parah, bukan? membuat 6 kali adalah optimasi teoretis. Jadi, sementara ya, Anda ingin batch operasi Anda - hampir tidak pernah Anda hanya perlu melakukan batch cepat produk dot tanpa ada yang lain untuk dilakukan pada data tersebut. Biaya deswizzling / pencar di pihak PPU akan menjadi terlalu banyak pengorbanan bagi saya.
Kaj

Mengerang, saya berdiri dikoreksi - pada SPU saya akan membutuhkan 20 jika dilakukan secara naif (tapi saya akan mengocok di tempat). Itu salah satu hal di mana saya akhirnya melakukan banyak swizzles untuk membuatnya optimal. 360 memiliki titik intrinsik yang bagus (tetapi tidak memiliki manipulasi bit yang mengagumkan).
Kaj

Ya, sekarang saya memikirkannya, jika Anda mencoba untuk melakukan "produk 4 titik" Anda dapat melakukan lebih dari 20 instruksi karena Anda dapat menggabungkan beberapa dari menambahkan kemudian. Tetapi memiliki vektor Anda dalam register sebagai xxxx, yyyy, zzzz - apakah Anda bengkak atau disimpan sebagai SoA - menyingkirkan shuffles sepenuhnya. Lagi pula, Anda benar bahwa SoA membuat kode logika bercabang lebih lambat - tapi saya berpendapat bahwa solusi dalam banyak kasus seperti itu adalah dengan memasukkan data Anda dan mengubah logika cabang menjadi loop datar yang bagus.
Charlie

Sepakat. Saya cukup yakin jika saya memeriksa kode SPU lama saya (tidak bisa, perusahaan sebelumnya) ada contoh di mana saya memindahkannya ke format xxxxyyyyzzzz untuk optimasi tanpa menyadarinya secara khusus. Saya tidak pernah menawarkannya dari PPU dalam format itu. Pikiran Anda, OP apa yang merenungkan dma-ing x, y, z secara terpisah. Itu pasti tidak akan berhasil untuk saya. Saya juga (seperti yang saya lakukan) lebih suka berdesir secara lokal karena tidak semuanya berfungsi lebih baik dalam format xxxxyyyyzzzz. Saya harus memilih pertempuran Anda kurasa. Mengoptimalkan untuk SPU adalah ledakan dan Anda merasa sangat pintar setelah Anda mendapatkan solusi yang ketat: o)
Kaj
Dengan menggunakan situs kami, Anda mengakui telah membaca dan memahami Kebijakan Cookie dan Kebijakan Privasi kami.
Licensed under cc by-sa 3.0 with attribution required.