wgsl.runWGSL Öğren
Türkçe English

Bölüm 07

Atomik ve Bellek Senkronizasyonu

Atomic işlemler, çok sayıda invocation aynı bellek adresine dokunduğunda gerekir. Barrier'lar ise bir workgroup içinde net bir önce/sonra sınırı koyar. Bu bölüm, GPU kodunun bağımsız lane'lerden paylaşılan state'e geçtiği noktayı öğretir.

Atomic tipler

atomic<T>, aynı değere birden fazla invocation erişebildiğinde anlam kazanır. WGSL tarafında bileşen tipi i32 veya u32 olur; atomic değerler pratikte workgroup veya storage address space'lerinde karşımıza çıkar.

atomic deklarasyonlarıworkgroup + storage
var<workgroup> local_count : atomic<u32>;

@group(0) @binding(0)
var<storage, read_write> global_count : atomic<u32>;

workgroup atomic, aynı workgroup içindeki lane'ler işbirliği yapıyorsa uygundur. storage atomic ise birçok workgroup'un host tarafından görülebilen ortak bir buffer'a birikim yaptığı durumlarda kullanılır.

Atomic fonksiyonlar

Her atomic fonksiyon tek bellek konumu için bölünmez bir güncelleme sağlar. En önemli alışkanlık şudur: hangi fonksiyon eski değeri döndürür, hangisi yalnızca yeni değeri yayımlar?

Fonksiyon Ne zaman kullanılır?
atomicLoad(p) Atomic değeri tutarlı şekilde okumak istediğinde.
atomicStore(p, v) Eski değerle birleştirmeden yeni değer yayımlamak istediğinde.
atomicAdd / atomicSub Counter, allocation index, histogram bin veya birikimli sayaç gerektiğinde.
atomicMin / atomicMax Birçok lane'den ortak minimum veya maksimum toplamak istediğinde.
atomicAnd / atomicOr / atomicXor Bit mask, flag veya kompakt durum geçişleri için.
atomicCompareExchangeWeak Compare-and-swap döngüsü kurup retry mantığını yönetmen gerektiğinde.

Klasik ilk örnek: histogram

Her invocation farklı input okuyabilir ama aynı bin'e denk gelebilir. Atomic yoksa iki lane aynı eski bin değerini okuyup artışlardan birini kaybettirebilir. atomicAdd, increment işlemini tek bölünmez güncelleme haline getirir.

histogram.wgslpaylaşılan bin'ler
@group(0) @binding(0)
var<storage, read> input : array<u32>;

@group(0) @binding(1)
var<storage, read_write> bins : array<atomic<u32>>;

@compute @workgroup_size(256)
fn histogram(@builtin(global_invocation_id) gid : vec3u) {
  let i = gid.x;
  if (i >= arrayLength(&input)) {
    return;
  }

  let bin = input[i] & 255u;
  atomicAdd(&bins[bin], 1u);
}
Studio hareketi: Reflection'da bins kaynağının writable storage olarak göründüğünü kontrol et. Sonra workgroup size'ı değiştirip metadata ile binding'lerin nasıl ayrıştığını izle.

Barrier workgroup sınırı çizer

Barrier, aynı workgroup içindeki bütün invocation'ların verdiği bir sözdür: herkes bu noktaya gelmeden kimse ilerlemez. Bu yüzden barrier çağrıları uniform şekilde erişilebilir olmalıdır. Barrier tüm GPU işlerini bitiren genel bir düğme değildir; belirli scope içindeki lane'leri senkronize eder.

Barrier Neyle ilişkilendir?
workgroupBarrier() var<workgroup> memory üzerinden aynı workgroup içinde işbirliği.
storageBarrier() Storage address space erişimlerinde storage buffer okuma/yazma sıralaması.
textureBarrier() Shader'ın senkronize bir desende texture yazıp sonra okuduğu durumlar.

Workgroup scratch memory

En yaygın barrier deseni şudur: veriyi workgroup memory'ye al, bekle, reduce veya tekrar kullan, gerekiyorsa bir sonraki aşama için tekrar bekle. Her invocation algoritmanın bir lane'ini taşır ama array workgroup içinde paylaşılır.

workgroup-sum.wgslscratch + barrier
var<workgroup> scratch : array<u32, 256>;

@group(0) @binding(0)
var<storage, read> input : array<u32>;

@group(0) @binding(1)
var<storage, read_write> partials : array<u32>;

@compute @workgroup_size(256)
fn sum_block(
  @builtin(local_invocation_id) lid : vec3u,
  @builtin(global_invocation_id) gid : vec3u,
  @builtin(workgroup_id) wid : vec3u
) {
  let local_index = lid.x;
  scratch[local_index] = select(0u, input[gid.x], gid.x < arrayLength(&input));
  workgroupBarrier();

  var stride = 128u;
  loop {
    if (local_index < stride) {
      scratch[local_index] = scratch[local_index] + scratch[local_index + stride];
    }
    workgroupBarrier();

    if (stride == 1u) {
      break;
    }
    stride = stride / 2u;
  }

  if (local_index == 0u) {
    partials[wid.x] = scratch[0];
  }
}

Memory layout hala önemlidir

Atomic ve barrier senkronizasyon problemini çözer; layout kurallarını ortadan kaldırmaz. Buffer içinde struct varsa host ve shader aynı alignment/stride hikayesinde buluşmalıdır.

WGSL şekli Pratik layout alışkanlığı
f32, i32, u32 4 byte scalar değer gibi düşün.
vec2f 8 byte alignment bekle.
vec3f ve vec4f 16 byte alignment etrafında plan yap; host packing hataları çoğu zaman vec3f civarında çıkar.
mat4x4f 16 byte aligned column'lar olarak düşün.

Çalışma döngüsü

  1. Paylaşılan adresi adlandır: workgroup memory mi, storage memory mi, ikisi birden mi?
  2. Bütün yazmaları bul; iki invocation aynı adrese denk gelebilir mi karar ver.
  3. Adres çakışabiliyorsa update'e uyan en küçük atomic operasyonu seç.
  4. Sonraki kod workgroup memory okuyorsa, bütün lane'lerin ulaşacağı barrier noktasını koy.
  5. Studio'da legality için diagnostics, API sözleşmesi için reflection, sonuç için readback kullan.