Optimalisasi rendering adegan dari kartun Disney "Moana". Bagian 3

gambar

Hari ini kita akan melihat dua tempat lagi di mana pbrt menghabiskan banyak waktu mengurai adegan dari kartun Disney "Moana" . Mari kita lihat apakah mungkin untuk meningkatkan produktivitas di sini. Ini diakhiri dengan apa yang bijaksana untuk dilakukan di pbrt-v3. Di pos lain, saya akan membahas seberapa jauh kita bisa melangkah jika kita meninggalkan larangan perubahan. Dalam hal ini, kode sumber akan terlalu berbeda dari sistem yang dijelaskan dalam buku Rendering Berbasis Fisik .

Optimasi Parser


Setelah peningkatan kinerja yang diperkenalkan pada artikel sebelumnya , proporsi waktu yang dihabiskan di pbrt parser, dan sangat signifikan dari awal, secara alami meningkat bahkan lebih. Saat ini, parser saat startup dihabiskan sebagian besar waktu.

Saya akhirnya mengumpulkan kekuatan saya dan mengimplementasikan tokenizer dan parser yang ditulis secara manual untuk adegan pbrt. Format file adegan pbrt cukup sederhana untuk diurai : jika Anda tidak memperhitungkan garis yang dikutip, token dipisahkan oleh spasi, dan tata bahasanya sangat mudah (tidak pernah ada kebutuhan untuk melihat lebih dari satu token), tetapi parser Anda sendiri masih seribu baris kode yang Anda butuhkan tulis dan debug. Itu membantu saya bahwa itu dapat diuji pada banyak adegan; setelah memperbaiki gangguan yang jelas, saya terus bekerja sampai saya berhasil membuat gambar yang persis sama seperti sebelumnya: seharusnya tidak ada perbedaan dalam piksel karena mengganti parser. Pada tahap ini, saya benar-benar yakin bahwa semuanya dilakukan dengan benar.

Saya mencoba membuat versi baru seefisien mungkin, menundukkan file input ke mmap() sebanyak mungkin dan menggunakan implementasi baru std::string_view dari C ++ 17 untuk meminimalkan pembuatan salinan string dari isi file. Selain itu, karena strtod() membutuhkan banyak waktu di jejak sebelumnya, saya menulis parseNumber() dengan perawatan khusus: bilangan bulat tunggal dan bilangan bulat biasa diproses secara terpisah, dan dalam kasus standar ketika pbrt dikompilasi untuk menggunakan pelampung 32-bit , menggunakan strtof() bukannya strtod() 1 .

Dalam proses membuat implementasi parser baru, saya agak takut parser lama akan lebih cepat: pada akhirnya, flex dan bison telah dikembangkan dan dioptimalkan selama bertahun-tahun. Saya tidak bisa mengetahui sebelumnya apakah sepanjang waktu menulis versi baru akan sia-sia sampai saya menyelesaikannya dan membuatnya berfungsi dengan benar.

Yang menggembirakan saya, pengurai kita sendiri ternyata merupakan kemenangan besar: generalisasi dari flex dan bison mengurangi kinerja sehingga versi baru dengan mudah mengambil alih mereka. Berkat parser baru, waktu peluncuran menurun menjadi 13 menit 21 detik, artinya, ia mempercepat 1,5 kali lagi! Bonus tambahan adalah bahwa sekarang mungkin untuk menghapus semua dukungan flex dan bison dari sistem pbrt build. Selalu sakit kepala, terutama di Windows, di mana kebanyakan orang tidak menginstalnya secara default.

Manajemen Status Grafik


Setelah mempercepat pengurai secara signifikan, muncul detail baru yang menjengkelkan: pada tahap ini, sekitar 10% dari waktu pengaturan dihabiskan untuk fungsi pbrtAttributeBegin() dan pbrtAttributeEnd() , dan sebagian besar waktu ini dialokasikan dan dirilis memori dinamis. Selama menjalankan pertama, yang memakan waktu 35 menit, fungsi-fungsi ini hanya memakan waktu sekitar 3% dari waktu eksekusi, sehingga mereka dapat diabaikan. Tetapi dengan optimasi selalu seperti ini: ketika Anda mulai menyingkirkan masalah besar, masalah kecil menjadi lebih penting.

Deskripsi adegan pbrt didasarkan pada status hierarkis grafik, yang menunjukkan transformasi saat ini, materi saat ini, dan sebagainya. Di dalamnya, Anda dapat mengambil snapshot dari kondisi saat ini ( pbrtAttributeBegin() ), membuat perubahan sebelum menambahkan geometri baru ke adegan, dan kemudian kembali ke keadaan semula ( pbrtAttributeEnd() ).

