Windows'da Çok İş parçacıklı Java Uygulamasının Çok Düşük CPU Kullanımı


18

Sayısal optimizasyon problemlerinin bir sınıfını çözmek için bir Java uygulaması üzerinde çalışıyorum - daha büyük olması için büyük ölçekli doğrusal programlama sorunları. Tek bir sorun, paralel olarak çözülebilen daha küçük alt sorunlara bölünebilir. CPU çekirdeklerinden daha fazla alt problem olduğundan, bir ExecutorService kullanıyorum ve her bir alt problemi ExecutorService'e gönderilen Callable olarak tanımlıyorum. Bir alt problemi çözmek için yerel kütüphanenin çağrılması gerekir - bu durumda doğrusal bir programlama çözücüsü.

Sorun

Uygulamayı Unix'de ve 44 fiziksel çekirdeğe ve 256 g'a kadar belleğe sahip Windows sistemlerinde çalıştırabilirim, ancak Windows'taki hesaplama süreleri, büyük sorunlar için Linux'tan daha büyük bir sıralamadır. Windows sadece önemli ölçüde daha fazla bellek gerektirmez, aynı zamanda zaman içinde CPU kullanımı birkaç saat sonra başlangıçta% 25'ten% 5'e düşer. Windows'daki görev yöneticisinin ekran görüntüsü:

Görev Yöneticisi CPU kullanımı

Gözlemler

  • Genel sorunun büyük örnekleri için çözüm süreleri saatler ila günler arasında değişir ve 32 g'a kadar bellek tüketir (Unix'te). Bir alt problemin çözüm süreleri ms aralığındadır.
  • Çözülmesi sadece birkaç dakika süren küçük sorunlarda bu sorunla karşılaşmıyorum.
  • Linux her iki soketi de kutudan çıkarırken kullanıyor; Windows, uygulamanın her iki çekirdeği kullanması için BIOS'ta bellek serpiştirmeyi açıkça etkinleştirmemi gerektiriyor. Bunu yapmamamın zaman içinde genel CPU kullanımının bozulması üzerinde hiçbir etkisi yoktur.
  • VisualVM iş parçacıklarına baktığınızda tüm havuz iş parçacıkları çalışıyor, hiçbiri beklemede veya başka.
  • VisualVM'ye göre,% 90 CPU zamanı yerel işlev çağrısında harcanıyor (küçük bir doğrusal programı çözme)
  • Çöp Toplama bir sorun değildir çünkü uygulama çok fazla nesne oluşturmaz ve referansları kaldırmaz. Ayrıca, çoğu bellek yığın dışı tahsis gibi görünüyor. En büyük örnek için Linux'ta 4g yığın, Windows'ta 8g yığın yeterlidir.

Ne denedim

  • her türlü JVM argümanı, yüksek XMS, yüksek metaspace, UseNUMA bayrağı, diğer GC'ler.
  • farklı JVM'ler (Hotspot 8, 9, 10, 11).
  • farklı doğrusal programlama çözücülerinin farklı yerel kütüphaneleri (CLP, Xpress, Cplex, Gurobi).

Sorular

  • Yerel çağrıları yoğun şekilde kullanan çok iş parçacıklı büyük bir Java uygulamasının Linux ve Windows arasındaki performans farkını ne artırır?
  • Örneğin Windows'a yardımcı olacak uygulamada değiştirebileceğim bir şey var mı, örneğin binlerce Callables alan bir ExecutorService kullanmaktan kaçınmalı mıyım?

ForkJoinPoolBunun yerine denedin ExecutorServicemi? Sorununuz CPU'ya bağlıysa,% 25 CPU kullanımı gerçekten düşüktür.
Karol Dowbecki

1
Sorununuz CPU'yu% 100'e itmesi gereken bir şey gibi geliyor, ancak yine de% 25'in üzerindesiniz. Bazı problemler ForkJoinPooliçin manuel programlamaya göre daha verimlidir.
Karol Dowbecki

2
Hotspot sürümleri arasında geçiş yaparken, "istemci" sürümünü değil "sunucuyu" kullandığınızdan emin misiniz? Linux'ta CPU kullanımınız nedir? Ayrıca, birkaç gün Windows çalışma süresi etkileyici! Sırrın ne? : P
erickson

