Audit aset
Folder Sumber Daya

String dan teks

Penanganan string dan teks adalah sumber umum masalah kinerja dalam proyek Unity. Di C#, semua string adalah immutableAnda tidak dapat mengubah isi paket immutable (read-only). Ini adalah kebalikan dari mutable. Kebanyakan paket tidak dapat diakses, termasuk paket yang diunduh dari registry paket atau melalui URL Git.
Lihat di Glossary
. Setiap manipulasi hasil string dalam alokasi string baru penuh. Ini relatif mahal, dan ringkasan string berulang dapat berkembang menjadi masalah kinerja ketika dilakukan pada string besar, pada set data besar, atau dalam loop ketat.

Selanjutnya, sebagai komputasi string N memerlukan alokasi string perantara N–1, kondensasi serial juga dapat menjadi penyebab utama dari tekanan memori yang dikelola.

Untuk kasus di mana string harus dikonsepkan dalam loop ketat atau selama setiap bingkai, gunakan StringBuilder untuk melakukan operasi concatenation yang sebenarnya. Contoh StringBuilder juga dapat digunakan kembali untuk lebih meminimalkan alokasi memori yang tidak perlu.

Microsoft mempertahankan daftar praktik terbaik untuk bekerja dengan string di C #, yang dapat ditemukan di sini di situs MSDN: msdn.microsoft.com.

Perbandingan lokal dan ordinal

Salah satu masalah kinerja inti sering ditemukan dalam kode terkait string adalah penggunaan yang tidak diinginkan dari API string default. API ini dibangun untuk aplikasi bisnis, dan mencoba menangani string penanganan dari berbagai aturan budaya dan linguistik berkaitan dengan karakter yang ditemukan dalam teks.

Misalnya, kode contoh berikut kembali benar ketika berjalan di bawah penduduk lokal AS-Inggris, tetapi kembali palsu bagi banyak penduduk Eropa.

Note: Sebagai Unity 5.3 dan 5.4, runtimes skrip Unity selalu berjalan di bawah warga negara AS (en-US):

    String.Equals("encyclopedia", “encyclopædia”);

Untuk sebagian besar proyek Unity, ini sepenuhnya tidak perlu. Ini kira-kira sepuluh kali lebih cepat untuk menggunakan jenis perbandingan ordinal, yang membandingkan string dengan cara yang akrab untuk programmer C dan C++: dengan hanya membandingkan setiap ketepatan parut string, tanpa memperhatikan karakter yang diwakili oleh byte.

Beralih ke perbandingan string ordinal sesederhana memasok StringComparison.Ordinal sebagai argumen akhir untuk String.Equals:

myString.Equals(otherString, StringComparison.Ordinal);

API string built-in yang efisien

Di luar beralih ke perbandingan ordinal, C # String API tertentu dikenal sangat tidak efisien. Di antara ini adalah String.Format, String.StartsWith dan String.EndsWith. String.Format sulit diganti, tetapi metode perbandingan string tidak efisien dioptimalkan.

Sementara rekomendasi Microsoft adalah untuk lulus StringComparison.Ordinal ke perbandingan string apa pun yang tidak perlu disesuaikan untuk lokalisasi, benchmark Unity menunjukkan bahwa dampak ini relatif minimal dibandingkan dengan implementasi kustom.

Method Waktu (ms) untuk string pendek 100k
String.StartsWith, kultur default Chili
String.EndsWith, kultur default 142 g
String.StartsWith, ordinal 115 g
String.EndsWith, ordinal Chili
penggantian StartsWith kustom 4.5
penggantian EndsWith kustom 4.5

Kedua String.StartsWith dan String.EndsWith dapat diganti dengan versi yang dikodekan tangan sederhana, mirip dengan contoh yang terpasang di bawah ini.


    public static bool CustomEndsWith(this string a, string b)
    {
        int ap = a.Length - 1;
        int bp = b.Length - 1;
    
        while (ap >= 0 && bp >= 0 && a [ap] == b [bp])
        {
            ap--;
            bp--;
        }
    
        return (bp < 0);
    }

    public static bool CustomStartsWith(this string a, string b)
    {
        int aLen = a.Length;
        int bLen = b.Length;
    
        int ap = 0; int bp = 0;
    
        while (ap < aLen && bp < bLen && a [ap] == b [bp])
        {
            ap++;
            bp++;
        }
    
        return (bp == bLen);
    }

Ekspresi rutin

Sitemap Expressions adalah cara yang kuat untuk mencocokkan dan memanipulasi string, mereka dapat sangat intensif. Lebih lanjut, karena implementasi C# dari Regular Expressions, bahkan pertanyaan Boolean IsMatch sederhana mengalokasikan struktur data transien besar "di bawah kap." Ini transien berhasil memori churn harus dianggap tidak dapat diterima, kecuali selama awalisasi.

Jika ekspresi rutin diperlukan, sangat dianjurkan untuk tidak menggunakan metode Regex.Match statis atau Regex.Replace, yang menerima ekspresi rutin sebagai parameter string. Metode ini mengkompilasi ekspresi rutin on-the-fly dan tidak cache objek yang dihasilkan.

Kode contoh ini adalah satu-liner yang tidak biasa.


