Yerel ayarlar arasında dizileri / sınıfları / kayıtları aktarma

Dec 26 2020

Tipik bir N-Vücut simülasyonunda, her dönemin sonunda, her yerel ayarın, dünyanın kendi bölümünü (yani tüm bedenleri) yerellerin geri kalanıyla paylaşması gerekir . Bunun üzerinde yerel bakış yaklaşımıyla (yani on Locifadeler kullanarak ) çalışıyorum. Anlam veremediğim bazı garip davranışlarla karşılaştım, bu yüzden işlerin daha karmaşık hale geldiği bir test programı yapmaya karar verdim. Denemeyi çoğaltmak için kod burada.

proc log(args...?n) {
    writeln("[locale = ", here.id, "] [", datetime.now(), "] => ", args);
}

const max: int = 50000;
record stuff {
    var x1: int;
    var x2: int;

    proc init() {
        this.x1 = here.id;
        this.x2 = here.id;
    }
}

class ctuff {
    var x1: int;
    var x2: int;

    proc init() {
        this.x1 = here.id;
        this.x2 = here.id;
    }
}

class wrapper {
    // The point is that total size (in bytes) of data in `r`, `c` and `a` are the same here, because the record and the class hold two ints per index.
    var r: [{1..max / 2}] stuff;
    var c: [{1..max / 2}] owned ctuff?;
    var a: [{1..max}] int;

    proc init() {
        this.a = here.id;
    }
}

proc test() {
    var wrappers: [LocaleSpace] owned wrapper?;
    coforall loc in LocaleSpace {
        on Locales[loc] {
            wrappers[loc] = new owned wrapper();
        }
    }

    // rest of the experiment further down.

}

Burada iki ilginç davranış gerçekleşir.

1. Verileri taşıma

Şimdi, wrapperdizideki her bir örnek wrapperskendi yerel ayarında yaşamalıdır. Özellikle, referanslar ( wrappers) yerel ayarı 0 yaşayacak, ancak dahili veri ( r, c, a), ilgili yerel ayarda yaşamalıdır. Bu yüzden bazılarını 1. yerelden 3. yerel ayarlara taşımaya çalışıyoruz, şöyle ki:

on Locales[3] {
    var timer: Timer;
    timer.start();
    var local_stuff = wrappers[1]!.r;
    timer.stop();
    log("get r from 1", timer.elapsed());
    log(local_stuff);
}

on Locales[3] {
    var timer: Timer;
    timer.start();
    var local_c = wrappers[1]!.c;
    timer.stop();
    log("get c from 1", timer.elapsed());
}

on Locales[3] {
    var timer: Timer;
    timer.start();
    var local_a = wrappers[1]!.a;
    timer.stop();
    log("get a from 1", timer.elapsed());
}

Şaşırtıcı bir şekilde, zamanlamalarım bunu gösteriyor

  1. Size ( const max) ne olursa olsun , diziyi gönderme ve kayıt zamanı sabittir, ki bu bana mantıklı gelmiyor. Hatta kontrol ettim chplvisve boyutu GETgerçekten artıyor ama zaman aynı kalıyor.

  2. Ders alanını gönderme zamanı zamanla artar, bu da mantıklıdır, ancak oldukça yavaştır ve burada hangi duruma güveneceğimi bilmiyorum.

2. Yerel ayarları doğrudan sorgulamak.

Sorunu aydınlatmak için .locale.idbazı değişkenleri de doğrudan sorguluyorum. İlk olarak, yerel 2'de yaşamayı beklediğimiz verileri yerel 2'den sorguluyoruz:

on Locales[2] {
    var wrappers_ref = wrappers[2]!; // This is always 1 GET from 0, okay.
    log("array",
        wrappers_ref.a.locale.id,
        wrappers_ref.a[1].locale.id
    );
    log("record",
        wrappers_ref.r.locale.id,
        wrappers_ref.r[1].locale.id,
        wrappers_ref.r[1].x1.locale.id,
    );
    log("class",
        wrappers_ref.c.locale.id,
        wrappers_ref.c[1]!.locale.id,
        wrappers_ref.c[1]!.x1.locale.id
    );
}

Ve sonuç:

[locale = 2] [2020-12-26T19:36:26.834472] => (array, 2, 2)
[locale = 2] [2020-12-26T19:36:26.894779] => (record, 2, 2, 2)
[locale = 2] [2020-12-26T19:36:27.023112] => (class, 2, 2, 2)

Beklenen. Yine de, aynı verinin yerelini 1. yerelde sorguladığımızda şunu elde ederiz:

