Mikroişlemci bellekten bir veri okuması gerektiğinde yalnızca o veriyi değil, o verinin bulunduğu bellek bölgesini bir bütün olarak okur. Cache line denen bu büyüklük 16, 32, ve yaygın olarak 64 olabiliyor. Sonuçta, mikroişlemci 8 baytlık bir size_t okuması gerektiğinde bile 64 bayt okuyabiliyor.
Bu aslında çok yararlı bir işlem çünkü bir dizinin içindeki sayaçlarla sırayla işlem yapılacaksa bir seferde 8 sayaç belleğe okunmuş olabiliyor. Hızlı...
Ancak, eğer o biçimde belleğe okunmuş verilere birden fazla iş parçacığı tarafından yazılıyorsa, iş parçacıkları birbirlerinden habersiz olarak o bellek parçasının tazeliğini bozmuş olabiliyorlar. Bu, asıl ilgilendikleri veri aynı olmasa da sorun oluşturuyor. Örneğin, bellekte yan yana duran x ve y diye iki değişken olsa, x'i kullanan iş parçacığı için bütün değişkenler belleğe alınmış olsalar, yan taraftaki iş parçacığı y'ye yazdı diye x için okunmuş olan bellek geçersiz kabul ediliyor ve tekrar okunuyor.
Bu durum, aslında veri paylaşmayan iş parçacıklarının sanki veri paylaşıyorlarmış gibi yavaşlamalarına neden olabiliyor. Bunun etkisini görmek de şaşırtıcı derecede kolay.
Aşağıdaki program doldurmaUzunluğu değişkeninin değerine göre farklı hızda çalışıyor. Örneğin, benim ortamımda doldurmaUzunluğu 8 (veya daha az) olduğunda 64 olduğunun iki katı yavaş kalıyor.
import std.stdio;
import std.concurrency;
import std.string;
import core.thread;
/* İş parçaları kendi sayaçlarını bu değere kadar arttıracaklar. */
enum toplamArtış = 100_000_000;
/* İşçi ve dolayısıyla sayaç adedi. */
enum toplamİşçi = 2;
/* Bu uzunluğu değiştirerek 'sayaçlar' dizisindeki her Sayaç.değer
* değişkeninin bellekte diğerlerinden ne kadar uzakta olacağını
* belirleyebilirsiniz. */
enum doldurmaUzunluğu = 8;
/* size_t gibi kullanılabilen bir tür. Farkı, 'doldurma'nın uzunluğu
* değiştirilerek bellekte size_t'den daha fazla yer tutması sağlanabilir. */
union Sayaç
{
size_t değer;
ubyte[doldurmaUzunluğu] doldurma;
alias değer this;
}
/* İşçilerin arttıracakları sayaçlar. Her ne kadar paylaşılıyormuş gibi
* görünse de her işçi yalnızca kendi sayacını değiştirecek. */
shared Sayaç sayaçlar[toplamİşçi];
/* Çok basit bir işçi işlevi: Kendisine verilen sayacı arttırır. */
void arttırıcı(size_t işçiNumarası)
{
foreach (i; 0 .. toplamArtış) {
++sayaçlar[işçiNumarası];
}
}
void main()
{
Tid[toplamİşçi] işçiler;
foreach (i, ref işçi; işçiler) {
işçi = spawnLinked(&arttırıcı, i);
}
/* Programda hata olmadığını denetlemeden önce bütün işçilerin
* sonlanmalarını bekle. */
thread_joinAll();
/* Bütün sayaçlar doğru değerde olmalı. */
foreach (sayaç; sayaçlar) {
assert(sayaç == toplamArtış, format("Sayaçlar: %s", sayaçlar));
}
}
Ali
--
[ Bu gönderi, http://ddili.org/forum'dan dönüştürülmüştür. ]