Mengganti hot path di JavaScript aplikasi Anda dengan WebAssembly

Selalu cepat, ya

Dalam artikel sebelumnya, saya membahas bagaimana WebAssembly memungkinkan Anda menghadirkan ekosistem library C/C++ ke web. Salah satu aplikasi yang menggunakan library C/C++ secara ekstensif adalah squoosh, yaitu aplikasi web kami yang memungkinkan Anda mengompresi gambar dengan berbagai codec yang telah dikompilasi dari C++ ke WebAssembly.

WebAssembly adalah mesin virtual level rendah yang menjalankan bytecode yang disimpan dalam file .wasm. Kode byte ini diketik dengan ketat dan terstruktur sedemikian rupa sehingga dapat dikompilasi dan dioptimalkan untuk sistem host dengan jauh lebih cepat daripada yang dilakukan JavaScript. WebAssembly menyediakan lingkungan untuk menjalankan kode yang dengan mempertimbangkan sandboxing dan penyematan sejak awal.

Berdasarkan pengalaman saya, sebagian besar masalah performa di web disebabkan oleh tata letak paksa dan paint berlebihan, tetapi terkadang aplikasi perlu melakukan tugas komputasi yang mahal dan memakan banyak waktu. WebAssembly dapat membantu.

Jalur Panas

Dalam squoosh, kami menulis fungsi JavaScript yang memutar buffering gambar sebanyak 90 derajat. Meskipun OffscreenCanvas ideal untuk hal ini, tetapi tidak didukung di seluruh browser yang kami targetkan, dan sedikit bug di Chrome.

Fungsi ini melakukan iterasi pada setiap piksel gambar input dan menyalinnya ke posisi berbeda dalam gambar output untuk mencapai rotasi. Untuk gambar berukuran 4094 x 4096 piksel (16 megapiksel), diperlukan lebih dari 16 juta iterasi blok kode dalam, yang kami sebut sebagai "jalur panas". Meskipun jumlah iterasi yang cukup besar, dua dari tiga browser yang kami uji menyelesaikan tugas dalam 2 detik atau kurang. Durasi yang dapat diterima untuk jenis interaksi ini.

for (let d2 = d2Start; d2 >= 0 && d2 < d2Limit; d2 += d2Advance) {
    for (let d1 = d1Start; d1 >= 0 && d1 < d1Limit; d1 += d1Advance) {
    const in_idx = ((d1 * d1Multiplier) + (d2 * d2Multiplier));
    outBuffer[i] = inBuffer[in_idx];
    i += 1;
    }
}

Namun, satu browser memerlukan waktu lebih dari 8 detik. Cara browser mengoptimalkan JavaScript sangat rumit, dan mesin yang berbeda mengoptimalkan untuk hal yang berbeda pula. Sebagian mengoptimalkan eksekusi mentah, sebagian mengoptimalkan interaksi dengan DOM. Dalam hal ini, kita mencapai jalur yang tidak dioptimalkan di satu browser.

Di sisi lain, WebAssembly dibuat sepenuhnya berdasarkan kecepatan eksekusi mentah. Jadi, jika kita menginginkan performa yang cepat dan dapat diprediksi di seluruh browser untuk kode seperti ini, WebAssembly dapat membantu.

WebAssembly untuk performa yang dapat diprediksi

Secara umum, JavaScript dan WebAssembly dapat mencapai performa puncak yang sama. Namun, untuk JavaScript, performa ini hanya dapat dijangkau di "jalur cepat", dan sering kali sulit untuk tetap berada di "jalur cepat" tersebut. Salah satu manfaat utama yang ditawarkan WebAssembly adalah performa yang dapat diprediksi, bahkan di seluruh browser. Dengan pengetikan yang ketat dan arsitektur tingkat rendah, compiler dapat memberikan jaminan yang lebih kuat sehingga kode WebAssembly hanya perlu dioptimalkan sekali dan akan selalu menggunakan "jalur cepat".

Menulis untuk WebAssembly

Sebelumnya, kami menggunakan library C/C++ dan mengompilasinya ke WebAssembly untuk menggunakan fungsinya di web. Kami tidak benar-benar menyentuh kode library, kami hanya menulis sejumlah kecil kode C/C++ untuk membentuk jembatan antara browser dan library. Kali ini motivasi kita berbeda: Kita ingin menulis sesuatu dari awal dengan mempertimbangkan WebAssembly sehingga kita dapat memanfaatkan keunggulan yang dimiliki WebAssembly.

