Uygulamanızın JavaScript'indeki aktif yolu WebAssembly ile değiştirme

Sürekli hızlı ilerliyor

Önceki bahsettiğim makaleler ve WebAssembly C/C++ kitaplık ekosistemini web'e taşımanızı sağlar. Uygulama içi C/C++ kitaplıklarını kapsamlı bir şekilde kullanır. squoosh, haline getirilmiş çeşitli codec'ler ile sıkıştırmanıza olanak tanıyan bir web C++'tan WebAssembly'ye derlenir.

WebAssembly, depolanan bayt kodunu çalıştıran alt düzey bir sanal makinedir. .wasm dosyada. Bu bayt kodu, çok basit bir şekilde yazılmış ve açısından çok daha hızlı bir şekilde derlenip optimize edilebilmesini JavaScript bunları yapabilir. WebAssembly, aklınızdan çıkarmayın ve yerleştirmeyi unutmayın.

Deneyimlerime göre, web'deki performans sorunlarının çoğu, düzen ve aşırı miktarda boyama da var. Ancak ara sıra bir uygulamanın maliyetli ve fazla zaman alan bir görevdir. WebAssembly size burayı tıklayın.

Popüler Yol

Squoosh'ta bir JavaScript işlevi yazdık bir resim arabelleğini 90 derecenin katları kadar döndüren bir uygulamadır. Bu sırada OffscreenCanvas, ve bu özellik, hedeflediğimiz tarayıcılarda desteklenmiyor ve biraz Chrome'da büyük hatalar meydana geldi.

Bu işlev, bir giriş resminin her pikseli üzerinde yineleme yapar ve bunu bir çıkış resminde farklı bir konuma getirerek döndürmeyi sağlar. 4.094 piksel için 4096 piksel (16 megapiksel) bir görüntünün 16 milyonun üzerinde iterasyonu Buna "kısa yol" adını veriyoruz. Proje oldukça büyük olsa da test ettiğimiz her üç tarayıcıdan ikisi, işlemi 2 saniyede bitirir saniye veya daha kısa. Bu tür etkileşim için kabul edilebilir bir süre.

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;
    }
}

Ancak bir tarayıcı 8 saniyeden uzun sürüyor. Tarayıcıların JavaScript'i optimize etme şekli gerçekten karmaşıktır ve farklı motorlar farklı şeyler için optimizasyon yapar. Bazıları ham yürütme için, bazıları DOM ile etkileşim için optimizasyon yapar. İçinde Bu örnekte, bir tarayıcıda optimize edilmemiş bir yola ulaştık.

Öte yandan WebAssembly, tamamen ham yürütme hızıyla geliştirilmiştir. ODK Bunun gibi kodların tarayıcılarda hızlı, tahmin edilebilir performans göstermesini istiyorsak, WebAssembly size yardımcı olabilir.

Tahmin edilebilir performans için WebAssembly

Genel olarak, JavaScript ve WebAssembly aynı en yüksek performansı elde edebilir. Bununla birlikte, JavaScript'te bu performansa yalnızca "hızlı yolda", ve bu "hızlı yolda" kalmak genellikle zordur. Projenin yaşam döngüsünün WebAssembly, farklı tarayıcılarda bile tahmin edilebilir performans sunar. Katı Yazma ve alt düzey mimari, derleyicinin daha güçlü WebAssembly kodunun yalnızca bir kez optimize edilmesini ve her zaman "hızlı yolu" kullanın.

WebAssembly için yazma

Daha önce C/C++ kitaplıklarını alıp yardımcı olmasını sağlar. Kütüphanelerin koduna dokunmadık, Chrome Tarayıcı ile Google Chrome arasında bir köprü oluşturmak için ve kütüphane. Bu sefer motivasyonumuz farklı: Birbirine sıfırdan bir şeyler oluşturabilmemiz için WebAssembly'yi daha fazla avantaj sunar.

