SORU
1 Kasım 2011, Salı


Performansını artırmak için yollar tutarlılık

Aşağıdaki örnekte, bir iş parçacığı "" olan bir ByteBuffer tüketici alıyor. aracılığıyla mesaj gönderiyor En iyi performansı tutarlı değil ama çok iyi.

public class Main {
    public static void main(String... args) throws IOException {
        for (int i = 0; i < 10; i  )
            doTest();
    }

    public static void doTest() {
        final ByteBuffer writeBuffer = ByteBuffer.allocateDirect(64 * 1024);
        final ByteBuffer readBuffer = writeBuffer.slice();
        final AtomicInteger readCount = new PaddedAtomicInteger();
        final AtomicInteger writeCount = new PaddedAtomicInteger();

        for(int i=0;i<3;i  )
            performTiming(writeBuffer, readBuffer, readCount, writeCount);
        System.out.println();
    }

    private static void performTiming(ByteBuffer writeBuffer, final ByteBuffer readBuffer, final AtomicInteger readCount, final AtomicInteger writeCount) {
        writeBuffer.clear();
        readBuffer.clear();
        readCount.set(0);
        writeCount.set(0);

        Thread t = new Thread(new Runnable() {
            @Override
            public void run() {
                byte[] bytes = new byte[128];
                while (!Thread.interrupted()) {
                    int rc = readCount.get(), toRead;
                    while ((toRead = writeCount.get() - rc) <= 0) ;
                    for (int i = 0; i < toRead; i  ) {
                        byte len = readBuffer.get();
                        if (len == -1) {
                            // rewind.
                            readBuffer.clear();
//                            rc  ;
                        } else {
                            int num = readBuffer.getInt();
                            if (num != rc)
                                throw new AssertionError("Expected "   rc   " but got "   num) ;
                            rc  ;
                            readBuffer.get(bytes, 0, len - 4);
                        }
                    }
                    readCount.lazySet(rc);
                }
            }
        });
        t.setDaemon(true);
        t.start();
        Thread.yield();
        long start = System.nanoTime();
        int runs = 30 * 1000 * 1000;
        int len = 32;
        byte[] bytes = new byte[len - 4];
        int wc = writeCount.get();
        for (int i = 0; i < runs; i  ) {
            if (writeBuffer.remaining() < len   1) {
                // reader has to catch up.
                while (wc - readCount.get() > 0) ;
                // rewind.
                writeBuffer.put((byte) -1);
                writeBuffer.clear();
            }
            writeBuffer.put((byte) len);
            writeBuffer.putInt(i);
            writeBuffer.put(bytes);
            writeCount.lazySet(  wc);
        }
        // reader has to catch up.
        while (wc - readCount.get() > 0) ;
        t.interrupt();
        t.stop();
        long time = System.nanoTime() - start;
        System.out.printf("Message rate was %.1f M/s offsets %d %d %d%n", runs * 1e3 / time
                , addressOf(readBuffer) - addressOf(writeBuffer)
                , addressOf(readCount) - addressOf(writeBuffer)
                , addressOf(writeCount) - addressOf(writeBuffer)
        );
    }

    // assumes -XX: UseCompressedOops.
    public static long addressOf(Object... o) {
        long offset = UNSAFE.arrayBaseOffset(o.getClass());
        return UNSAFE.getInt(o, offset) * 8L;
    }

    public static final Unsafe UNSAFE = getUnsafe();
    public static Unsafe getUnsafe() {
        try {
            Field field = Unsafe.class.getDeclaredField("theUnsafe");
            field.setAccessible(true);
            return (Unsafe) field.get(null);
        } catch (Exception e) {
            throw new AssertionError(e);
        }
    }

    private static class PaddedAtomicInteger extends AtomicInteger {
        public long p2, p3, p4, p5, p6, p7;

        public long sum() {
//            return 0;
            return p2   p3   p4   p5   p6   p7;
        }
    }
}

parmak izleri aynı veri bloğu için zamanlama. Sonunda sayılar dışında önbelleğinde aynı her zaman koydu olduklarını gösteren nesnelerin göreli adresleri. 10 uzun testler belirli bir kombinasyonu sürekli olarak aynı performansı verir gösterir.

Message rate was 63.2 M/s offsets 136 200 264
Message rate was 80.4 M/s offsets 136 200 264
Message rate was 80.0 M/s offsets 136 200 264

Message rate was 81.9 M/s offsets 136 200 264
Message rate was 82.2 M/s offsets 136 200 264
Message rate was 82.5 M/s offsets 136 200 264

Message rate was 79.1 M/s offsets 136 200 264
Message rate was 82.4 M/s offsets 136 200 264
Message rate was 82.4 M/s offsets 136 200 264