Arsitektur WebAssembly

Saat menulis untuk WebAssembly, sebaiknya Anda memahami sedikit lebih banyak tentang apa sebenarnya WebAssembly.

Mengutip WebAssembly.org:

Saat mengompilasi potongan kode C atau Rust ke WebAssembly, Anda akan mendapatkan file .wasm yang berisi deklarasi modul. Deklarasi ini terdiri dari daftar "impor" yang diharapkan modul dari lingkungannya, daftar ekspor yang disediakan oleh modul ini untuk host (fungsi, konstanta, potongan memori), dan tentu saja petunjuk biner sebenarnya untuk fungsi yang ada di dalamnya.

Sesuatu yang tidak saya sadari sampai saya memeriksanya: Tumpukan yang membuat WebAssembly menjadi "mesin virtual berbasis stack" tidak disimpan dalam potongan memori yang digunakan modul WebAssembly. Stack ini sepenuhnya bersifat internal VM dan tidak dapat diakses oleh developer web (kecuali melalui DevTools). Dengan demikian, Anda dapat menulis modul WebAssembly yang tidak memerlukan memori tambahan sama sekali dan hanya menggunakan stack internal VM.

Dalam kasus ini, kita perlu menggunakan beberapa memori tambahan untuk memungkinkan akses arbitrer ke piksel gambar dan menghasilkan versi rotasi gambar tersebut. Inilah fungsi WebAssembly.Memory.

Pengelolaan memori

Umumnya, setelah menggunakan memori tambahan, Anda akan merasa perlu untuk mengelola memori tersebut. Bagian memori mana yang sedang digunakan? Mana yang gratis? Misalnya, di C, Anda memiliki fungsi malloc(n) yang menemukan ruang memori n byte berturut-turut. Fungsi semacam ini juga disebut "allocator". Tentu saja, implementasi pengalokasi yang digunakan harus disertakan dalam modul WebAssembly dan akan meningkatkan ukuran file. Ukuran dan performa fungsi pengelolaan memori ini dapat sangat bervariasi, bergantung pada algoritme yang digunakan. Itulah sebabnya banyak bahasa menawarkan beberapa implementasi yang dapat dipilih ("dmalloc", "emmalloc", "wee_alloc", dll.).

Dalam kasus ini, kita mengetahui dimensi gambar input (dan juga dimensi gambar output) sebelum menjalankan modul WebAssembly. Di sini kita melihat peluang: Secara tradisional, kita akan meneruskan buffering RGBA gambar input sebagai parameter ke fungsi WebAssembly dan menampilkan gambar yang diputar sebagai nilai yang ditampilkan. Untuk menghasilkan nilai hasil tersebut, kita harus menggunakan alokator. Namun, karena kita mengetahui jumlah total memori yang diperlukan (dua kali ukuran gambar input, sekali untuk input dan sekali untuk output), kita dapat menempatkan gambar input ke dalam memori WebAssembly menggunakan JavaScript, menjalankan modul WebAssembly untuk menghasilkan gambar ke-2 yang dirotasi, lalu menggunakan JavaScript untuk membaca kembali hasilnya. Kita dapat keluar dari program ini tanpa menggunakan manajemen memori sama sekali.

Dimanjakan dengan banyak pilihan

Jika telah melihat fungsi JavaScript asli yang ingin kita gunakan untuk WebAssembly-fy, Anda dapat melihat bahwa fungsi tersebut hanyalah kode komputasi tanpa API khusus JavaScript. Dengan demikian, seharusnya cukup mudah untuk membawa kode ini ke bahasa apa pun. Kami mengevaluasi 3 bahasa berbeda yang dikompilasi untuk WebAssembly: C/C++, Rust, dan AssemblyScript. Satu-satunya pertanyaan yang perlu kita jawab untuk setiap bahasa adalah: Bagaimana cara mengakses memori mentah tanpa menggunakan fungsi pengelolaan memori?

C dan Emscripten

Emscripten adalah compiler C untuk target WebAssembly. Tujuan Emscripten adalah berfungsi sebagai pengganti langsung untuk compiler C populer seperti GCC atau clang dan sebagian besar kompatibel dengan flag. Ini adalah bagian inti dari misi Emscripten karena ingin membuat kompilasi kode C dan C++ yang ada ke WebAssembly semudah mungkin.