3
Belki kullanmayı deneyin Xperf bir oluşturmak için FlameGraph . Bu, CPU'nun ne yaptığına dair bir fikir verebilir (umarım hem kullanıcı hem de çekirdek modu), ancak Windows'ta hiç yapmadım.
Karol Dowbecki

1
@Nils, her iki çalıştırma (unix / win) yerel kütüphaneyi çağırmak için aynı arayüzü kullanıyor mu? Soruyorum, çünkü farklı görünüyor. Gibi: kazanmak jna, linux jni kullanır.
SR

Yanıtlar:


2

Windows için işlem başına iş parçacığı sayısı, işlemin adres alanı ile sınırlıdır (ayrıca bkz. Mark Russinovich - Windows'un Sınırlarını Zorlama: İşlemler ve İş Parçacıkları ). Bunun sınırlara yaklaştığında yan etkilere neden olduğunu düşünün (bağlam anahtarlarının yavaşlaması, parçalanması ...). Windows için iş yükünü bir dizi işleme bölmeye çalışacağım. Yıllar önce benzer bir sorun için bunu daha rahat yapmak için bir Java kütüphanesi uyguladım (Java 8), isterseniz bir göz atın: Harici bir süreçte görevleri ortaya çıkarmak için Kütüphane .


Bu çok ilginç görünüyor! Ben iki nedenden ötürü (henüz) bu kadar ileri gitmek biraz tereddüt: 1) soketler aracılığıyla nesneleri serileştirme ve gönderme bir performans yükü olacak; 2) her şeyi serileştirmek istiyorsam, bu bir görevle bağlantılı tüm bağımlılıkları içerir - kodu yeniden yazmak biraz iş olacaktır - yine de, yararlı bağlantılar için teşekkür ederiz.
Nils

Endişelerinizi tamamen paylaşıyorum ve kodu yeniden tasarlamak bazı çabalar olacaktır. Grafiği gezerken, işi yeni bir alt sürece ayırma zamanı geldiğinde iş parçacığı sayısı için bir eşik değeri girmeniz gerekir. Adres 2'ye), Java bellek eşlemeli dosyaya (java.nio.MappedByteBuffer) bir göz atın; veriler, örneğin grafik verileriniz gibi işlemler arasında etkili bir şekilde paylaşabilirsiniz. Godspeed :)
geri

0

Pencereler, bir süre dokunulmadan sonra belleği bir miktar önbelleğe alıyor gibi görünüyor ve bu yüzden CPU Disk hızı tarafından tıkanıyor

Process explorer ile doğrulayabilir ve ne kadar belleğin önbelleğe alındığını kontrol edebilirsiniz


Sence? Yeterli boş hafıza var. Windows neden değiştirmeye başlasın ki? Her neyse, teşekkürler.
Nils

En azından dizüstü bilgisayarımın pencereleri bazen yeterli belleğe sahip olsa bile bazen simge durumuna küçültülmüş uygulamaları değiştiriyor
Yahudi

0

Bu performans farkının işletim sisteminin iş parçacıklarını nasıl yönettiğinden kaynaklandığını düşünüyorum. JVM tüm OS farklarını gizler. Eğer gibi, bu konuda bilgi edinebilir birçok site vardır bu örneğin. Ancak bu, farkın ortadan kalktığı anlamına gelmez.

Sanırım Java 8+ JVM'de çalışıyorsunuz. Bu nedenle, akış ve fonksiyonel programlama özelliklerini kullanmaya çalışmanızı öneririm. Birçok küçük bağımsız probleminiz olduğunda ve sıralıdan paralel yürütmeye kolayca geçmek istediğinizde fonksiyonel programlama çok kullanışlıdır. İyi haber şu ki, kaç tane iş parçacığını (ExecutorService gibi) yönetmek zorunda olduğunuzu belirlemek için bir politika tanımlamanız gerekmez. Sadece örneğin ( buradan alınmıştır ):

package com.mkyong.java8;

import java.util.ArrayList;
import java.util.List;
import java.util.stream.IntStream;
import java.util.stream.Stream;

public class ParallelExample4 {