Message rate was 34.7 M/s offsets 136 200 264
Message rate was 39.1 M/s offsets 136 200 264
Message rate was 39.0 M/s offsets 136 200 264

Tamponlar ve sayaç her biri üç kez test edilir ve bu arabellekleri benzer sonuçları vermek gibi görünüyor. Bu arabellekleri bellek koydu bir şekilde hakkında bir şey olduğuna inanıyorum bu YÜZDEN görüşmüyorum.

Daha yüksek performans daha sık verebilecek bir şey var mı? Önbellek bir çarpışma gibi görünüyor ama bu gerçek olabilir göremiyorum.

BTW: M/s saniyede milyonlarca mesajı ve kimseye ihtiyaç olasılığı daha fazladır, ama sürekli hızlı yapmak için nasıl anlamak iyi olurdu.


EDİT: bekle ile senkronize Kullanarak ve haberdar sonuç çok daha tutarlı hale geliyor. Ama daha hızlı değil.

Message rate was 6.9 M/s
Message rate was 7.8 M/s
Message rate was 7.9 M/s
Message rate was 6.7 M/s
Message rate was 7.5 M/s
Message rate was 7.7 M/s
Message rate was 7.3 M/s
Message rate was 7.9 M/s
Message rate was 6.4 M/s
Message rate was 7.8 M/s

EDİT: görev kümesini kullanarak, iki iş parçacığı aynı çekirdek değiştirmek için kilit performans tutarlı yapabilirim.

Message rate was 35.1 M/s offsets 136 200 216
Message rate was 34.0 M/s offsets 136 200 216
Message rate was 35.4 M/s offsets 136 200 216

Message rate was 35.6 M/s offsets 136 200 216
Message rate was 37.0 M/s offsets 136 200 216
Message rate was 37.2 M/s offsets 136 200 216

Message rate was 37.1 M/s offsets 136 200 216
Message rate was 35.0 M/s offsets 136 200 216
Message rate was 37.1 M/s offsets 136 200 216

If I use any two logical threads on different cores, I get the inconsistent behaviour

Message rate was 60.2 M/s offsets 136 200 216
Message rate was 68.7 M/s offsets 136 200 216
Message rate was 55.3 M/s offsets 136 200 216

Message rate was 39.2 M/s offsets 136 200 216
Message rate was 39.1 M/s offsets 136 200 216
Message rate was 37.5 M/s offsets 136 200 216

Message rate was 75.3 M/s offsets 136 200 216
Message rate was 73.8 M/s offsets 136 200 216
Message rate was 66.8 M/s offsets 136 200 216

EDİT: bir GC tetikleyen davranışları değişecek gibi görünüyor. Bu el ile tetik GC bir yarım ile aynı arabellek sayaçları tekrarlanan test göster.

faster after GC

Message rate was 27.4 M/s offsets 136 200 216
Message rate was 27.8 M/s offsets 136 200 216
Message rate was 29.6 M/s offsets 136 200 216
Message rate was 27.7 M/s offsets 136 200 216
Message rate was 29.6 M/s offsets 136 200 216
[GC 14312K->1518K(244544K), 0.0003050 secs]
[Full GC 1518K->1328K(244544K), 0.0068270 secs]
Message rate was 34.7 M/s offsets 64 128 144
Message rate was 54.5 M/s offsets 64 128 144
Message rate was 54.1 M/s offsets 64 128 144
Message rate was 51.9 M/s offsets 64 128 144
Message rate was 57.2 M/s offsets 64 128 144

and slower

Message rate was 61.1 M/s offsets 136 200 216
Message rate was 61.8 M/s offsets 136 200 216
Message rate was 60.5 M/s offsets 136 200 216
Message rate was 61.1 M/s offsets 136 200 216
[GC 35740K->1440K(244544K), 0.0018170 secs]
[Full GC 1440K->1302K(244544K), 0.0071290 secs]
Message rate was 53.9 M/s offsets 64 128 144
Message rate was 54.3 M/s offsets 64 128 144
Message rate was 50.8 M/s offsets 64 128 144
Message rate was 56.6 M/s offsets 64 128 144
Message rate was 56.0 M/s offsets 64 128 144
Message rate was 53.6 M/s offsets 64 128 144

EDİT: BegemoT kütüphane çekirdek kimliği 3.8 GHz i7 (Ev PC) aşağıdaki gibi elde kullanılan . Kullanarak @

Not: uzaklıklar 8 kat yanlıştır. Yığın boyutu küçük olduğu için, JVM büyük bir yığın (yalnızca 32 GB veya daha az) ile olduğu gibi 8 ile çarpın referans yok.