WebAssembly mimarisi

WebAssembly için yazarken, kampanya hakkında biraz daha bilgi sahibi olmanız aslında ne olduğunu öğrenin.

WebAssembly.org'dan alıntı yapmak için:

WebAssembly'de C veya Rust kodu parçası derlediğinizde bir .wasm alırsınız dosyasını seçin. Bu beyan aşağıdakilerin bir listesinden oluşur: "içe aktarmalar" dışa aktarımların bir listesidir. Bu dışa aktarımların ana makineye (işlevler, sabit değerler, bellek parçaları) ve fonksiyonların gerçek ikili talimatlarıdır.

Bunu inceleyene kadar fark etmediğiniz bir şeyi fark ettim: WebAssembly, "yığın tabanlı bir sanal makine" parçasında depolanmaz kullanılan belirli bir bellek birimi anlamına gelir. Yığın tamamen sanal makine içindedir ve web geliştiricileri tarafından erişilemez (Geliştirici Araçları hariç). Bu nedenle, veya hiç ek belleğe ihtiyaç duymayan WebAssembly modülleri yazmak ve yalnızca sanal makine dahili yığınını kullanın.

Örneğimizde rastgele erişime izin vermek için ek bellek kullanmamız gerekecek piksel olarak ayarlayarak bu resmin döndürülmüş bir sürümünü oluşturur. Bu WebAssembly.Memory ne işe yarar?

Bellek yönetimi

Genellikle, ek bellek kullandığınızda bir şekilde o anıyı yönetin. Belleğin hangi bölümleri kullanılıyor? Hangileri ücretsiz? Örneğin C'de, bellek alanı bulan bir malloc(n) fonksiyonu vardır. / art arda n bayt. Bu tür işlevler "ayırıcılar" olarak da adlandırılır. Kullanımdaki toplayıcının uygulaması elbette webAssembly modülünü kullanabilir ve dosyanızın boyutunu büyütebilirsiniz. Bu boyut ve performans bağlı olarak, veri feed'inizde belirtilen bu bellek yönetimi işlevleri birçok dilin birden fazla uygulama seçeneği sunmasının nedeni, kullanılan algoritmanın ("dmalloc", "emmalloc", "wee_alloc" vb.) arasından seçim yapabilirsiniz.

Örneğimizde, giriş resminin boyutlarını biliyoruz (dolayısıyla da çıkış resminin boyutları) çalıştıracağız. Burada bir fırsat olduğunu görmüş olduk: Geleneksel olarak, giriş resminin RGBA tamponu parametresini WebAssembly işlevine ekleyebilir ve döndürülen resmi, değer. Bu döndürülen değeri oluşturmak için ayırıcıdan yararlanmamız gerekir. Ama gerekli toplam bellek miktarını bildiğimiz için (giriş boyutunun iki katı bir kez giriş ve bir kez çıkış için olmak üzere), giriş resmini JavaScript kullanan WebAssembly belleği kullanıyorsanız web sayfası oluşturmak için WebAssembly modülünü 2. döndürülmüş resim ve ardından, sonucu tekrar okumak için JavaScript'i kullanın. Böylelikle ve hiçbir bellek yönetimi kullanmanıza gerek yok.

Çok fazla seçenek yok

Orijinal JavaScript işlevine baktıysanız olduğunu fark ederseniz, bunun tamamen sayısal bir işlem JavaScript'e özel API'ler içermeyen kod. Dolayısıyla, oldukça düz bu kodu herhangi bir dile yönlendirebilirsiniz. 3 farklı dili değerlendirdik C/C++, Rust ve AssemblyScript: C/C++, Rust ve AssemblyScript. Tek soru tüm diller için cevap vermemiz gereken soru şudur: Ham belleğe nasıl erişiriz? ne kadar iyi performans gösterir?

C ve Emscripten