Mengakses memori mentah bersifat alami seperti C dan pointer ada karena alasan tersebut:

uint8_t* ptr = (uint8_t*)0x124;
ptr[0] = 0xFF;

Di sini, kita mengubah angka 0x124 menjadi pointer ke bilangan bulat 8-bit (atau byte) yang tidak ditandatangani. Hal ini secara efektif mengubah variabel ptr menjadi array mulai dari alamat memori 0x124, yang dapat kita gunakan seperti array lainnya, sehingga kita dapat mengakses setiap byte untuk membaca dan menulis. Dalam kasus ini, kita melihat buffering RGBA dari gambar yang ingin kita atur ulang untuk mencapai rotasi. Untuk memindahkan piksel, sebenarnya kita perlu memindahkan 4 byte berurutan sekaligus (satu byte untuk setiap saluran: R, G, B, dan A). Untuk mempermudah, kita dapat membuat array bilangan bulat 32-bit yang tidak ditandatangani. Berdasarkan konvensi, gambar input kita akan dimulai di alamat 4 dan gambar output akan dimulai langsung setelah gambar input berakhir:

int bpp = 4;
int imageSize = inputWidth * inputHeight * bpp;
uint32_t* inBuffer = (uint32_t*) 4;
uint32_t* outBuffer = (uint32_t*) (inBuffer + imageSize);

for (int d2 = d2Start; d2 >= 0 && d2 < d2Limit; d2 += d2Advance) {
    for (int d1 = d1Start; d1 >= 0 && d1 < d1Limit; d1 += d1Advance) {
    int in_idx = ((d1 * d1Multiplier) + (d2 * d2Multiplier));
    outBuffer[i] = inBuffer[in_idx];
    i += 1;
    }
}

Setelah mem-porting seluruh fungsi JavaScript ke C, kita dapat mengompilasi file C dengan emcc:

$ emcc -O3 -s ALLOW_MEMORY_GROWTH=1 -o c.js rotate.c

Seperti biasa, emscripten menghasilkan file kode glue bernama c.js dan modul wasm yang disebut c.wasm. Perhatikan bahwa modul wasm di-gzip hanya menjadi ~260 Byte, sedangkan kode glue sekitar 3,5 KB setelah gzip. Setelah mengutak-atik, kami dapat menghapus kode glue dan membuat instance modul WebAssembly dengan API biasa. Hal ini sering kali dimungkinkan dengan Emscripten selama Anda tidak menggunakan apa pun dari library standar C.

Rust

Rust adalah bahasa pemrograman baru dan modern dengan sistem jenis yang lengkap, tanpa runtime dan model kepemilikan yang menjamin keamanan memori dan keamanan thread. Rust juga mendukung WebAssembly sebagai fitur inti dan tim Rust telah menyumbangkan banyak alat yang sangat baik untuk ekosistem WebAssembly.

Salah satu alat tersebut adalah wasm-pack, dari kelompok kerja Rustwasm. wasm-pack mengambil kode Anda dan mengubahnya menjadi modul yang mudah digunakan untuk web dan berfungsi bawaan dengan bundler seperti webpack. wasm-pack adalah pengalaman yang sangat nyaman, tetapi saat ini hanya berfungsi untuk Rust. Grup ini mempertimbangkan untuk menambahkan dukungan bagi bahasa penargetan WebAssembly lainnya.

Di Rust, {i>slices<i} adalah {i>array<i} di C. Dan seperti di C, kita perlu membuat irisan yang menggunakan alamat awal kita. Hal ini bertentangan dengan model keamanan memori yang diterapkan Rust. Jadi, untuk memahaminya, kita harus menggunakan kata kunci unsafe agar kita dapat menulis kode yang tidak sesuai dengan model tersebut.

let imageSize = (inputWidth * inputHeight) as usize;
let inBuffer: &mut [u32];
let outBuffer: &mut [u32];
unsafe {
    inBuffer = slice::from_raw_parts_mut::<u32>(4 as *mut u32, imageSize);
    outBuffer = slice::from_raw_parts_mut::<u32>((imageSize * 4 + 4) as *mut u32, imageSize);
}