Regex.Match(myString, "foo");

Namun, setiap kali dieksekusi, menghasilkan 5 kilobyte sampah. Refactoring sederhana dapat menghilangkan banyak sampah ini:


var myRegExp = new Regex("foo");

myRegExp.Match(myString);

Dalam contoh ini, setiap panggilan ke myRegExp.Match "hanya" menghasilkan 320 byte sampah. Meskipun ini masih mahal untuk operasi pencocokan sederhana, itu adalah peningkatan yang cukup besar atas contoh sebelumnya.

Oleh karena itu, jika ekspresi rutin adalah literal string invariant, itu jauh lebih efisien untuk menginjak mereka dengan melewatinya sebagai parameter pertama dari konstror objek Regex. Regexe prekompiled ini harus digunakan kembali.

XML, JSON dan parsing teks bentuk panjang lainnya

Membuat teks sering merupakan salah satu operasi yang paling berat yang terjadi pada waktu pemuatan. Kadang-kadang, waktu yang dihabiskan teks parsing dapat melampaui waktu yang dihabiskan memuat dan mengulang Aset.

Alasan di balik ini tergantung pada parser tertentu yang digunakan. C# built-in XML parser sangat fleksibel, tetapi sebagai hasilnya, tidak dapat dioptimalkan untuk tata letak data tertentu.

Banyak parsers pihak ketiga dibangun pada refleksi. Sementara refleksi adalah pilihan yang sangat baik selama pembangunan (karena itu memungkinkan parser untuk dengan cepat beradaptasi untuk mengubah tata letak data), sangat lambat.

Unity telah memperkenalkan solusi parsial dengan API JSONUtility built-in, yang menyediakan antarmuka ke sistem serialisasi Unity yang membaca / membatasi JSON. Di sebagian besar tolok, lebih cepat daripada C # JSON parsers murni, tetapi memiliki keterbatasan yang sama seperti antarmuka lain ke sistem serialisasi Unity - itu tidak dapat serialisasi banyak jenis data yang kompleks, seperti Dictionaries, tanpa kode tambahan.

Note: Lihat antarmuka ISerializationCallbackReceiver untuk satu cara untuk menambahkan pemrosesan tambahan yang diperlukan untuk mengkonversi ke / dari jenis data yang kompleks selama proses serialisasi Unity.

Ketika menghadapi masalah kinerja yang timbul dari parsing data tekstual, mempertimbangkan tiga resolusi alternatif.

Opsi 1: Parse pada waktu membangun

Cara terbaik untuk menghindari biaya parsing teks adalah untuk sepenuhnya menghilangkan parsing teks pada runtime. Secara umum, ini berarti “baking” data tekstual ke dalam format biner melalui beberapa jenis langkah build.

Sebagian besar pengembang yang memilih untuk rute ini memindahkan data mereka ke semacam hierarki kelas ScriptableObject-derived dan kemudian mendistribusikan data melalui AssetBundles. Untuk diskusi yang sangat baik menggunakan ScriptableObjects, lihat Richard Fine’s Unite 2016 berbicara di youtube.

Strategi ini menawarkan kinerja terbaik, tetapi hanya cocok untuk data yang tidak perlu dihasilkan secara dinamis. paling cocok untuk parameter desain game dan konten lainnya.

Opsi 2: Beban split dan malas

Kemungkinan kedua adalah untuk membagi data yang harus dimasukkan ke dalam chunks yang lebih kecil. Setelah split, biaya parsing data dapat menyebar di beberapa bingkai. Dalam kasus yang ideal, mengidentifikasi bagian spesifik dari data yang diperlukan untuk menyajikan pengalaman yang diinginkan kepada pengguna dan memuat hanya bagian-bagian tersebut.

Dalam contoh sederhana: jika proyek adalah permainan platform, itu tidak akan perlu untuk serialisasi data untuk semua tingkat bersama menjadi satu raksasa blob. Jika data dibagi menjadi Aset individu untuk setiap tingkat, dan mungkin tersegmentasi tingkat ke daerah, data dapat diserang sebagai pemain mendekatinya.

Meskipun suara ini mudah, dalam prakteknya membutuhkan investasi substansial dalam kode alat dan mungkin memerlukan struktur data untuk diorganisasikan kembali.

Opsi 3: Thread

Untuk data yang parsed sepenuhnya menjadi objek C# biasa, dan tidak memerlukan interaksi dengan API Unity, dimungkinkan untuk memindahkan operasi parsing ke benang pekerja.

Opsi ini dapat sangat kuat pada platform dengan sejumlah besar inti. Namun, itu membutuhkan pemrograman yang cermat untuk menghindari membuat tengkar dan kondisi balap.

Note: Perangkat iOS memiliki sebagian besar 2 core. Kebanyakan perangkat Android memiliki dari 2 hingga 4. Teknik ini lebih menarik ketika membangun target membangun yang mandiri dan konsol.

Proyek yang memilih untuk menerapkan threading menggunakan built-in C # Thread dan kelas ThreadPool (lihat msdn.microsoft.com) untuk mengelola benang pekerja mereka, bersama dengan kelas sinkronisasi C # standar.

Audit aset
Folder Sumber Daya