writer.currentCore() -> Core[#0]
reader.currentCore() -> Core[#5]
Message rate was 54.4 M/s offsets 3392 3904 4416
writer.currentCore() -> Core[#0]
reader.currentCore() -> Core[#6]
Message rate was 54.2 M/s offsets 3392 3904 4416
writer.currentCore() -> Core[#0]
reader.currentCore() -> Core[#5]
Message rate was 60.7 M/s offsets 3392 3904 4416

writer.currentCore() -> Core[#0]
reader.currentCore() -> Core[#5]
Message rate was 25.5 M/s offsets 1088 1600 2112
writer.currentCore() -> Core[#0]
reader.currentCore() -> Core[#5]
Message rate was 25.9 M/s offsets 1088 1600 2112
writer.currentCore() -> Core[#0]
reader.currentCore() -> Core[#5]
Message rate was 26.0 M/s offsets 1088 1600 2112

writer.currentCore() -> Core[#0]
reader.currentCore() -> Core[#5]
Message rate was 61.0 M/s offsets 1088 1600 2112
writer.currentCore() -> Core[#0]
reader.currentCore() -> Core[#5]
Message rate was 61.8 M/s offsets 1088 1600 2112
writer.currentCore() -> Core[#0]
reader.currentCore() -> Core[#5]
Message rate was 60.7 M/s offsets 1088 1600 2112

Aynı mantıksal iş parçacığı kullanılan görebilirsiniz, ama performans, bir çalışma içinde çalışan arasında, ama (bir çalışma içinde aynı nesneleri kullanılır) değişir


Sorunu buldum. Bellek düzeni tehdit eden bir durum ama bunu çözmek için basit bir şekilde görebiliyordum. ByteBuffer bir nesne yaratmak çok dolgu ekleyebilirsiniz. böylece genişletilmiş olmam atın.

    final ByteBuffer writeBuffer = ByteBuffer.allocateDirect(64 * 1024);
    final ByteBuffer readBuffer = writeBuffer.slice();
    new PaddedAtomicInteger();
    final AtomicInteger readCount = new PaddedAtomicInteger();
    final AtomicInteger writeCount = new PaddedAtomicInteger();

Bu ekstra dolgu olmadan (kullanılmayan nesne), sonuçları 3.8 GHz i7 bu gibi görünüyor.

Message rate was 38.5 M/s offsets 3392 3904 4416
Message rate was 54.7 M/s offsets 3392 3904 4416
Message rate was 59.4 M/s offsets 3392 3904 4416

Message rate was 54.3 M/s offsets 1088 1600 2112
Message rate was 56.3 M/s offsets 1088 1600 2112
Message rate was 56.6 M/s offsets 1088 1600 2112

Message rate was 28.0 M/s offsets 1088 1600 2112
Message rate was 28.1 M/s offsets 1088 1600 2112
Message rate was 28.0 M/s offsets 1088 1600 2112

Message rate was 17.4 M/s offsets 1088 1600 2112
Message rate was 17.4 M/s offsets 1088 1600 2112
Message rate was 17.4 M/s offsets 1088 1600 2112

Message rate was 54.5 M/s offsets 1088 1600 2112
Message rate was 54.2 M/s offsets 1088 1600 2112
Message rate was 55.1 M/s offsets 1088 1600 2112

Message rate was 25.5 M/s offsets 1088 1600 2112
Message rate was 25.6 M/s offsets 1088 1600 2112
Message rate was 25.6 M/s offsets 1088 1600 2112

Message rate was 56.6 M/s offsets 1088 1600 2112
Message rate was 54.7 M/s offsets 1088 1600 2112
Message rate was 54.4 M/s offsets 1088 1600 2112

Message rate was 57.0 M/s offsets 1088 1600 2112
Message rate was 55.9 M/s offsets 1088 1600 2112
Message rate was 56.3 M/s offsets 1088 1600 2112

Message rate was 51.4 M/s offsets 1088 1600 2112
Message rate was 56.6 M/s offsets 1088 1600 2112
Message rate was 56.1 M/s offsets 1088 1600 2112

Message rate was 46.4 M/s offsets 1088 1600 2112
Message rate was 46.4 M/s offsets 1088 1600 2112
Message rate was 47.4 M/s offsets 1088 1600 2112

atılan yastıklı nesne ile.

Message rate was 54.3 M/s offsets 3392 4416 4928
Message rate was 53.1 M/s offsets 3392 4416 4928
Message rate was 59.2 M/s offsets 3392 4416 4928

Message rate was 58.8 M/s offsets 1088 2112 2624
Message rate was 58.9 M/s offsets 1088 2112 2624
Message rate was 59.3 M/s offsets 1088 2112 2624

Message rate was 59.4 M/s offsets 1088 2112 2624
Message rate was 59.0 M/s offsets 1088 2112 2624
Message rate was 59.8 M/s offsets 1088 2112 2624

Message rate was 59.8 M/s offsets 1088 2112 2624
Message rate was 59.8 M/s offsets 1088 2112 2624
Message rate was 59.2 M/s offsets 1088 2112 2624

Message rate was 60.5 M/s offsets 1088 2112 2624
Message rate was 60.5 M/s offsets 1088 2112 2624
Message rate was 60.5 M/s offsets 1088 2112 2624

Message rate was 60.5 M/s offsets 1088 2112 2624
Message rate was 60.9 M/s offsets 1088 2112 2624
Message rate was 60.6 M/s offsets 1088 2112 2624

Message rate was 59.6 M/s offsets 1088 2112 2624
Message rate was 60.3 M/s offsets 1088 2112 2624
Message rate was 60.5 M/s offsets 1088 2112 2624

Message rate was 60.9 M/s offsets 1088 2112 2624
Message rate was 60.5 M/s offsets 1088 2112 2624
Message rate was 60.5 M/s offsets 1088 2112 2624

Message rate was 60.7 M/s offsets 1088 2112 2624
Message rate was 61.6 M/s offsets 1088 2112 2624
Message rate was 60.8 M/s offsets 1088 2112 2624

Message rate was 60.3 M/s offsets 1088 2112 2624
Message rate was 60.7 M/s offsets 1088 2112 2624
Message rate was 58.3 M/s offsets 1088 2112 2624

Ne yazık ki her zaman bir GC sonra, nesneleri en iyi şekilde düzenleneceğini değil o riski vardır. Bu sorunu çözmek için tek yol orijinal sınıf için doldurma eklemek için olabilir. :(

CEVAP
4 Kasım 2011, Cuma


İşlemci önbelleğe alanında bir uzman değilim ama sorun aslında bir önbellek sorunu veya başka bir bellek sorunu olduğunu düşünüyorum. Eski nesneler temizlik olmadan tamponlar Ve sayaçların tekrarlanan ayırma belli aralıklarla tutarsız performans yol açabilir çok kötü bir önbellek düzeni, almak için neden olabilir.

Kullanarak kodunuzu ve bir kaç modifiye ben yapabilir performansını tutarlı (benim test makinesidir Intel Core2 Quad CPU Q6600 2.4 GHz w/ Win7x64 - pek aynı sayılmaz ama umarım yakın kadar ilgili sonuçları). Kabaca aynı etkiye sahip iki farklı şekilde yaptım.

İlk olarak, sadece bir kez ve her test geçmek için daha sonra yeniden oluşturulur; böylece doTest yöntem dışında tamponlar ve sayaçları oluşturma taşıyın. Şimdi bir tahsisat olsun, güzel önbelleğinde oturur ve performans tutarlıdır.

Aynı yeniden elde etmek için başka bir yol ama "farklı" arabellekleri/sayaçları performTiming döngü sonra bir gc eklemek için:

for ( int i = 0; i < 3; i   )
    performTiming ( writeBuffer, readBuffer, readCount, writeCount );
System.out.println ();
System.gc ();

İşte sonuç ortada daha fazla veya daha az aynı - gc sağlar arabellekleri/sayaçlar olarak iadesi ve sonraki ayırma biter yeniden aynı bellek (en azından benim test sistemi) ve sonunda önbellek ile tutarlı performans (ben de ilave baskı gerçek adreslerini doğrulamak için yeniden aynı yerlerde). Benim tahminim Temizle yeniden lider olmadan eninde sonunda önbellek içine sığmayan bir tampon tahsis ile bitiyor ve performansını takas gibi çekiyor. Sanıyorum ki bazı garip şeyler ile sipariş tahsisi (gibi yapabilirsiniz performansını kötü benim makine tarafından hareket sayaç ayırma önünde tamponlar) veya oluşturmak biraz ölü boşluk her çalıştırmak için "Temizle" önbelleği istemediysen ortadan kaldırmak için arabellekleri bir önceki döngü.

Son olarak, dediğim gibi, işlemci ve Tampon Bellek ve düzen eğlenceli açıklamalar yanıltıcı ya da yanlış - üzgünüm eğer benim uzmanlık alanım değil.

Bunu Paylaş:
  • Google+
  • E-Posta
Etiketler:

YORUMLAR

SPONSOR VİDEO

Rastgele Yazarlar

  • Feel The Electricity!

    Feel The Ele

    20 ŞUBAT 2010
  • Erica Griffin

    Erica Griffi

    8 HAZİRAN 2009
  • lane182videos

    lane182video

    6 EKİM 2011