for d2 in 0..d2Limit {
    for d1 in 0..d1Limit {
    let in_idx = (d1Start + d1 * d1Advance) * d1Multiplier + (d2Start + d2 * d2Advance) * d2Multiplier;
    outBuffer[i as usize] = inBuffer[in_idx as usize];
    i += 1;
    }
}

Mengompilasi file Rust menggunakan

$ wasm-pack build

menghasilkan modul wasm 7,6 KB dengan sekitar 100 byte kode lem (keduanya setelah gzip).

AssemblyScript

AssemblyScript adalah project yang cukup muda yang bertujuan menjadi compiler TypeScript-to-WebAssembly. Namun, perlu diperhatikan bahwa skrip ini tidak hanya akan menggunakan TypeScript apa pun. AssemblyScript menggunakan sintaksis yang sama dengan TypeScript, tetapi mengganti library standar untuk miliknya sendiri. Library standar mereka memodelkan kemampuan WebAssembly. Artinya, Anda tidak bisa hanya mengompilasi TypeScript yang tersimpan di WebAssembly. Namun, artinya Anda tidak perlu mempelajari bahasa pemrograman baru untuk menulis WebAssembly.

    for (let d2 = d2Start; d2 >= 0 && d2 < d2Limit; d2 += d2Advance) {
      for (let d1 = d1Start; d1 >= 0 && d1 < d1Limit; d1 += d1Advance) {
        let in_idx = ((d1 * d1Multiplier) + (d2 * d2Multiplier));
        store<u32>(offset + i * 4 + 4, load<u32>(in_idx * 4 + 4));
        i += 1;
      }
    }

Mengingat jenis platform kecil yang dimiliki fungsi rotate(), port kode ini dapat dilakukan dengan mudah ke AssemblyScript. Fungsi load<T>(ptr: usize) dan store<T>(ptr: usize, value: T) disediakan oleh AssemblyScript untuk mengakses memori mentah. Untuk mengompilasi file AssemblyScript, kita hanya perlu menginstal paket npm AssemblyScript/assemblyscript dan menjalankan

$ asc rotate.ts -b assemblyscript.wasm --validate -O3

AssemblyScript akan memberi kita modul wasm ~300 Byte dan tanpa kode glue. Modul ini hanya berfungsi dengan WebAssembly API vanila.

Forensik WebAssembly

Rust memiliki 7,6 KB yang sangat besar jika dibandingkan dengan 2 bahasa lainnya. Ada beberapa alat dalam ekosistem WebAssembly yang dapat membantu Anda menganalisis file WebAssembly (terlepas dari bahasa yang digunakan) dan memberitahukan apa yang terjadi serta membantu memperbaiki situasi Anda.

Berkelok-kelok

Twiggy adalah alat lain dari tim WebAssembly Rust yang mengekstrak banyak data bermanfaat dari modul WebAssembly. Alat ini tidak khusus Rust dan memungkinkan Anda memeriksa hal-hal seperti grafik panggilan modul, menentukan bagian yang tidak digunakan atau berlebihan, dan mencari bagian mana yang berkontribusi pada total ukuran file modul Anda. Yang terakhir dapat dilakukan dengan perintah top Twiggy:

$ twiggy top rotate_bg.wasm
Screenshot penginstalan Twiggy

Dalam hal ini, kita dapat melihat bahwa sebagian besar ukuran file berasal dari allocator. Hal ini mengejutkan karena kode kita tidak menggunakan alokasi dinamis. Faktor besar lainnya adalah subbagian "nama fungsi".

wasm-strip

wasm-strip adalah alat dari WebAssembly Binary Toolkit, atau disingkat wabt. Library ini berisi beberapa alat yang memungkinkan Anda memeriksa dan memanipulasi modul WebAssembly. wasm2wat adalah membongkar yang mengubah modul wasm biner menjadi format yang dapat dibaca manusia. Wabt juga berisi wat2wasm yang memungkinkan Anda mengubah format yang dapat dibaca manusia kembali menjadi modul wasm biner. Meskipun kami menggunakan dua alat pelengkap ini untuk memeriksa file WebAssembly, kami mendapati wasm-strip sebagai yang paling berguna. wasm-strip menghapus bagian dan metadata yang tidak diperlukan dari modul WebAssembly:

$ wasm-strip rotate_bg.wasm

Ini mengurangi ukuran file modul karat dari 7,5 KB menjadi 6,6 KB (setelah gzip).

wasm-opt

wasm-opt adalah alat dari Binaryen. Fungsi ini mengambil modul WebAssembly dan mencoba mengoptimalkannya untuk ukuran dan performa hanya berdasarkan bytecode. Beberapa {i>tool<i} seperti Emscripten sudah menjalankan alat ini, beberapa lainnya tidak. Ada baiknya Anda mencoba menghemat beberapa {i>byte<i} tambahan dengan menggunakan alat-alat ini.

wasm-opt -O3 -o rotate_bg_opt.wasm rotate_bg.wasm

Dengan wasm-opt, kita dapat memangkas beberapa byte lagi untuk menyisakan total 6,2 KB setelah gzip.

#![no_std]

Setelah beberapa konsultasi dan riset, kami menulis ulang kode Rust tanpa menggunakan library standar Rust, menggunakan fitur #![no_std]. Tindakan ini juga akan menonaktifkan alokasi memori dinamis sepenuhnya, sehingga menghapus kode pelacak dari modul kita. Mengompilasi file Rust ini dengan

$ rustc --target=wasm32-unknown-unknown -C opt-level=3 -o rust.wasm rotate.rs

menghasilkan modul wasm 1,6 KB setelah wasm-opt, wasm-strip, dan gzip. Meskipun masih lebih besar dari modul yang dihasilkan oleh C dan AssemblyScript, modul ini cukup kecil untuk dianggap sebagai ringan.

Performa

Sebelum kami mengambil kesimpulan berdasarkan ukuran file saja — kami melanjutkan perjalanan ini untuk mengoptimalkan performa, bukan ukuran file. Jadi bagaimana kita mengukur kinerja dan apa hasilnya?

Cara menjalankan benchmark

Meskipun WebAssembly merupakan format bytecode level rendah, WebAssembly tetap perlu dikirim melalui compiler untuk menghasilkan kode mesin khusus host. Sama seperti JavaScript, compiler bekerja dalam beberapa tahap. Sederhananya: Tahap pertama jauh lebih cepat saat dikompilasi, tetapi cenderung menghasilkan kode yang lebih lambat. Setelah modul mulai berjalan, browser akan mengamati bagian mana yang sering digunakan dan mengirimkannya melalui compiler yang lebih optimal tetapi lebih lambat.

Kasus penggunaan kita menarik karena kode untuk memutar gambar akan digunakan satu kali, mungkin dua kali. Jadi, dalam sebagian besar kasus, kita tidak akan pernah mendapatkan manfaat dari compiler pengoptimalan. Hal ini penting untuk diingat saat menjalankan benchmark. Menjalankan modul WebAssembly 10.000 kali dalam satu loop akan memberikan hasil yang tidak realistis. Untuk mendapatkan angka yang realistis, kita harus menjalankan modul sekali dan membuat keputusan berdasarkan angka dari satu proses tersebut.

Perbandingan performa

Perbandingan kecepatan per bahasa
Perbandingan kecepatan per browser

Kedua grafik ini adalah tampilan yang berbeda pada data yang sama. Pada grafik pertama, kita membandingkan per browser, pada grafik kedua kita membandingkan per bahasa yang digunakan. Harap dicatat bahwa saya memilih skala waktu logaritmik. Selain itu, semua benchmark harus menggunakan gambar pengujian 16 megapiksel dan mesin host yang sama, kecuali untuk satu browser, yang tidak dapat dijalankan di komputer yang sama.

Tanpa terlalu banyak menganalisis grafik ini, jelaslah bahwa kami telah memecahkan masalah performa awal kami: Semua modul WebAssembly berjalan dalam waktu ~500 md atau kurang. Hal ini mengonfirmasi hal yang telah kita siapkan di awal: WebAssembly memberi Anda performa yang dapat diprediksi. Apa pun bahasa yang kami pilih, variasi antara browser dan bahasa sangat minim. Tepatnya: Standar deviasi JavaScript di semua browser adalah ~400 md, sedangkan standar deviasi untuk semua modul WebAssembly kami di semua browser adalah ~80 md.

Upaya