[locale = 1] [2020-12-26T19:34:28.509624] => (array, 2, 2)
[locale = 1] [2020-12-26T19:34:28.574125] => (record, 2, 2, 1)
[locale = 1] [2020-12-26T19:34:28.700481] => (class, 2, 2, 2)

Açıkça 2. yerelde olması gerekmesine wrappers_ref.r[1].x1.locale.idrağmen yerel ayar 1'de yaşadığını ima ediyor . Tek tahminim, zaman .locale.idyürütüldüğünde verilerin (yani .xkaydın) zaten sorgulama yerel ayarına (1) taşınmış olmasıdır.

Sonuç olarak, deneyin ikinci bölümü, ilk bölüme cevap vermezken, ikincil bir soruya yol açıyor.


NOT: Tüm deney ile çalıştırılır -nl 4içinde chapel/chapel-gasnetliman işçisi görüntüsü.

Yanıtlar

4 Brad Dec 27 2020 at 23:49

İyi gözlemler, biraz ışık tutabilecek miyim bir bakayım.

İlk not olarak, gasnet Docker görüntüsüyle alınan tüm zamanlamalar, Chapel'de amaçlandığı gibi her bir yerel ayarı kendi hesaplama düğümünde çalıştırmak yerine yerel sisteminizi kullanarak birden çok düğümde yürütmeyi simüle ettiğinden, bir tuz parçasıyla alınmalıdır. Sonuç olarak, dağıtılmış bellek programları geliştirmek için kullanışlıdır, ancak performans özelliklerinin gerçek bir küme veya süper bilgisayarda çalıştırılmasından çok farklı olması muhtemeldir. Yani, hala (örneğin sizin gözlem "bu çok daha uzun zaman alıyor") kaba zamanlamaları almak için ya kullanılarak sayım iletişim için yararlı olabilir, dedi chplvisya CommDiagnostics modülü .

Zamanlamalarla ilgili gözlemlerinizle ilgili olarak, sınıf dizisi durumunun çok daha yavaş olduğunu da gözlemliyorum ve bazı davranışları açıklayabileceğime inanıyorum:

İlk olarak, herhangi bir düğümler arası iletişimin aşağıdaki gibi bir formül kullanılarak karakterize edilebileceğini anlamak önemlidir alpha + beta*length. Uzunluktan alphabağımsız olarak, iletişimi gerçekleştirmenin temel maliyetini temsil ettiğini düşünün . Bu, ağa ulaşmak için yazılım yığını aracılığıyla arama yapmanın, verileri kabloya koymanın, diğer tarafa almanın ve yazılım yığını aracılığıyla oradaki uygulamaya geri getirmenin maliyetini temsil eder. Alfanın kesin değeri, iletişim türü, yazılım yığını seçimi ve fiziksel donanım gibi faktörlere bağlı olacaktır. Bu arada, betailetişimin nasıl uygulandığına bağlı olarak, kabloya yerleştirilecek daha fazla veri olduğu için veya potansiyel olarak arabelleğe alınacak veya kopyalanacağı için daha uzun mesajların daha pahalı olduğu durumlarda, iletişimin bayt başına maliyetini temsil ettiğini düşünün .

Tecrübelerime göre, çoğu sistem konfigürasyonunda alphatipik olarak değer hakimdir beta. Bu, daha uzun veri aktarımları yapmanın ücretsiz olduğu anlamına gelmez, ancak yürütme süresindeki varyans, daha uzun ve daha kısa aktarımlar için, tek bir aktarım yerine birçok aktarım gerçekleştirmek için olduğundan çok daha küçük olma eğilimindedir. Sonuç olarak, biri transferini gerçekleştirmek arasındaki seçerken nvs unsurların n1 elemanın transferleri, neredeyse her zaman eski isteyeceksiniz.

Zamanlamalarınızı araştırmak için, zamanlanmış kod bölümlerinizi CommDiagnosticsmodüle çağrılarla aşağıdaki gibi parantezledim :

resetCommDiagnostics();
startCommDiagnostics();
...code to time here...
stopCommDiagnostics();
printCommDiagnosticsTable();

ve sizin yaptığınız gibi chplvis, kayıt dizisini veya ints dizisini yerelleştirmek için gereken iletişim sayısının ben değiştirdikçe sabit olduğunu buldum max, örneğin:

yerel almak execute_on
0 0 0
1 0 0
2 0 0
3 21 1

Bu, uygulamadan beklediğim şeyle tutarlıdır: Bir dizi değer türü için, dizi meta verilerine erişmek için sabit sayıda iletişim gerçekleştiririz ve ardından dizi öğelerini tek bir veri aktarımında ileterek genel giderler (birden fazla alphamaliyet ödemekten kaçının ).