Emscripten, WebAssembly hedefi için bir C derleyicisidir. Emscripten'in hedefi GCC veya clang gibi tanınmış C derleyicileri için bir alternatif işlevi görür ve çoğunlukla flag'lerle uyumludur. Bu, Emscripten'in misyonunun temel bir parçasıdır Google Analytics 4'te, mevcut C ve C++ kodlarını WebAssembly'de derlemeyi yapmasını sağlar.

Ham belleğe erişmek C'nin doğasında vardır ve bu süreç için işaretçiler neden:

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

Burada 0x124 sayısını imzasız, 8 bitlik bir işaretçiye dönüştürüyoruz. tam sayılar (veya baytlar). Bu, ptr değişkenini etkili bir şekilde diziye dönüştürür. ile başlayan bir 0x124 bellek adresi oluştururuz. Böylece okuma ve yazma işlemleri için baytlara ayrı ayrı erişebiliriz. Bizim örneğimizde elde etmek için yeniden sıralamak istediğimiz bir resmin RGBA tamponuna baktıklarını sağlayabilir. Bir pikseli taşımak için tek seferde art arda 4 bayt taşımamız gerekir (her kanal için bir bayt: R, G, B ve A). Bunu kolaylaştırmak için 32 bit tam sayılar dizisidir. Kural olarak, giriş resmimiz çıkış resmi ise giriş görüntüsünden hemen sonra başlar. bitiş:

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;
    }
}

JavaScript işlevinin tamamını C'ye taşıdıktan sonra, C dosyasını derleyebiliriz emcc ile:

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

Her zaman olduğu gibi, emscripten, c.js adında bir yapışkan kod dosyası ve bir wasm modülü oluşturur. adı c.wasm. Wasm modülünün gzip'i yalnızca ~260 Bayt'a çıkaracağını, yapışkan kodun boyutu, gzip'ten sonra yaklaşık 3,5 KB'tır. Biraz oynadıktan sonra, bu oyunlardan bir kod yazmanızı ve WebAssembly modüllerini, normal API'lerle örneklendirmenizi öneririz. Herhangi bir şey kullanmadığınız sürece Emscripten'de bu durumla sık sık karşılaşılır C standart kitaplığından alınır.

Rust

Rust, zengin türde bir sisteme sahip, çalışma zamanı gerektirmeyen yeni ve modern bir programlama dilidir ve bellek güvenliği ile iş parçacığı güvenliğini garanti eden bir sahiplik modeli içerir. Pas temel bir özellik olarak WebAssembly'yi destekliyor. Rust ekibi, WebAssembly ekosistemine birçok mükemmel araca katkıda bulundu.

Bu araçlardan biri wasm-pack'dır. rustwasm çalışma grubuna ulaştı. wasm-pack. kodunuzu alıp web'de çalışan bir modüle dönüştürür ilk kullanım olanağı sunar. wasm-pack son derece kullanışlı bir deneyim sunuyor, ancak şu anda yalnızca Rust'ta kullanılabilir. Grup diğer WebAssembly hedefleme dilleri için destek sunmayı değerlendiriyoruz.

Rust'ta dilimler, C'de bulunan dizilerdir. Tıpkı C'de olduğu gibi, başlangıç adreslerimizi kullanan dilimler. Bu durum, bellek güvenliği modeline aykırıdır bu, Rust'ın zorunlu kıldığı bir anlam taşıyor. unsafe anahtar kelimesini kullanmamız için bu modele uymayan kod yazmamızı sağlar.

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;
    }
}

Rust dosyalarını derleyerek

$ wasm-pack build