    public static void main(String[] args) {

        long count = Stream.iterate(0, n -> n + 1)
                .limit(1_000_000)
                //.parallel()   with this 23s, without this 1m 10s
                .filter(ParallelExample4::isPrime)
                .peek(x -> System.out.format("%s\t", x))
                .count();

        System.out.println("\nTotal: " + count);

    }

    public static boolean isPrime(int number) {
        if (number <= 1) return false;
        return !IntStream.rangeClosed(2, number / 2).anyMatch(i -> number % i == 0);
    }

}

Sonuç:

Normal akışlar için 1 dakika 10 saniye sürer. Paralel akışlar için 23 saniye sürer. PS i7-7700, 16G RAM, Windows 10 ile test edildi

Bu nedenle, Java'da işlev programlama, akış, lambda işlevi hakkında okumanızı ve kodunuzla (bu yeni bağlamda çalışmaya uyarlanmış) az sayıda test uygulamaya çalışmanızı öneririm.


Yazılımın diğer bölümlerinde akışları kullanıyorum, ancak bu durumda bir grafikte gezinirken görevler oluşturulur. Akarsu kullanarak bunu nasıl sararım bilmiyorum.
Nils

Grafikte gezinebilir, bir liste oluşturabilir ve ardından akışları kullanabilir misiniz?
xcesco

Paralel akışlar sadece bir ForkJoinPool için sözdizimsel şekerdir. Denedim ki (yukarıdaki @KarolDowbecki yorumuna bakın).
Nils

0

Lütfen sistem istatistiklerini gönderir misiniz? Görev yöneticisi, mevcut olan tek araçsa bazı ipuçları sağlamak için yeterince iyidir. Görevlerinizin IO'yu bekleyip beklemediğini kolayca anlayabilir - ki bu tarif ettiğiniz şeye dayanarak suçluya benziyor. Bazı bellek yönetimi sorunlarından kaynaklanıyor olabilir veya kitaplık diske vb. Geçici veriler yazabilir.

CPU kullanımının% 25'ini söylediğinizde, aynı anda yalnızca birkaç çekirdek meşgul demektir? (Tüm çekirdekler zaman zaman çalışıyor olabilir, ancak aynı anda değil.) Sistemde gerçekten kaç tane iş parçacığının (veya işlemin) oluşturulduğunu kontrol eder misiniz? Sayı her zaman çekirdek sayısından daha mı büyük?

Yeterli iş parçacığı varsa, birçoğu boşta bir şey bekliyor mu? Doğruysa, ne beklediklerini görmek için kesmeyi (veya bir hata ayıklayıcıyı eklemeyi) deneyebilirsiniz.


Bu sorunu temsil eden bir yürütme için görev yöneticisinin ekran görüntüsünü ekledim. Uygulamanın kendisi, makinede fiziksel çekirdekler olduğu kadar çok iş parçacığı oluşturur. Java bu rakama 50'den fazla konu katmaktadır. Daha önce de belirtildiği gibi VisualVM tüm iş parçacıklarının meşgul (yeşil) olduğunu söylüyor. CPU'yu Windows üzerinde sınırlandırmıyorlar. Linux üzerinde yapıyorlar.
Nils

@Nils Gerçekten tüm iş parçacığı aynı anda meşgul değil, ama aslında sadece 9 - 10 şüpheli . Tüm çekirdekler boyunca rastgele planlanırlar, dolayısıyla ortalama 9/44 =% 20 kullanım oranına sahip olursunuz. Farkı görmek için Java iş parçacıklarını ExecutorService yerine doğrudan kullanabilir misiniz? 44 iş parçacığı oluşturmak zor değildir ve her biri bir görev havuzundan / kuyruğundan Runnable / Callable alır. (VisualVM, tüm Java iş parçacıklarının meşgul olduğunu göstermesine rağmen, gerçek şu ki 44 iş parçacığının hızlı bir şekilde programlanması, böylece hepsinin VisualVM örnekleme döneminde çalışma şansı elde etmesi olabilir.)
Xiao-Feng Li

Bu aslında bir noktada yaptığım bir düşünce ve bir şey. Uygulamamda, yerel erişimin her bir iş parçacığı için yerel olduğundan emin oldum, ancak bu hiçbir fark yaratmadı.
Nils
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.