Buna karşılık, sınıf dizisini yerelleştirmek için iletişim sayısının dizinin boyutuyla orantılı olduğunu buldum. Örneğin, varsayılan değer olan 50.000 için maxşunu gördüm:

yerel almak koymak execute_on
0 0 0 0
1 0 0 0
2 0 0 0
3 25040 25.000 1

Bu ayrımın nedeninin c, bir ownedsınıflar dizisi olgusuyla ilgili olduğuna inanıyorum, burada sadece tek bir sınıf değişkeni belirli bir ctuffnesneye "sahip olabilir" . Sonuç olarak, dizi öğelerini cbir yerel ayardan diğerine kopyalarken, kayıt ve tamsayı durumlarında olduğu gibi yalnızca ham verileri kopyalamakla kalmaz, aynı zamanda öğe başına bir sahiplik aktarımı da gerçekleştirirsiniz. Bu, esasen uzak değerin nil, değerini yerel sınıf değişkenine kopyaladıktan sonra olarak ayarlanmasını gerektirir . Şu anki uygulamamızda, bu get, uzak sınıf değerini yerel olana kopyalamak için bir uzaktan kumanda kullanılarak yapılmış gibi görünüyor , ardından putuzak değeri ayarlamak için bir uzaktan kumanda izliyor nil, bu nedenle, dizi başına bir alma ve koyma elemanımız var ve sonuçta O (n) önceki durumlarda olduğu gibi O (1) yerine iletişim. Ek çabayla, derleyicinin bu durumu optimize etmesini sağlayabiliriz, ancak sahiplik aktarımını gerçekleştirme ihtiyacı nedeniyle her zaman diğerlerinden daha pahalı olacağına inanıyorum.

O hipotezini test ownedsınıfları değişen ek yük sonuçlanan edildi ctuffolmaktan nesneleri ownediçin unmanageduygulamadan herhangi bir mülkiyet anlambilim kaldırır, hangi. Bunu yaptığımda, değer durumlarında olduğu gibi sabit sayıda iletişim görüyorum:

yerel almak execute_on
0 0 0
1 0 0
2 0 0
3 21 1

Bunun, dilin sınıf değişkenlerinin sahipliğini yönetmeye ihtiyaç duymadığında, işaretçi değerlerini tek bir transferde kolayca aktarabileceği gerçeğini temsil ettiğine inanıyorum.

Bu performans notlarının ötesinde, hangisinin kullanılacağını seçerken sınıflar ve kayıtlar arasındaki temel anlamsal farkı anlamak önemlidir. Bir sınıf nesnesi, öbek üzerinde tahsis edilir ve bir sınıf değişkeni, esasen o nesneye bir başvuru veya göstericidir. Bu nedenle, bir sınıf değişkeni bir yerel ayardan diğerine kopyalandığında, yalnızca işaretçi kopyalanır ve orijinal nesne olduğu yerde kalır (iyi veya kötü). Bunun aksine, bir kayıt değişkeni nesnenin kendisini temsil eder ve "yerinde" tahsis edilmiş olarak düşünülebilir (örneğin, bir yerel değişken için yığın üzerinde). Bir kayıt değişkeni bir yerel ayardan diğerine kopyalandığında, kopyalanan nesnenin kendisidir (yani kaydın alanlarının değerleri), bu da nesnenin kendisinin yeni bir kopyasıyla sonuçlanır. Daha fazla ayrıntı için bu SO sorusuna bakın.

İkinci gözleminize geçersek, yorumunuzun doğru olduğuna ve bunun uygulamada bir hata olabileceğine inanıyorum (kendime güvenmek için biraz daha uğraşmam gerekiyor). Spesifik olarak, wrappers_ref.r[1].x1sonucun yerel bir değişkende depolandığı ve .locale.idsorgunun orijinal alandan ziyade sonucu depolayan yerel değişkene uygulandığı konusunda olanların doğru olduğunu düşünüyorum . Bu teoriyi ref, alana a alarak ve ardından locale.idbu referansı aşağıdaki gibi yazdırarak test ettim :

ref x1loc = wrappers_ref.r[1].x1;
...wrappers_ref.c[1]!.x1.locale.id...

ve bu doğru sonucu veriyor gibi görünüyordu. Ayrıca teorilerimizin doğru olduğunu gösteren üretilen koda da baktım. Uygulamanın bu şekilde davranması gerektiğine inanmıyorum ama kendime güvenmeden önce biraz daha düşünmem gerekiyor. Chapel'in GitHub sorunları sayfasında buna karşı bir hata açmak isterseniz , orada daha fazla tartışma için çok memnun oluruz.