Hidrasi objek kelihatan seperti masalah yang sudah selesai sehinggalah anda mengukurnya. Anda mengambil satu baris daripada pangkalan data, memetakan tatasusunan (array) tersebut kepada satu objek, dan meneruskan kerja. Kebanyakan kita menganggapnya sebagai urusan teknikal di sebalik tabir—tidak kelihatan, tidak menarik, dan dianggap cukup pantas. Kemudian pada suatu hari, anda melakukan profil pada tugasan berkelompok (batch job) atau pekerja barisan (queue worker) dan menyedari bahawa sebahagian besar masa CPU hilang begitu sahaja ke dalam pemeta (mapper). Kesedaran itulah yang membawa kepada HydraType, sebuah penghidrasi yang dibina berasaskan satu peraturan yang teguh: sesebuah kelas tidak sepatutnya menanggung beban ciri yang tidak digunakan oleh propertinya.
Jika sesuatu medan ialah rentetan (string) biasa yang tidak memerlukan sebarang transformasi, kod yang dijana haruslah kelihatan seperti ditulis sendiri oleh manusia. Tugasan terus. Tiada gelung (loops), tiada saluran (pipelines), tiada refleksi (reflection).
Apa Sebenarnya Kos Hidrasi
Pada pandangan pertama, menukarkan ['name' => 'Alice', 'age' => 30] kepada objek User kelihatan remeh. Masalah bermula apabila anda mahukan fleksibiliti. Kebanyakan penghidrasi tujuan umum bergantung kepada refleksi untuk memeriksa kelas semasa masa larian (runtime). Mereka membina peta metadata, mengendalikan paksaan jenis (type coercion), dan menyelesaikan graf objek bersarang. Mereka juga sangat gemar menggunakan saluran (pipelines). Setiap nilai akan melalui urutan mutator, pengesahan (assertions), dan penukar (transformers), walaupun urutan tersebut kosong. Struktur gelung itu sendiri menambah beban tambahan (overhead).
Ambil beberapa puluh rekod dan anda tidak akan menyedarinya. Tetapi aplikasi PHP moden secara rutin memproses beribu-ribu mesej barisan, mengimport set CSV yang besar, atau menghidrasi set hasil yang mendalam daripada Elasticsearch. Dalam senario tersebut, pemeta yang membazirkan walaupun beberapa mikrosaat bagi setiap medan akan menjadi kesesakan (bottleneck) yang nyata. Seribu objek dengan lapan medan setiap satu akan berganda menjadi beban yang anda rasai.
Falsafah Sifar Beban Tambahan
Idea utama di sebalik HydraType ialah pengasingan ciri. Penukaran jenis, hidrasi objek bersarang, dan pengesahan semuanya disokong, namun tiada satu pun daripadanya bersifat mandatori. Jika sesuatu properti tidak memerlukan apa-apa selain salinan terus daripada tatasusuna ke objek, penulis (writer) yang dijana akan mengandungi perkara tersebut sahaja. Tiada saluran kongsi yang memaksa medan skalar biasa untuk menunggu di belakang pengesah yang tidak diperlukannya.
Ini lebih sukar untuk dicapai daripada yang disangka. Banyak perpustakaan berkongsi satu laluan pelaksanaan tunggal untuk setiap medan kerana ia mengekalkan pangkalan kod yang kecil dan seragam. HydraType mengambil pendekatan sebaliknya. Ia menjana kelas PHP yang unik untuk setiap DTO sasaran, mengeluarkan operasi yang diperlukan secara tepat oleh kelas spesifik tersebut. Kerumitan menjadi pilihan (opt-in), bukan beban menyeluruh ke atas setiap properti.
Bagaimana Kelajuan Dicapai
Empat pilihan konkrit membezakan HydraType daripada alatan berasaskan refleksi generik dan juga pendekatan jana yang lain.
1. Jana Kod Sekali, Jalankan Selamanya
HydraType memeriksa kelas sasaran anda sekali sahaja, kemudian menulis kelas PHP khusus yang disesuaikan dengan bentuk tepatnya. Jika DTO anda mengandungi $name (string awam), $status (int), dan $createdAt (DateTime peribadi), penulis yang dijana mengetahui semua ini lebih awal. Tiada refleksi berulang, tiada pembacaan rentetan, dan tiada pembinaan metadata secara malas semasa masa larian. Sebaik sahaja OPCache menyusun fail yang dijana, Penghidrasi tersebut tidak dapat dibezakan daripada PHP yang ditulis dengan tangan.
2. Hapuskan Saluran (Pipelines) Kosong
Kebanyakan penghidrasi menyusun kerja mereka di sekeliling saluran masa larian. Mereka mengekalkan satu tatasusuna callables—mutator, pengesah, penukar—dan melakukan iterasi ke atas setiap nilai. Walaupun tatasusuna tersebut kosong, foreach atau array_reduce tetap dilaksanakan. HydraType menghapuskan perkara ini sepenuhnya. Semasa penjanaan kod, ia memeriksa apa yang sebenarnya diperlukan oleh sesuatu properti. Jika senarai operasi adalah kosong, ia mengeluarkan tugasan biasa seperti $object->name = $data['name'];. Masa larian tidak perlu melakukan iterasi pada apa pun, kerana penjana sudah pun melakukan pemikiran tersebut.
3. Gunakan Skop Closure untuk Menghapuskan Penulisan Refleksi
Properti peribadi (private properties) adalah masalah klasik bagi pemeta luaran. Jalan keluar biasa ialah ReflectionProperty::setValue(), tetapi kaedah itu membawa penalti yang berat berbanding akses properti secara terus. HydraType mengelaknya dengan menggunakan Closure::bind. Penulis yang dijana mengandungi closures yang diskopkan kepada kelas sasaran, yang membolehkan ia menyentuh properti peribadi dan dilindungi secara terus tanpa refleksi. Pengikatan (binding) berlaku sekali semasa pembinaan; selepas itu, closure berjalan pada kelajuan hampir asli (near-native speed).
4. Gunakan Semula Penulis (Writer)
Sesetengah perpustakaan menginstansiasi strategi atau graf refleksi baharu bagi setiap objek yang mereka hidrasi. HydraType mencipta penulisnya sekali, kemudian menggunakan semula instans tersebut merentasi beribu-ribu objek. Kos bagi setiap objek turun ke tahap minimum iaitu menetapkan nilai properti dan mengembalikan hasil.
Angka-angka
Pada PHP 8.2, perbezaannya sangat ketara:
- HydraType: 267.9 ns
- Ocramius GeneratedHydrator: 307.9 ns
- Symfony PropertyNormalizer: 8,499.0 ns
- Valinor: 10,755.2 ns
Symfony PropertyNormalizer dan Valinor adalah alatan yang berkuasa, tetapi ia bersifat umum. PropertyNormalizer adalah sebahagian daripada komponen pensiri yang mengendalikan pengesanan format, penormal, dan graf objek yang mendalam. Valinor sangat teliti tentang pokok jenis dan paksaan jenis. Kuasa tersebut datang dengan kosnya. Dalam ujian ini, ia adalah kira-kira tiga puluh hingga empat puluh kali lebih perlahan daripada HydraType. Malah Ocramius GeneratedHydrator, yang sudah pun menggunakan penjanaan kod, ketinggalan sedikit disebabkan perbezaan seni bina berkaitan saluran paip dan akses properti.
Konteks adalah penting. Satu pertanyaan pangkalan data atau satu kitaran balik HTTP jauh mengatasi semua angka ini. Jika anda menghidrasi tiga objek selepas satu pertanyaan, mengejar nanosaat adalah pembaziran tenaga. Jurang tersebut menjadi bermakna apabila anda melakukan penskalaan. Satu proses berkelompok yang menghidrasi lima puluh ribu rekod, atau pengguna barisan yang membina semula objek daripada tatasusunan cache, akan merasai perbezaan antara 267 nanosaat dan sepuluh mikrosaat. Darabkan merentasi medan dan baris, dan pemeta yang lebih pantas boleh menjimatkan beberapa saat daripada sesuatu tugasan tanpa mengubah sebarang logik perniagaan.
Pengajaran Sebenar
Pengajarannya di sini bukanlah setiap projek memerlukan penghidrasi tersuai. Tetapi pilihan seni bina akan memberikan kesan kumulatif. Dengan menjana kod yang hanya menyertakan apa yang anda minta, HydraType mengekalkan kepantasan properti biasa sambil tetap menawarkan jalan keluar untuk properti yang kompleks. Penukaran jenis dan objek bersarang wujud apabila anda memerlukannya, tetapi ia tidak menjejaskan semakan prestasi bagi setiap rentetan skalar pada objek tersebut.
Sebelum anda menukar alatan, lakukan profil. Jika hidrasi tidak muncul dalam jejak dunia nyata anda, maka tiada apa yang perlu diperbaiki. Tetapi jika anda menghantar set data yang besar melalui pemeta generik dan melihat penggunaan CPU melonjak, ingatlah bahawa penetapan PHP biasa masih wujud. Kadangkala kod yang paling pantas hanyalah kod yang anda tulis sendiri, dijana sekali dan kemudian dilupakan.