yaklaşık 100 bayt yapışkan koda sahip (her ikisi de gzip'ten sonra) 7,6 KB'lık bir wasm modülü oluşturur.

AssemblyScript

AssemblyScript, oldukça yeni bir proje olan 3D kodlu proje aracıdır. İnsanların Ancak, herhangi bir TypeScript kullanmayacağını unutmayın. AssemblyScript, TypeScript ile aynı söz dizimini kullanır ancak standart kendi kitaplıklarını oluşturabilirler. Kullandıkları standart kitaplıktaki WebAssembly. Bu, yalanladığınız herhangi bir TypeScript'i derleyemeyeceğiniz anlamına gelir. webAssembly'yi de ekler, ancak bu anlamına gelir programlama dilini kullanabilirsiniz.

    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;
      }
    }

rotate() işlevimizin sahip olduğu küçük tür yüzeyi göz önüne alındığında, bu kodun AssemblyScript'e taşınması oldukça kolay. load<T>(ptr: usize) ve store<T>(ptr: usize, value: T) işlevleri AssemblyScript tarafından ham belleğe erişme. AssemblyScript dosyamızı derlemek için, sadece AssemblyScript/assemblyscript npm paketini yükleyip çalıştırmamız gerekiyor

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

AssemblyScript bize ~300 Bayt wasm modülü sağlar ancak yapışkan kodu yoktur. Modül, normal WebAssembly API'leriyle çalışır.

WebAssembly Adli Bilimleri

Rust'ın 7,6 KB boyutu, diğer 2 dille karşılaştırıldığında şaşırtıcı derecede büyük. Orada WebAssembly ekosisteminde bulunan ve verileri analiz etmenize yardımcı webAssembly dosyalarınız (oluşturulduğu dilden bağımsız olarak) ve ve aynı zamanda durumunuzu iyileştirmenize yardımcı olur.

Kıvrımlı

Twiggy, Rust'ın Bir WebAssembly'den çok sayıda bilgilendirici veri çıkaran WebAssembly ekibi modülünü kullanabilirsiniz. Bu araç Rust'a özgü değildir ve aşağıdaki gibi öğeleri incelemenize olanak tanır: modülün çağrı grafiğine bakarak, kullanılmayan veya gereksiz kısımları modülünüzün toplam dosya boyutuna katkıda bulunan bölümleri de görebilirsiniz. İlgili içeriği oluşturmak için kullanılan ikincisi Twiggy'nin top komutuyla yapılabilir:

$ twiggy top rotate_bg.wasm
Twiggy kurulum ekran görüntüsü

Bu örnekte, dosya boyutumuzun büyük bir kısmının ayırıcıdır. Kodumuz dinamik ayırmaları kullanmadığı için bu şaşırtıcıydı. Etki yaratan bir diğer faktör de "işlev adları"dır. bölüm oluşturabilirsiniz.

wasm şeridi

wasm-strip, WebAssembly İkili Program Araç Seti'ndeki bir araçtır veya kısaca wabt'tır. Bir WebAssembly modüllerini incelemenize ve değiştirmenize olanak tanıyan iki araçtan yararlanabilirsiniz. wasm2wat, ikili wasm modülünü bir sökücüye dönüştüren bir programdır. biçimi de vardır. Wabt, şunu da açmanızı sağlayan wat2wasm içerir: bu okunabilir biçimi tekrar ikili wasm modülüne dönüştürebilir. Kullanıcılarımız incelemek için bu iki tamamlayıcı aracı kullanarak wasm-strip olacaktır. wasm-strip gereksiz bölümleri kaldırır ve meta veriler aşağıda verilmiştir:

$ wasm-strip rotate_bg.wasm