Metrik lainnya adalah besarnya upaya yang harus kami lakukan untuk membuat dan mengintegrasikan modul WebAssembly ke dalam squoosh. Sulit menetapkan nilai numerik pada upaya, jadi saya tidak akan membuat grafik apa pun tetapi ada beberapa hal yang ingin saya tunjukkan:

AssemblyScript berjalan lancar. Selain memungkinkan Anda menggunakan TypeScript untuk menulis WebAssembly, peninjauan kode menjadi sangat mudah bagi rekan-rekan saya, tetapi juga menghasilkan modul WebAssembly bebas lem yang berukuran sangat kecil dengan performa yang baik. Alat dalam ekosistem TypeScript, seperti lebih terlihat dan tslint, kemungkinan akan berfungsi dengan baik.

Rust yang dikombinasikan dengan wasm-pack juga sangat nyaman, tetapi lebih unggul pada project WebAssembly yang lebih besar karena binding dan pengelolaan memori diperlukan. Kita harus sedikit menyimpang dari {i>happy-path<i} untuk mencapai ukuran file yang kompetitif.

C dan Emscripten membuat modul WebAssembly yang sangat kecil dan berperforma tinggi secara langsung, tetapi tanpa keberanian untuk langsung memasukkan kode glue dan menguranginya hanya untuk kebutuhan dasar, ukuran total (modul WebAssembly + kode glue) akhirnya menjadi cukup besar.

Kesimpulan

Jadi, bahasa apa yang harus Anda gunakan jika Anda memiliki hot path JS dan ingin membuatnya lebih cepat atau lebih konsisten dengan WebAssembly. Seperti biasa dengan pertanyaan kinerja, jawabannya adalah: Tergantung. Jadi apa yang kita kirimkan?

Grafik perbandingan

Dibandingkan dengan kompromi performa / ukuran modul dari berbagai bahasa yang kami gunakan, pilihan terbaik adalah C atau AssemblyScript. Kami memutuskan untuk mengirimkan Rust. Ada beberapa alasan untuk keputusan ini: Semua codec yang dikirimkan di Squoosh sejauh ini dikompilasi menggunakan Emscripten. Kami ingin memperluas pengetahuan tentang ekosistem WebAssembly dan menggunakan bahasa yang berbeda dalam produksi. AssemblyScript adalah alternatif yang kuat, tetapi project ini relatif masih muda dan compilernya tidak sebaik compiler Rust.

Meskipun perbedaan ukuran file antara Rust dan ukuran bahasa lain terlihat cukup drastis dalam grafik pencar, pada kenyataannya tidak terlalu besar: Memuat 500B atau 1,6 KB bahkan melalui 2G membutuhkan waktu kurang dari 1/10 detik. Dan Rust mudah-mudahan akan segera menutup kesenjangan dalam hal ukuran modul.

Dalam hal performa runtime, Rust memiliki rata-rata yang lebih cepat di berbagai browser daripada AssemblyScript. Terutama pada project yang lebih besar, Rust akan cenderung menghasilkan kode dengan lebih cepat tanpa memerlukan pengoptimalan kode manual. Namun itu tidak boleh menghalangi Anda untuk menggunakan fitur yang paling nyaman bagi Anda.

Jadi, AssemblyScript merupakan penemuan yang hebat. Solusi ini memungkinkan developer web memproduksi modul WebAssembly tanpa harus mempelajari bahasa baru. Tim AssemblyScript sangat responsif dan secara aktif berupaya meningkatkan toolchain mereka. Kami pasti akan mengawasi AssemblyScript di masa mendatang.

Pembaruan: Karat

Setelah memublikasikan artikel ini, Nick Fitzgerald dari tim Rust mengarahkan kami ke buku Rust Wasm mereka yang luar biasa, yang berisi bagian tentang mengoptimalkan ukuran file. Dengan mengikuti petunjuk yang ada (terutama mengaktifkan pengoptimalan waktu link dan penanganan panik manual), kita dapat menulis kode Rust “normal” dan kembali menggunakan Cargo (npm Rust) tanpa memperbesar ukuran file. Modul Rust berakhir dengan 370B setelah {i>gzip<i}. Untuk mengetahui detailnya, lihat PR yang saya buka di Squoosh.

Terima kasih banyak kepada Ashley Williams, Steve Klabnik, Nick Fitzgerald, dan Max Graey atas semua bantuan mereka dalam perjalanan ini.