JavaProgramlamaKıdemli Java Geliştirici

VarHandle API, geleneksel volatile değişkenlerle mümkün olmayan yollarla bellek konumu erişimini bellek sıralama kısıtlamalarından nasıl ayırır?

Hintsage yapay zeka asistanı ile mülakatları geçin

Sorunun cevabı.

VarHandle, bellek konumu erişimcilerini bellek sıralama semantikalarından ayırarak volatile erişimini genelleştirir. Bir volatile değişken her okuma ve yazmada toplam sıralama (ardışık tutarlılık) zorunluluğu getirirken, VarHandle geliştiricilerin tam ardışık tutarlılık gereksizken daha zayıf tutarlılık modelleri seçmelerini sağlayan dört ayrı mod—plain, opaque, acquire/release ve volatile—sunmaktadır. Bu ayrım, x86 veya ARM gibi mimarilerde maliyetli StoreLoad sınırlarının geçersiz kılınmasına olanak tanıyarak, tek üretici–tek tüketici kuyrukları gibi senaryoların verimliliğini önemli ölçüde artırır. API, sun.misc.Unsafe kullanmadan bu hedefe ulaşarak, yığın dışı erişim, dizi elemanı manipülasyonu ve kayıt alanı güncellemeleri için tam olarak desteklenen bir standart mekanizma sağlar; bu, kesin ve doğrulanabilir bellek semantikalarına sahiptir.

Hayattan bir durum

Üretici bir iş parçacığının olayları yazdığı ve tüketici bir iş parçacığının bunları işlediği, her iki iş parçacığının da paylaşılan bir destek dizisi üzerinde çalıştığı bir telemetri alımı için kilitsiz bir halka tamponunu optimize ettik. İlk uygulama, görünürlüğü sağlarken her slot güncellemesinde tam bir bellek sınırı tetikleyen bir volatile dizi kullandı; bu, ARM tabanlı sunucularımızda bir darboğaz haline geldi.

İlk alternatif olarak, volatile tutarak yanlış paylaşımı önlemek için önbellek satırı dolgusu eklemeyi düşündük. Bu, doğruluğu korudu ve önbellek tutarlılığı trafiğini azalttı, ancak yine de volatile ile ilgili olan tam StoreLoad engeli maliyetini getiriyordu ve üretici ile tüketici arasında gerekmedikçe sıralama garantileri için değerli CPU döngüleri harcıyordu.

Tampon indekslerini koruyan synchronized bloklarına geri dönmeyi değerlendirdik; bu, karşılıklı dışlamayı sağlayarak güvenlik akıl yürütmesini basitleştirirdi. Ne yazık ki, bu yaklaşım üretici ve tüketici işlemlerini seri hale getirerek, alt-milisaniye işleme hedeflerimiz için hayati önem taşıyan kilitsiz gecikme özelliklerini yok etti ve ağır yük altında öncelik tersliği riskleri yarattı.

Üretici yazımları için setRelease ve tüketici okumaları için getAcquire ile VarHandle’ı benimsedik. Bu eşleşme, bir yazım ile sonrasındaki okuma arasında gerekli olan happens-before ilişkisini sağladı ve diğer değişkenlerle ilgili toplam bir sıralama zorunluluğu getirmeden mükemmel bir şekilde tek üretici–tek tüketici kuyrukumuz için gereken bellek modeline uydu.

Sonuç olarak, ARM sunucularında, doğruluğu korumak kaydıyla volatile bazına göre yaklaşık yüzde kırk daha iyi bir verimlilik elde edildi; bu, algoritmik tasarımın zaten eşzamanlılık kalıplarını sınırlandırdığı durumlarda daha zayıf tutarlılık modellerinin yeterli olduğunu göstermektedir.

Adayların sıklıkla gözden kaçırdığı noktalar

VarHandle, yığın dışı belleğe erişim için Unsafe etrafında güvenli bir sarmalayıcı mı?

VarHandle, MemorySegment aracılığıyla yığın dışı parçaları yönetebilse de, temel mimari ilerlemesi bellek sıralama modlarını açığa çıkarmasında yatmaktadır; Unsafe yalnızca opak engellerle bunu yaklaşık olarak gerçekleştirmiştir. VarHandle, bir erişimin senkronizasyon sırasına (acquire/release) katılıp katılmadığını veya yalnızca atomiklik sağladığını (opak) beyan etmeyi sağlar; bu ayrımlar, Unsafe'in ham putOrdered ile karıştırdığı veya uygun şekilde yaklaşık olarak gerçekleştirmek için manuel engel eklemesi gerektirdiği ayrıcalıklardır ve bu, JMM'ye karşı kod doğrulamasını önemli ölçüde daha güvenilir hale getirir.

setOpaque, yazımımın başka bir iş parçacığına sonunda görünür olmasını garanti ediyor mu?

Hayır. Opaque modu, atomiklik ve tutarlılık sağlar—yazım bölünemez ve aynı değişkenle ilgili diğer opak erişimlerle sıralı görünür—ancak hiçbir iş parçacığı arasında happens-before garantisi sunmaz. getOpaque ile okuma yapan bir iş parçacığı, başka bir senkronizasyon mekanizması bir önbellek temizleme zorlamadıkça, eski bir önbellek değerini görmeyi sonsuza kadar döngüde kalabilir; bu, yazıcı ile okuyucu arasındaki gerekli görünürlük kenarını oluşturan acquire/release'den farklıdır.

Ne zaman volatile modunu, setRelease/getAcquire'i tercih etmeliyim?

volatile’ı, ardışık tutarlılık gerektirdiğinizde tercih edin: tüm volatile işlemlerinin birbirleriyle toplam sıralaması açısından genel senkronizasyon sırasındaki toplam sıralanması. acquire/release'i, yalnızca belirli bir yazım ile sonrasındaki okuma (yayım güvenliği) arasındaki sıralamayı sağlamak istediğinizde kullanın; diğer tüm bellek erişimleri ile koordine olmadan. acquire/release'in, ardışık tutarlılığa dayanan algoritmalara yanlış uygulanması, bağımsız değişken güncellemelerinin farklı gözlemcilere sıralamadan çıktığına dair ince yeniden sıralama hatalarına yol açar.