Bu işlem, pas modülünün dosya boyutunu 7,5 KB'tan 6,6 KB'a (gzip'ten sonra) düşürür.

wasm-opt

wasm-opt, Binaryen tarafından sunulan bir araçtır. Bir WebAssembly modülü gerekir ve modülü hem boyut hem de bayt koduna göre performans göstermeyi sağlar. Emscripten gibi bazı araçlar bazıları ise yoktur. Genellikle ilerlemeye devam etmek için ek bayt olarak değerlendirebilirsiniz.

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

wasm-opt ile bir dizi bayt daha ayıklayabiliriz. Böylece, toplamda gzip'ten sonra 6,2 KB.

#![no_std]

Biraz danışmanlık ve araştırmadan sonra, bunu yapmadan önce Rust kodumuzu Rust'ın standart kitaplığındaki #![no_std] özelliğini kullanabilirsiniz. Bu, dinamik bellek ayırmalarını da tamamen devre dışı bırakarak ayırıcı kodunu modülümüzün içinden ayırabiliriz. Bu Rust dosyası derleniyor şununla:

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

wasm-opt, wasm-strip ve gzip'ten sonra 1,6 KB'lık wasm modülü sağladı. Ancak C ve AssemblyScript tarafından oluşturulan modüllerden daha büyük olduğundan, kabul edilir.

Performans

Sadece dosya boyutunu temel alarak varılan sonuçlara geçmeden önce optimize edebilirsiniz. Peki, performansı ve kaliteyi sonuçlar neydi?

Karşılaştırma yapma

WebAssembly düşük seviyeli bir bayt kodu biçimi olsa da, kullanarak ana makineye özel makine kodu oluşturabilirsiniz. Tıpkı JavaScript'te olduğu gibi, derleyici birçok aşamada çalışır. Basitçe ifade etmek gerekirse: İlk aşama, daha hızlı derlemeye başlar ancak daha yavaş kod üretme eğilimindedir. Modül başladıktan sonra çalışırken, tarayıcı hangi bölümlerin sık kullanıldığını gözlemler ve bu parçaları .

Kullanım alanımız, bir resmi döndürme kodunun kullanılması açısından ilginçtir. bir kez, belki iki kez. Böylece çoğu durumda ilk etapta yararları hakkında daha fazla bilgi edinin. Bu aşama, her şeyi öğrenebilirsiniz. WebAssembly modüllerimizi bir döngüde 10.000 kez çalıştırmak gerçekçi olmayan sonuçlara yol açabilir. Gerçekçi sayılara ulaşmak için modülü bir kez çalıştırmalı ve o tek koşuda elde edilen sayılara dayanarak kararlar vereceksiniz.

Performans karşılaştırması

Dil başına hız karşılaştırması
Tarayıcı başına hız karşılaştırması

Bu iki grafik, aynı verilerin farklı görünümleridir. İlk grafikte karşılaştırma yaptık. İkinci grafikte, kullanılan dil başına karşılaştırma yaptık. Lütfen logaritmik bir zaman ölçeği seçtiğimi unutmayın. Projedeki her iletişimin karşılaştırmalar için, aynı 16 megapiksellik test resmi ve makinede çalıştırılamayan bir tarayıcı bulunuyor.

Bu grafikleri çok fazla analiz etmemiş olsak da, orijinal tablomuza çok sayıda performans sorunu: Tüm WebAssembly modülleri ~500 ms veya daha kısa sürede çalışır. Bu başlangıçta sunduklarımızı doğruluyor: WebAssembly, size tahmin edilebilir bazı yolları da görmüştük. Hangi dili seçersek seçelim, tarayıcılar arasındaki farklılık ve dil kullanımı asgari düzeydedir. Tam olarak ifade etmek gerekirse: JavaScript'in standart sapması tüm tarayıcılarda standart sapma ~400 ms'dir. Tüm tarayıcılardaki WebAssembly modüllerinin uzunluğu yaklaşık 80 ms'dir.

Yapılması gerekenler

Diğer bir metrik de her şeyi oluşturup bunları bir araya getirmek için WebAssembly modülümüzü squoosh haline getiriyoruz. Kullanıcılara sayısal bir değer atamak Bu yüzden grafik oluşturmayacağım ama sunmak istediğim birkaç şey var şunu belirtin:

AssemblyScript sorunsuzdu. Yalnızca bu özellik için TypeScript'i kullanmakla kalmaz, meslektaşlarım için kod incelemesini çok kolay hale getiren bir araç olan WebAssembly yazma yapışkansız WebAssembly modüllerini sağlar. bazı yolları da görmüştük. TypeScript ekosistemindeki araçlar (ör. daha hoş ve tslint) muhtemelen işe yarar.

Paslanma özelliği wasm-pack ile birlikte kullanıldığında da son derece pratiktir ama en iyi sonucu verir. daha büyük WebAssembly projelerinde bağlama vardı. Ayrıca, bellek yönetimi de gerekir. Rekabet gücüne sahip bir pazar için mutlu yoldan biraz uzaklaşmak zorunda kaldık. dosya boyutu.

C ve Emscripten, çok küçük ve yüksek performanslı bir WebAssembly modülü oluşturdu. ama bir koda girip onu koda düşürme cesaretinizi toplam boyutun (WebAssembly modülü + yapışkan kod) sonuç olarak çok büyük oluyor.

Sonuç

Peki bir JS etkin yolunuz varsa ve bunu oluşturmak istiyorsanız veya WebAssembly ile daha hızlı veya tutarlı bir şekilde çalışmasını sağlar. Performansta her zamanki gibi yanıt: Duruma göre değişir. Peki ne gönderdik?

Karşılaştırma grafiği

Modül boyutu / farklı dillerin performans dengesi açısından karşılaştırma C veya AssemblyScript'in kullanılması en iyi seçenektir. Rust'u göndermeye karar verdik. Orada bu karar için birden fazla neden vardır: Şimdiye kadar Squoosh'ta gönderilen tüm codec'ler Emscripten kullanılarak derlenir. Proje yönetimi hakkında bilgimizi WebAssembly ekosistemini kullanın ve üretimde farklı bir dil kullanın. AssemblyScript güçlü bir alternatiftir ancak proje nispeten yenidir ve derleyici, Rust derleyici kadar olgun değildir.

Rust ve diğer dillerin dosya boyutu arasındaki fark oldukça büyük görünüyorsa aslında bu, o kadar da büyük bir şey değildir: 2G üzerinden bile 500 B veya 1,6 KB yüklemek saniyenin 1/10'undan kısa sürer. Ve Rust'ın yakında modül boyutu konusundaki boşluğu kapatacağını umuyorum.

Çalışma zamanı performansı açısından bakıldığında, Rust diğer tarayıcılarda AssemblyScript. Özellikle de büyük projelerde Rust'ın ve manuel kod optimizasyonlarına gerek kalmadan daha hızlı kod üretmenizi sağlar. Ama bu sizi en rahat ettiğiniz şeyi kullanmaktan alıkoymaz.

Hepsi bu kadar değil: AssemblyScript harika bir keşif oldu. Web'e izin verir geliştiricilerin yeni bir bilgi edinmek zorunda kalmadan WebAssembly modülleri dili'ne dokunun. AssemblyScript ekibi son derece duyarlıydı ve aktif bir şekilde geliştirmeye karar veriyor. Kesinlikle takip edeceğiz AssemblyScript'e erişim.

Güncelleme: Rust

Bu makaleyi yayınladıktan sonra, Nick Fitzgerald Rust ekibinden aldığımız mükemmel Rust Wasm kitabına dikkat edin. dosya boyutunu optimize etmeyle ilgili bir bölüm. (en önemlisi, bağlantı süresi optimizasyonlarını ve manuel panik müdahale) "normal" Rust kodu yazmamızı ve Dosya boyutunu büyütmeden Cargo (Rust'un npm kadarı). Rust modülü sona eriyor 370B'ye yükseltiyorlar. Ayrıntılar için lütfen Squoosh'ta açtığım halkla ilişkiler departmanına göz atın.

Bu yolculukta bize yardımcı oldukları için Ashley Williams, Steve Klabnik, Nick Fitzgerald ve Max Graey'e özel teşekkürlerimizi sunuyoruz.