Keadaan grafik disimpan dalam struktur dengan nama yang tidak terduga ... GraphicsState . Untuk menyimpan salinan objek GraphicsState dalam tumpukan status grafik yang disimpan, std::vector . Melihat anggota GraphicsState , kita dapat mengasumsikan sumber masalah - three std::map , dari nama hingga contoh tekstur dan bahan:

 struct GraphicsState { // ... std::map<std::string, std::shared_ptr<Texture<Float>>> floatTextures; std::map<std::string, std::shared_ptr<Texture<Spectrum>>> spectrumTextures; std::map<std::string, std::shared_ptr<MaterialInstance>> namedMaterials; }; 

Meneliti file adegan ini, saya menemukan bahwa sebagian besar kasus penyimpanan dan pengembalian status grafis dilakukan dalam baris berikut:

 AttributeBegin ConcatTransform [0.981262 0.133695 -0.138749 0.000000 -0.067901 0.913846 0.400343 0.000000 0.180319 -0.383420 0.905800 0.000000 11.095301 18.852249 9.481399 1.000000] ObjectInstance "archivebaycedar0001_mod" AttributeEnd 

Dengan kata lain, itu memperbarui transformasi saat ini dan instantiate objek; tidak ada perubahan pada isi std::map . Membuat salinan lengkap dari mereka - mengalokasikan node pohon merah-hitam, meningkatkan jumlah referensi untuk pointer umum, mengalokasikan ruang dan menyalin string - hampir selalu buang-buang waktu. Semua ini dibebaskan ketika mengembalikan keadaan grafis sebelumnya.

Saya mengganti masing-masing peta ini dengan pointer std::shared_ptr untuk memetakan dan menerapkan pendekatan copy-on-write, di mana penyalinan di dalam blok awal / akhir atribut hanya terjadi ketika isinya perlu diubah. Perubahan itu tidak terlalu sulit, tetapi mengurangi waktu peluncuran lebih dari satu menit, yang memberi kami 12 menit 20 detik pemrosesan sebelum dimulainya rendering - lagi akselerasi 1,08 kali.

Bagaimana dengan rendering time?


Pembaca yang penuh perhatian akan memperhatikan bahwa sejauh ini saya belum mengatakan apa-apa tentang waktu render. Yang mengejutkan saya, ternyata cukup lumayan bahkan di luar kotak: pbrt dapat membuat gambar adegan dengan kualitas sinematik dengan beberapa ratus sampel per piksel pada dua belas inti prosesor untuk jangka waktu dua hingga tiga jam. Misalnya, gambar ini, salah satu yang paling lambat, ditampilkan dalam 2 jam 51 menit 36 โ€‹โ€‹detik:


Bukit pasir dari Moana disajikan oleh pbrt-v3 dengan resolusi 2048x858 pada 256 sampel per piksel. Total waktu rendering pada mesin Google Compute Engine dengan 12 core / 24 thread dengan frekuensi 2 GHz dan versi terbaru dari pbrt-v3 adalah 2 jam 51 menit 36 โ€‹โ€‹detik.

Menurut pendapat saya, ini sepertinya indikator yang masuk akal. Saya yakin bahwa perbaikan masih mungkin dilakukan, dan studi yang cermat terhadap tempat-tempat di mana sebagian besar waktu dihabiskan akan mengungkapkan banyak hal yang "menarik", tetapi sejauh ini tidak ada alasan khusus untuk itu.

Ketika profiling, ternyata sekitar 60% dari waktu rendering dihabiskan di persimpangan sinar dengan objek (sebagian besar operasi dilakukan melewati BVH), dan 25% dihabiskan mencari tekstur ptex. Rasio ini mirip dengan indikator adegan yang lebih sederhana, jadi pada pandangan pertama tidak ada yang jelas bermasalah di sini. (Namun, saya yakin Embree akan dapat melacak sinar ini dalam waktu yang lebih singkat.)

Sayangnya, skalabilitas paralel tidak begitu baik. Saya biasanya melihat bahwa 1400% sumber daya CPU dihabiskan untuk rendering, dibandingkan dengan ideal 2400% (pada 24 CPU virtual di Google Compute Engine). Tampaknya masalah ini terkait dengan konflik saat mengunci di ptex, tetapi saya belum menyelidiki secara lebih rinci. Sangat mungkin bahwa pbrt-v3 tidak menghitung perbedaan sinar untuk sinar tidak langsung pada pelacak sinar; pada gilirannya, balok seperti itu selalu mendapatkan akses ke tingkat tekstur MIP yang paling terperinci, yang tidak terlalu berguna untuk caching tekstur.

Kesimpulan (untuk pbrt-v3)


Setelah mengoreksi manajemen keadaan grafik, saya berlari ke batas, setelah itu kemajuan lebih lanjut tanpa membuat perubahan signifikan pada sistem menjadi tidak jelas; semua yang lain membutuhkan banyak waktu dan tidak ada hubungannya dengan optimasi. Karena itu, saya akan membahas hal ini, setidaknya berkaitan dengan pbrt-v3.

Secara umum, progresnya serius: waktu peluncuran sebelum rendering berkurang dari 35 menit menjadi 12 menit 20 detik, artinya, total akselerasi adalah 2,83 kali. Selain itu, berkat kerja yang cerdas dengan cache konversi, penggunaan memori telah menurun dari 80 GB menjadi 69 GB. Semua perubahan ini tersedia sekarang jika Anda menyinkronkan dengan versi terbaru dari pbrt-v3 (atau jika Anda telah melakukan ini selama beberapa bulan terakhir). Dan kita mulai memahami betapa sampah memori Primitive untuk adegan ini; kami menemukan cara untuk menghemat memori 18 GB lainnya, tetapi tidak menerapkannya di pbrt-v3.

Inilah yang menghabiskan 12 menit 20 detik setelah semua optimasi kami:

Fungsi / OperasiPersentase Runtime
Bangun BVH34%
Parsing (kecuali strtof() )21%
strtof()20%
Tembolok konversi7%
Membaca File PLY6%
Alokasi memori dinamis5%
Pembalikan konversi2%
Manajemen Status Grafik2%
Lainnya3%

Di masa depan, opsi terbaik untuk meningkatkan kinerja akan menjadi multithreading yang lebih besar dari tahap peluncuran: hampir semuanya selama penguraian adegan adalah single-threaded; tujuan pertama kami yang paling alami adalah membangun BVH. Ini juga akan menarik untuk menganalisis hal-hal seperti membaca file PLY dan menghasilkan BVH untuk setiap instance objek dan mengeksekusi mereka secara tidak sinkron di latar belakang, sementara parsing akan dilakukan di utas utama.

Pada titik tertentu, saya akan melihat apakah ada implementasi strtof() yang lebih cepat; pbrt hanya menggunakan apa yang disediakan sistem. Namun, Anda harus berhati-hati dengan pilihan penggantian yang tidak diuji secara menyeluruh: parsing float values โ€‹โ€‹adalah salah satu aspek yang harus benar-benar dipastikan oleh programmer.

Ini juga terlihat menarik untuk lebih mengurangi beban pada parser: kami masih memiliki 17 GB file input teks untuk parsing. Kita dapat menambahkan dukungan penyandian biner untuk file input pbrt (mungkin mirip dengan pendekatan RenderMan ), tetapi saya memiliki perasaan campur aduk tentang ide ini; kemampuan untuk membuka dan memodifikasi file deskripsi adegan dalam editor teks cukup berguna, dan saya khawatir bahwa kadang-kadang kode biner akan membingungkan siswa menggunakan pbrt dalam proses pembelajaran. Ini adalah salah satu kasus di mana solusi yang tepat untuk pbrt mungkin berbeda dari solusi untuk render komersial tingkat produksi.

Sangat menarik untuk melacak semua optimasi ini dan untuk lebih memahami berbagai solusi. Ternyata pbrt memiliki asumsi tak terduga yang mengganggu adegan tingkat kerumitan ini. Semua ini adalah contoh yang bagus tentang betapa pentingnya bagi komunitas luas yang memberikan peneliti untuk memiliki akses ke adegan produksi nyata dengan tingkat kompleksitas yang tinggi; Saya sekali lagi mengucapkan banyak terima kasih kepada Disney untuk waktu yang dihabiskan untuk memproses adegan ini dan meletakkannya di domain publik.

Pada artikel selanjutnya , kita akan melihat aspek-aspek yang dapat lebih meningkatkan kinerja jika kita mengizinkan pbrt untuk membuat perubahan yang lebih radikal.

Catatan


  1. Pada sistem Linux yang saya uji, strtof() tidak lebih cepat dari strtod() . Perlu dicatat bahwa pada OS X strtod() sekitar dua kali lebih cepat, yang sama sekali tidak masuk akal. Untuk alasan praktis, saya terus menggunakan strtof() .

Source: https://habr.com/ru/post/id417913/


All Articles