Neredeyse iki hafta önce, Microsoft, TypeScript derleyicisini JavaScript'ten Go'ya taşıdıklarını ve çarpıcı bir 10 kat performans artışı vaadiyle duyurdu. Bu haber, TypeScript hayranlarından dil savaşı meraklılarına kadar herkesin yorum yaptığı teknoloji topluluklarında hızla yayıldı.
Duyuru, etkileyici performans rakamları ve iddialı hedeflerle öne çıksa da, bazı ilgi çekici detaylar henüz ele alınmadı. Bugün bunları ele alacağız. Neden bir hafta önce değil de bugün? Çünkü Architecture Weekly'de aceleci tık tuzakları istemiyoruz, değil mi? Gürültünün dinmesini kasıtlı olarak beklemek istedim.
Manşet rakamlarının altında tasarım seçimleri, performans tavizleri ve geliştirici araçlarının evrimi hakkında anlatılmaya değer bir hikaye yatıyor.
Derleyicilerle ilgilenmeseniz bile, sisteminizin tasarımı için çıkarılacak dersler var, örneğin:
Manşet performans iddialarının ötesine bakmak
Teknolojinizi problem alanınızla eşleştirmek
Runtime modelinizi anlamak.
Projeler geliştikçe temelleri yeniden gözden geçirmek.
Adım adım ele alalım, Microsoft makalesinin manşetinden başlayarak.
"10 Kat Daha Hızlı TypeScript" – Pek Sayılmaz
Öncelikle, Microsoft'un duyuru başlığında kafa karıştırıcı olabilecek bir şeyi açıklığa kavuşturalım:
"10 Kat Daha Hızlı TypeScript."
Gerçekten daha mı hızlı? Microsoft, Go ile yeniden yazılmış yeni kodu yayınladığında TypeScript ile yazılmış uygulamanız 10 kat daha mı hızlı olacak?

Aslında hızlanan şey TypeScript derleyicisidir, TypeScript dilinin kendisi veya JavaScript'in runtime performansı değil. TypeScript kodunuz daha hızlı derlenecek, ancak tarayıcıda veya Node.js'te aniden 10 kat daha hızlı çalışmayacak.
Bu biraz şuna benziyor:
"Arabanızı 10 kat daha hızlı yaptık!"
ve sonra sadece üretim sürecini hızlandırdıklarını açıklamak—arabanın kendisi hala aynı hızda gidiyor. Özellikle arabanızı aylardır bekliyorsanız bu hala değerli, ancak manşetin ima ettiği şey tam olarak bu değil.
"10 Kat Daha Hızlı" Manşetinin Ötesi
Anders Hejlsberg'in duyurusu etkileyici rakamlar sergiledi:

Bu tür dikkat çekici istatistikler daha derinlemesine bir bakışı hak ediyor, çünkü bu 10 kat hızlanmada birden fazla etken rol oynuyor. Bu sadece "Go, JavaScript'ten daha hızlıdır" demek değil.
Dürüst olalım: ne zaman "10 kat daha hızlı" iddiası görseniz, ona sağlıklı bir şüphecilikle yaklaşmalısınız. İlk tepkim, "Tamam, daha önce neyi suboptimal yapmışlardı?" oldu. Çünkü 10 kat iyileştirmeler havadan gelmez—genellikle bir şeyin ilk başta yeterince iyi yapılmadığını gösterir. İster implementasyon ister bazı tasarım seçimleri olsun.
"Node.js yavaştır" şeklinde yaygın bir inanış var, bu bir gerçekten çok tekrarlanan bir klişe. Bazı durumlarda doğru olabilir, ancak genel bir ifade değildir.
Birisi "Node.js yavaştır" derse, bu neredeyse C ve C++'ın yavaş olduğunu söylemek gibidir. Neden?
Node.js Mimarisi
Node.js, Google'ın V8 JavaScript motoru üzerine kurulmuştur, bu da Chrome'u güçlendiren aynı yüksek performanslı motordur. V8 motorunun kendisi C++ ile yazılmıştır ve Node.js, V8 motoru üzerine kurulmuş bir runtime ortamıdır. Bu mimari, Node.js performansını anlamanın anahtarıdır:
V8 Motoru: JavaScript'i Just-In-Time (JIT) derleme teknikleri kullanarak makine koduna derler
libuv: Asenkron I/O işlemlerini yöneten bir C kütüphanesi
Çekirdek Kütüphaneler: Birçoğu performans için C/C++ ile yazılmıştır. Bu yüzden Node.js, Go ve Rust veritabanı bağlayıcılarının performansları arasında genellikle büyük farklar olmaz.
JavaScript API'leri: Bu native implementasyonlar etrafındaki ince sarmalayıcılar
İnsanlar Node.js hakkında konuştuklarında, genellikle kritik operasyonların yüksek düzeyde optimize edilmiş C/C++ kodu tarafından yürütüldüğü bir sistemden bahsettiklerini fark etmezler. JavaScript'iniz genellikle sadece bu native implementasyonlara yapılan çağrıları orkestre eder.
İlginç bilgi: Node.js, uzun yıllardır mevcut olan en hızlı web sunucusu teknolojilerinden biri olmuştur. İlk ortaya çıktığında, özellikle yüksek eşzamanlılık, düşük hesaplama gerektiren iş yükleri için birçok geleneksel thread'li web sunucusunu benchmark'larda geride bıraktı. Bu bir tesadüf değildi—tasarım gereği böyleydi.
Belleğe Bağımlı (Memory-Bound) ve İşlemciye Bağımlı (CPU-Bound)
Node.js, özellikle web sunucuları ve ağ uygulamaları için tasarlandı; bunlar ağırlıklı olarak CPU-bound olmaktan ziyade memory-bound ve I/O-bound'dur:
Memory-bound işlemler, veriyi taşıma, dönüştürme veya depolama/alma işlemlerini içerir. Şunları düşünün:
JSON payload'larını ayrıştırma
Veri yapılarını dönüştürme
HTTP isteklerini yönlendirme
Yanıt verilerini biçimlendirme
I/O-bound işlemler, harici sistemleri beklemeyi içerir:
Veritabanı sorguları
Ağ istekleri
Dosya sistemi işlemleri
Harici API çağrıları
Tipik web uygulamaları için, zamanın çoğu bu I/O işlemlerinin tamamlanmasını beklemekle geçer. Tipik bir istek akışı şöyle olabilir:
HTTP isteğini al (memory-bound)
İstek verisini ayrıştır (memory-bound)
Veritabanını sorgula (I/O-bound, çoğunlukla bekleme)
Sonuçları işle (memory-bound)
Yanıtı biçimlendir (memory-bound)
HTTP yanıtını gönder (I/O-bound)
Bu iş akışında, gerçek CPU-yoğun hesaplama minimal düzeydedir. Çoğu web uygulaması zamanının %80-90'ını I/O işlemlerinin tamamlanmasını bekleyerek geçirir.
Node.js tam olarak bu senaryo için optimize edildi çünkü:
Engellemeyen (non-blocking) I/O: I/O işlemleri beklenirken Node.js diğer istekleri işleyebilir
C++ Temeli: Bellek işlemleri verimli C++ implementasyonlarına devredilir
Event Loop Verimliliği: Minimum ek yükle birçok eşzamanlı işlemi koordine etmede etkilidir
İşte birçok insanın kaçırdığı nokta: Node.js'deki I/O-yoğun operasyonlar neredeyse C seviyesinde hızlarda çalışır. Node.js'te bir ağ isteği yaptığınızda veya bir dosya okuduğunuzda, esasen ince bir JavaScript sarmalayıcısı ile C fonksiyonlarını çağırıyorsunuz.
Bu mimari, Node.js'i web sunucuları için devrim niteliğinde yaptı. Düzgün kullanıldığında, tek bir Node.js işlemi binlerce eşzamanlı bağlantıyı verimli bir şekilde yönetebilir ve genellikle tipik web iş yükleri için istek başına thread modellerinden daha iyi performans gösterir.
Bu, esasen çoklu görevlerin, insanlardaki görevler gibi, her zaman görevleri ele almanın en uygun yolu olmadığı fikrinden geldi. Birden çok görevi senkronize etmek ve bağlam değiştirmek ek yük getirir ve her zaman daha iyi sonuçlar vermez. Her şey, bir görevin eşzamanlı olarak yapılabilecek daha küçük parçalara bölünüp bölünemeyeceğine bağlıdır.
Node.js'in Zorlandığı Yer: CPU-Bound Operasyonlar
Node.js için gerçek performans zorlukları, CPU-yoğun görevlerdir—örneğin TypeScript derlemek. Bu iş yükleri, Node.js'in optimize edildiği web sunucusu senaryolarından temel olarak farklı özelliklere sahiptir.
CPU-bound operasyonlar, minimum bekleme ile ağır hesaplama içerir:
Karmaşık algoritmalar ve hesaplamalar
Büyük dosyaları ayrıştırma ve analiz etme
Görüntü/video işleme
Kod derleme
Bu senaryolarda, darboğaz harici sistemleri beklemek değil—ham hesaplama gücü ve runtime'ın algoritmaları ne kadar verimli çalıştırabildiğidir.
Tek Thread Sınırlaması
JavaScript, tek thread'li bir event loop modeliyle tasarlandı. Bu model, eşzamanlı I/O'yu (çoğu zaman beklemede harcanır) yönetmek için en iyi sonucu verir, ancak CPU-yoğun işlemler için sorunlu hale gelir:
// Node.js event loop'unun nasıl çalıştığına dair sözde kod
while (olayVarMi()) {
const olay = sonrakiOlayiAl();
olayiIsle(olay); // Eğer bu uzun sürerse,
// diğer her şey bekler
}
CPU-yoğun bir görev çalıştığında, bu tek thread'i tekeline alır. Bu süre zarfında, Node.js diğer olayları işleyemez, yeni istekleri yönetemez veya hatta mevcut olanlara yanıt veremez. Hesaplama tamamlanana kadar etkili bir şekilde engellenir.
İşte bu yüzden Node.js'te karmaşık bir algoritma çalıştırmak tüm web sunucunuzu yanıt vermez hale getirebilir—event loop hesaplama ile meşguldür ve gelen istekleri işleyemez.
Event Loop
JavaScript'te verimli CPU-yoğun kod yazmak, event loop'u anlamayı ve ona saygı duymayı gerektirir. Kod, kontrolü periyodik olarak devredecek şekilde yapılandırılmalıdır, böylece diğer işlemler devam edebilir:
//////////////////////////////////////////////
// Naif yaklaşım - event loop'u engeller
/////////////////////////////////////////////
function buyukVeriyiIsle(veri) {
for (let i = 0; i < veri.length; i++) {
// Saniyeler sürebilecek ağır hesaplama
ogeyiIsle(veri[i]);
}
return sonuclar
}
//////////////////////////////////
// Event-loop dostu yaklaşım
//////////////////////////////////
async function buyukVeriyiParcaliIsle(veri) {
const sonuclar = []
const PARCA_BOYUTU = 1000
for (let i = 0; i < veri.length; i += PARCA_BOYUTU) {
const parca = veri.slice(i, i + PARCA_BOYUTU)
// Bir parçayı işle
for (const oge of parca) {
sonuclar.push(ogeyiIsle(oge))
}
// Bir sonraki parçayı işlemeden önce event loop'a kontrolü devret
await new Promise(resolve => setTimeout(resolve, 0))
}
return sonuclar
}
Bu "parçalama" yaklaşımı işe yarar ancak karmaşıklık getirir ve kodunuzu nasıl yapılandırdığınızı temelden değiştirir. Bu, CPU-yoğun kodu diğer işlemleri engelleme endişesi olmadan basitçe yazabileceğiniz native threading'e sahip dillerden çok farklı bir programlama modelidir.
TypeScript derleyicisi gibi karmaşık bir uygulama için, kod tabanı büyüdükçe event loop ile bu dansı yönetmek giderek zorlaşır.
Ayrıca daha fazlasını okuyun:
Derleyiciler: CPU Yoğun Canavar
Bir derleyici, pratik olarak CPU-yoğun iş yüklerinin tipik bir örneğidir. Şunları yapması gerekir:
Kaynak kodunu token'lara ve soyut sözdizimi ağaçlarına (Abstract Syntax Trees - AST) ayrıştırmak
Karmaşık tip kontrolü ve çıkarımı yapmak
Dönüşümler ve optimizasyonlar uygulamak
Çıktı kodu üretmek
Bu operasyonlar karmaşık algoritmalar, büyük bellek yapıları ve çok fazla hesaplama içerir—tam da JavaScript'in yürütme modelini zorlayan türden işler.
Özellikle TypeScript için, dil yıllar içinde daha karmaşık ve güçlü hale geldikçe, derleyicinin giderek daha sofistike tip kontrolü, çıkarım ve kod üretimi ile başa çıkması gerekti. Bu ilerleme doğal olarak bir JavaScript runtime'ında verimli olanın sınırlarını zorladı.
Threading Modelleri Önemlidir: Event Loop ve Native Eşzamanlılık Karşılaştırması
JavaScript ve Go implementasyonları arasındaki performans farkı sadece ham dil hızıyla ilgili değil—temelde threading modelleri ve bunların problem alanıyla ne kadar eşleştiğiyle ilgilidir.
Belirtildiği gibi, Node.js bir event loop modeli üzerinde çalışır:
┌───────────────────────────┐
┌─>│ zamanlayıcılar │
│ └─────────────┬─────────────┘
│ ┌─────────────┴─────────────┐
│ │ bekleyen callback'ler │
│ └─────────────┬─────────────┘
│ ┌─────────────┴─────────────┐
│ │ boşta, hazırlık │
│ └─────────────┬─────────────┘ ┌───────────────┐
│ ┌─────────────┴─────────────┐ │ gelen: │
│ │ poll │<─────┤ bağlantılar, │
│ └─────────────┬─────────────┘ │ veri, vb. │
│ ┌─────────────┴─────────────┐ └───────────────┘
│ │ kontrol │
│ └─────────────┬─────────────┘
│ ┌─────────────┴─────────────┐
└──┤ kapatma callback'leri│
└───────────────────────────┘
Bu tek thread'li yaklaşım, TypeScript derlemek gibi CPU-yoğun işler için, thread'i tekeline almayan kod yazmanız gerektiği anlamına gelir. Pratikte bu, işi event loop'a kontrolü geri verebilecek daha küçük parçalara ayırmayı içerir.
Bir derleyici için bu, önemli tasarım zorlukları yaratır:
Yapay Parçalama: Derleyici aşamalarının doğal akışı (ayrıştır → analiz et → dönüştür → üret) kontrolü devredebilecek küçük adımlara bölünmelidir.
Karmaşık Durum Yönetimi: İşleme, event loop iterasyonları boyunca parçalandığından, derleyici durumu dikkatlice yönetilmeli ve devretmeler arasında korunmalıdır.
Yerellik Bozulması: Event loop, derleyici operasyonları arasında ilgisiz görevleri işlediğinde, CPU önbellek yerelliği faydaları kaybolur ve performans düşer.
Bağımlılık Zorlukları: Derleyicilerin bileşenler arasında karmaşık karşılıklı bağımlılıkları vardır. Doğal olarak sıralı bir süreci bir event loop'a uyacak şekilde kırmak genellikle karmaşık koordinasyon mantığı gerektirir.
Go: Goroutine'lar ile Native Eşzamanlılık
Go, buna karşılık, Go runtime'ı tarafından yönetilen hafif thread'ler olan goroutine'ları sunar:
┌─────────┐ ┌─────────┐ ┌─────────┐ ┌─────────┐
│Goroutine│ │Goroutine│ │Goroutine│ │Goroutine│ ... daha fazla
└────┬────┘ └────┬────┘ └────┬────┘ └────┬────┘
│ │ │ │
└───────────┴───────────┴───────────┘
│
┌────────┴────────┐
│ Go Zamanlayıcı│
└────────┬────────┘
│
┌──────┴──────┐
│ OS Thread'leri │
└─────────────┘
Bu model, derleyici aşamalarının minimum koordinasyon ek yükü ile doğal olarak paralelleştirilmesini sağlar:
Doğal Paralellik: Farklı dosyalar eşzamanlı olarak ayrıştırılabilir ve tip kontrolünden geçirilebilir.
Doğrudan Thread Erişimi: CPU-yoğun operasyonlar kontrolü devretmeden doğrudan thread'ler üzerinde çalışabilir.
Verimli Koordinasyon: Go'nun channel'ları ve senkronizasyon primitifleri eşzamanlı işleri koordine etmek için tasarlanmıştır.
Bellek Verimliliği: Goroutine'lar OS thread'lerine (MB'lar) kıyasla minimum bellek (~2KB her biri) kullanır.
Bu modelde, dosya ayrıştırma, tip kontrolü ve kod üretimi, açık kontrol devretme noktaları olmadan eşzamanlı olarak gerçekleşebilir. Kod yapısı, derleyici aşamalarının mantıksal akışını daha doğal bir şekilde takip edebilir.
Ayrıca daha fazlasını okuyun:
Aynı Kod, Farklı Yürütme Modeli
Anders Hejlsberg, birden fazla dili değerlendirdiklerini ve Go'nun kod tabanını taşımak için en kolayı olduğunu söyledi. TypeScript ekibi görünüşe göre Go kodu üreten bir araç yaratmış, bu da birçok yerde neredeyse satır satır eşdeğer bir port ile sonuçlanmış. Bu sizi kodun "aynı şeyi yaptığı" düşüncesine yönlendirebilir, ancak bu bir yanılgıdır.

Kod aynı görünebilir ancak dillerin yürütme modelleri nedeniyle çok farklı davranabilir.
JavaScript'te:
Tüm kod tek bir thread üzerinde bir event loop ile çalışır,
Uzun süren işlemlerin parçalanması veya worker thread'lere devredilmesi gerekir,
Eşzamanlı yürütme, event loop'u engellemekten kaçınmak için dikkatli bir şekilde ele alınmalıdır.
Go'da:
Kod doğal olarak birden çok goroutine (hafif thread) üzerinde çalışır,
Uzun süren işlemler diğer işleri engellemeden yürütülebilir,
Dil ve runtime, eşzamanlı yürütme için tasarlanmıştır.
Kodu JavaScript'ten Go'ya yapısını değiştirmeden taşıdığınızda, örtük olarak nasıl çalıştığını değiştirirsiniz. JavaScript'te event loop'u engelleyecek operasyonlar, Go'da minimum çabayla eşzamanlı olarak çalışabilir.
Bu bir sonuca yol açabilir: doğrudan bir port işlemi dramatik performans iyileştirmeleri sağladığında, bu orijinal implementasyonun JavaScript'in yürütme modeli için tam olarak optimize edilmediğini gösterebilir.
Gerçekten performanslı JavaScript yazmak, onun asenkron doğasını ve event loop kısıtlamalarını benimsemek anlamına gelir—bu, bir derleyici gibi karmaşık kod tabanlarında giderek zorlaşan bir şeydir.
Peki ya Worker Thread'ler?
Bazılarınız şunu düşünüyor olabilir:
"Node.js'in artık Worker Thread'leri yok mu? Go'ya geçmek yerine onları kullanamazlar mıydı?"
Bu geçerli bir soru. Node.js, v10'da deneysel bir özellik olarak worker_threads
modülünü tanıttı ve v12'de (2019 ortası) kararlı hale geldi. Worker thread'ler, Node.js'de gerçek paralellik sağlar ve CPU-yoğun görevlerin ana event loop'u engellemeden ayrı thread'lerde çalışmasına olanak tanır.
Node.js'de Worker Thread'ler Nasıl Çalışır?
Çoğu Node.js uygulamasını karakterize eden tek thread'li event loop'un aksine, worker thread'ler JavaScript'in paralel olarak yürütülmesine izin verir:
// main.js
const { Worker } = require('worker_threads')
function runWorker(workerData) {
return new Promise((resolve, reject) => {
// worker.js dosyasını worker thread olarak başlat
const worker = new Worker('./worker.js', { workerData })
// Worker'dan gelen mesajı dinle
worker.on('message', resolve)
// Worker hatasını dinle
worker.on('error', reject)
// Worker çıkışını dinle
worker.on('exit', code => {
if (code !== 0) {
reject(new Error(`Worker ${code} çıkış koduyla durdu`))
}
})
})
}
// Birden çok CPU-yoğun görevi paralel olarak çalıştır
Promise.all([
runWorker({ chunk: data.slice(0, middleIndex) }),
runWorker({ chunk: data.slice(middleIndex) })
]).then(results => {
// Sonuçları birleştir
})
// worker.js
const { parentPort, workerData } = require('worker_threads')
// CPU-yoğun işi yap
const result = processData(workerData.chunk)
// Sonucu ana thread'e geri gönder
parentPort.postMessage(result)
Her worker, kendi JavaScript yığınına sahip kendi V8 örneğinde çalışır ve gerçek paralellik sağlar. Worker'lar, ana thread ile (ve birbirleriyle) mesaj gönderip alarak iletişim kurar; bu, kopyalamayı önlemek için belirli veri türlerinin sahipliğini devretmeyi içerebilir.
Worker Thread'ler Neden TypeScript İçin Çözüm Olmadı?
Peki TypeScript ekibi neden mevcut kod tabanını worker thread'lerle yeniden düzenlemek yerine Go'ya geçmeyi seçmiş olabilir? Bunu bilmiyoruz, ancak birkaç makul neden var:
Eski Kod Tabanı Zorlukları: TypeScript derleyicisi on yılı aşkın bir süredir geliştiriliyor. Tek thread'li yürütme için tasarlanmış büyük, olgun bir kod tabanını çok thread'li bir mimariyle yeniden düzenlemek genellikle sıfırdan başlamaktan daha karmaşıktır. Worker'lar öncelikle mesajlaşma yoluyla iletişim kurar. Bir derleyiciyi bu şekilde çalışacak şekilde yeniden yapılandırmak, bileşenlerin nasıl etkileşimde bulunduğunu temelden yeniden tasarlamayı gerektirir. Video, tek thread'li Go'nun bile daha hızlı olduğunu gösterdi, bu nedenle kodun event loop özelliklerini daha iyi dikkate almak için muhtemelen hala değiştirilmesi gerekecektir.
Veri Paylaşımı Karmaşıklığı: Worker'ların belleği paylaşma yeteneği sınırlıdır. Bir derleyici, paralel işleme için düzgün bir şekilde izole edilmiş parçalara ayrılmayan karmaşık, birbirine bağlı veri yapılarını (soyut sözdizimi ağaçları, tip sistemleri vb.) manipüle eder.
Performans Ek Yükü: Worker thread'ler paralellik sağlasa da ek yük getirirler. Her worker'ın ayrı belleğe sahip kendi V8 örneği vardır ve thread'ler arasında iletilen verilerin genellikle serileştirilmesi ve deserileştirilmesi gerekir. Thread'ler veya goroutine'lar kadar hafif değillerdir.
Zaman Çizelgesi Uyuşmazlığı: TypeScript derleyicisi tasarlandığında ve uygulandığında (yaklaşık 2012), Node.js'de worker thread'ler yoktu. Başlangıçta alınan mimari kararlar tek thread'li bir modeli varsaymış olmalı, bu da daha sonra paralelleştirmeyi zorlaştırmıştır.
Mevcut Yaklaşımın Sınırları: Ekip, worker thread'lerle bile JavaScript'in kendi özel iş yükleri için temel sınırlamalar getireceği ve sonunda tekrar darboğazlara neden olacağı sonucuna varmış olabilir.
Yetenek Seti Uyumu: Karar, kısmen organizasyonel uzmanlığı ve diğer geliştirici araçlarıyla stratejik uyumu yansıtmış olabilir.
Go'nun Yaklaşımı ve Worker Thread'ler Karşılaştırması
Go'nun eşzamanlılık yaklaşımı, derleyici iş yükleri için Node.js worker thread'lerine göre çeşitli avantajlar sunar:
Hafif Goroutine'lar: Goroutine'lar, worker thread'lere (ayrı bir V8 örneği gerektiren) kıyasla son derece hafiftir (başlangıçta ~2KB bellek), bu da ince taneli paralelliği daha pratik hale getirir.
Paylaşılan Bellek Modeli: Go, goroutine'lar arasında senkronizasyon primitifleri ile doğrudan paylaşılan belleğe izin verir, bu da karmaşık, birbirine bağlı veri yapılarıyla çalışmayı kolaylaştırır.
Dil Seviyesinde Eşzamanlılık: Eşzamanlılık, Go'da dil seviyesinde goroutine'lar ve channel'lar ile yerleşiktir, bu da paralel kodu yazmayı ve anlamayı daha doğal hale getirir.
Daha Düşük İletişim Ek Yükü: Goroutine'lar arasındaki iletişim, worker thread iletişimi için gereken serileştirme/deserileştirmeden önemli ölçüde daha verimlidir.
Olgun Zamanlayıcı: Go'nun runtime'ı, binlerce goroutine'u mevcut CPU çekirdekleri arasında yönetmek için olgun ve verimli bir zamanlayıcı içerir.
Ayrıca, Go'ya geçişin sadece "çoklu threading vs. tek threading" ile ilgili olmadığını, aynı zamanda eşzamanlılığın dil ve runtime ile derinlemesine entegre olduğu birinci sınıf bir kavram olduğu bir programlama modelini benimsemekle ilgili olduğunu düşünüyorum.
Evrim Problemi
TypeScript 2012'de başladığında, ekip o zamanki bağlama dayalı makul teknoloji seçimleri yaptı:
TypeScript, JavaScript'i genişleten bir Microsoft projesiydi, bu yüzden JavaScript kullanmak mantıklıydı
Başlangıçtaki kapsam ve karmaşıklık çok daha küçüktü
Go ve Rust gibi alternatifler hala ilk günlerinde idi
Performans talepleri daha mütevazıydı
Zamanla, TypeScript nispeten basit bir JavaScript üst kümesinden gelişmiş tip özellikleri, generic'ler, koşullu tipler ve daha fazlasını içeren sofistike bir dile dönüştü. Derleyici buna göre büyüdü ancak daha basit bir problem için tasarlanmış temeller üzerine inşa edilmiş olarak kaldı.
Bu, başarılı yazılımların genellikle erken tasarımında öngörülmeyen ölçeklendirme zorluklarıyla nasıl karşılaştığının klasik bir örneğidir. TypeScript derleyicisi daha karmaşık hale geldikçe ve daha büyük kod tabanlarına uygulandıkça, JavaScript temelleri giderek kısıtlayıcı hale geldi.
Bu size de tanıdık gelmiyor mu?
Eski kod nasıl oluşur? Günden güne.
Cevapsız Sorular ve Gelecekteki Değerlendirmeler
Go geçişinden elde edilen performans iyileştirmeleri etkileyici olsa da, Microsoft'un duyurusunda tam olarak ele alınmayan birkaç önemli soru var:
Peki ya Tarayıcı Desteği?
TypeScript sadece sunucularda ve geliştirme makinelerinde çalışmaz; çeşitli playground implementasyonları ve tarayıcı içi IDE'ler aracılığıyla doğrudan tarayıcılarda da kullanılır. Go tarayıcılarda native olarak çalışmadığı için Microsoft bu kullanım durumunu nasıl ele alacak?
Birkaç potansiyel yaklaşım var:
WebAssembly (WASM): Go implementasyonunu WebAssembly'ye derlemek, tarayıcılarda çalışmasını sağlayabilir. WASM performansı önemli ölçüde iyileşmiş olsa da, native Go'ya kıyasla hala ek yük olacaktır.
Çift Implementasyon: Microsoft, tarayıcılar için bir JavaScript sürümünü, diğer her şey için Go sürümünün yanında tutabilir. Bu, özellik denkliği ve bakım için zorluklar yaratacaktır.
Tarayıcıya Özgü Alternatif: Yaygın playground senaryoları için optimize edilmiş, azaltılmış işlevselliğe sahip, tarayıcıya özgü, daha basit bir implementasyon oluşturabilirler.
Bulut Derlemesi: Tarayıcı tabanlı araçlar, derlemeyi yerel olarak yapmak yerine Go derleyicisini çalıştıran bulut uç noktalarına kod gönderebilir.
Duyuru, yaklaşımlarını netleştirmiyor ve bu, TypeScript ekosistemini etkileyecek önemli bir detay.
Özellik Denkliği ve Performans Tavizleri
Performans iyileştirmeleri elde etmenin genellikle tavizler içerdiğini belirtmek gerekir. Yaygın bir yaklaşım, özellik kapsamını veya karmaşıklığını azaltmaktır. Microsoft tam özellik denkliğini koruduklarını iddia etse de, herhangi bir ince davranışın değişip değişmediğini veya belirli uç durumların farklı ele alınıp alınmadığını dikkatle izlemeliyiz.
Diğer dil geçişlerinden alınan tarihsel örnekler, %100 aynı davranışın elde edilmesinin zor olduğunu göstermektedir. Dikkate alınması gereken bazı sorular:
Mevcut tüm TypeScript hata mesajları tam olarak aynı kalacak mı?
Tip çıkarımındaki her uç durum aynı şekilde mi davranacak?
Derleme seçenekleri ve bayrakları aynı etkilere sahip olacak mı?
Performans optimizasyonları tip sistemi uç durumlarını nasıl etkileyecek?
TypeScript ekibinin geriye dönük uyumluluk konusunda güçlü bir geçmişi var, ancak sıfırdan bir yeniden yazım, doğası gereği ince davranışsal değişiklik riskleri taşır.
Genişletilebilirlik ve Eklenti Ekosistemi
TypeScript'in derleyiciyi genişleten zengin bir eklenti ve araç ekosistemi vardır. Go'ya geçiş, bu ekosistemin geleceği hakkında sorular doğurur:
Eklenti API'si uyumlu kalacak mı?
JavaScript/TypeScript tabanlı eklentilerin Go'da yeniden yazılması gerekecek mi?
Bu, TypeScript araçları oluşturma giriş engelini nasıl etkileyecek?
Bu düşünceler, sadece derleme performansının ötesinde daha geniş TypeScript ekosistemini etkileyecektir.
Bu Neden TypeScript'in Ötesinde Önem Taşıyor?
Bu vaka çalışmasının teknoloji seçimleri için daha geniş etkileri vardır:
Teknolojinizi problem alanınızla eşleştirin. Derleyiciler gibi CPU-bound görevler, hesaplama ve native threading için tasarlanmış dillerden yararlanır. Web sunucuları gibi IO-bound görevler genellikle event-loop modelleriyle iyi çalışır.
Projeler geliştikçe temelleri yeniden gözden geçirin. Küçük bir proje için işe yarayan şey, ölçekte bir kısıtlama haline gelebilir. Temel mimari kararlarını yeniden ziyaret etmeye istekli olun. Gerekirse yeniden yazmak gibi cesur bir hamlede yanlış bir şey yoktur. Ama yapmadan önce şunu okuyun.
Manşet performans iddialarının ötesine bakın. "10 kat iyileştirme" genellikle teknoloji yığınındaki değişikliğin ötesinde birden fazla katkıda bulunan faktöre sahiptir.
Runtime modelinizi anlayın. Node.js, Go, Rust veya başka bir ortam kullanıyor olun, kodun nasıl çalıştığını anlamak performans optimizasyonu için çok önemlidir.
Geleceğe Bakış
Bir TypeScript kullanıcısı olarak Emmett ve Pongo'yu geliştirmek iyi bir haber. Derleyiciyi "bedavaya" daha hızlı çalıştırabilirsem, bu harika. Ama öte yandan, neden bu kadar gürültü yaptığımızı ve bunu geliştirme topluluğuna sunmanın bir yolu olarak tık tuzağı kullandığımızı anlamıyorum.
Yeterli bağlam vermeden 10 kata odaklanmak sadece gereksiz tartışmalara yol açtı (C# geliştiricilerinin “neden C# değil?!“ çığlıklarını kasıtlı olarak es geçtim…).
İşte bu yüzden bu kullanım durumunu genişletmek istedim, çünkü teknoloji, dil seçimleri, performans optimizasyonu ve başarılı projelerin evrimi hakkında değerli dersler sunuyor.
JavaScript'ten Go'ya geçiş, "Node.js yavaştır" onaylaması olarak alınmamalıdır. Farklı sorunların farklı araçlar gerektirdiğinin bir kabulü olarak bakmak daha iyidir. JavaScript ve Node.js, tasarlandıkları işte iyi olmaya devam ediyor: yüksek eşzamanlılık ihtiyaçları olan IO-yoğun web uygulamaları.
Tabii ki, Microsoft'un bunu tık tuzağı bir iddiada bulunmak yerine daha ayrıntılı açıklasaydı daha iyi olurdu, ama yaşadığımız dünya bu.
Ne düşünüyorsunuz? Projelerinizde teknoloji evrimine benzer zorluklarla karşılaştınız mı? Onlarla nasıl başa çıktınız? Hiç 10 kat şaşırtıcı bir iyileştirme elde ettiniz mi? Bununla nasıl başa çıktınız?
Düşüncelerinizi yorumlarda duymak isterim!
Hiç Yorum Yapılmamış. İlk Yorumu Sen Yap