Mümkün olduğunda daima paralel bir akış kullanmalı mıyım?


514

Java 8 ve lambdas ile koleksiyonları akış olarak yinelemek ve paralel bir akışı kullanmak da kolaydır. Dokümanlardan iki örnek , ikincisi parallelStream kullanıyor:

myShapesCollection.stream()
    .filter(e -> e.getColor() == Color.RED)
    .forEach(e -> System.out.println(e.getName()));

myShapesCollection.parallelStream() // <-- This one uses parallel
    .filter(e -> e.getColor() == Color.RED)
    .forEach(e -> System.out.println(e.getName()));

Siparişi umursamadığım sürece, paraleli kullanmak her zaman faydalı olur mu? İşin daha fazla çekirdeğe bölünmesinin daha hızlı olduğu düşünülebilir.

Dikkat edilmesi gereken başka noktalar var mı? Paralel akış ne zaman kullanılmalı ve ne zaman paralel olmayan akış kullanılmalıdır?

(Bu sorunun paralel akışların nasıl ve ne zaman kullanılacağı hakkında bir tartışma başlatması istenir, çünkü bunları her zaman kullanmanın iyi bir fikir olduğunu düşünüyorum.)

Yanıtlar:


735

Paralel bir akış, sıralı olana göre çok daha yüksek bir ek yüke sahiptir. İplikleri koordine etmek önemli ölçüde zaman alır. Sıralı akışları varsayılan olarak kullanır ve yalnızca paralel olanları düşünürsem

  • İşlenecek çok fazla ürünüm var (veya her bir öğenin işlenmesi zaman alıyor ve paralelleştirilebiliyor)

  • İlk başta bir performans problemim var

  • İşlemi çok iş parçacıklı bir ortamda zaten çalıştırmıyorum (örneğin: bir web kapsayıcısında, zaten paralel işlemek için birçok isteğim varsa, her isteğin içine ek bir paralellik katmanı eklemek olumlu etkilerden daha olumsuz olabilir )

Örneğin, performans yine de senkronize erişim tarafından yönlendirilecek System.out.println()ve bu işlemin paralel hale getirilmesinin hiçbir etkisi veya hatta negatif bir etkisi olacaktır.

Ayrıca, paralel akışların tüm senkronizasyon sorunlarını sihirli bir şekilde çözmediğini unutmayın. İşlemde kullanılan tahminler ve işlevler tarafından paylaşılan bir kaynak kullanılıyorsa, her şeyin iş parçacığı açısından güvenli olduğundan emin olmanız gerekir. Özellikle, yan etkiler paralel giderseniz gerçekten endişelenmeniz gereken şeylerdir.

Her durumda, ölç, tahmin etme! Paralelliğin buna değip değmediğini sadece bir ölçüm söyleyecektir.


18
İyi cevap. Eğer işlemek için büyük miktarda ürün varsa, bu sadece iş parçacığı koordinasyon sorunları artırır ekledi; yalnızca her bir öğenin işlenmesi zaman alır ve paralelleştirilebilirse paralelleştirmenin yararlı olabileceği düşünülür.
Warren Dew

16
@WarrenDew Kabul etmiyorum. Çatal / Birleştirme sistemi N öğelerini örneğin 4 parçaya böler ve bu 4 parçayı sırayla işler. 4 sonuç daha sonra azalacaktır. Masif gerçekten hızlıysa, hızlı birim işleme için bile, paralelleştirme etkili olabilir. Ama her zamanki gibi ölçmek zorundasın.
JB Nizet

onları kullanmak için Runnableçağırmak uygulamak uygulamak nesneleri bir koleksiyon var , bir paralel 8 akarsu java kullanarak değiştirmek için tamam mı? Sonra sınıf dışında iş parçacığı kodu şerit mümkün. Ama herhangi bir dezavantajı var mı? start()Threads.forEach()
ycomp

1
@JBNizet 4 parça sırayla pocess ise, o zaman süreç paralel veya sıralı olarak bilmek bir fark yoktur? Pls açıklığa kavuşturmak
Harshana

3
@Harhana, 4 parçanın her birinin elemanlarının sırayla işleneceği anlamına geliyor. Bununla birlikte, parçaların kendileri aynı anda işlenebilir. Başka bir deyişle, birden fazla CPU çekirdeğiniz varsa, her bir parça, kendi elemanlarını sırayla işlerken diğer parçalardan bağımsız olarak kendi çekirdeğinde çalışabilir. (NOT: Bilmiyorum, eğer paralel Java akışları bu şekilde çalışırsa, sadece JBNizet'in ne anlama geldiğini açıklamaya çalışıyorum.)
yarın

258

Akış API'sı, hesaplamaların nasıl yürütüleceğinden soyutlanacak şekilde yazılmasını kolaylaştıracak şekilde tasarlanmıştır ve sıralı ve paralel arasında geçişi kolaylaştırır.

Ancak, kolay olması nedeniyle, her zaman iyi bir fikir olduğu anlamına gelmez ve aslında, sadece yapabildiğiniz için her yere düşmek kötü bir fikirdir .parallel().

İlk olarak, paralelliğin daha fazla çekirdek olduğunda daha hızlı yürütme olasılığı dışında hiçbir fayda sağlamadığını unutmayın. Paralel bir yürütme her zaman sıralı olandan daha fazla iş içerecektir, çünkü problemi çözmenin yanı sıra alt görevlerin gönderilmesini ve koordine edilmesini de yapmak zorundadır. Umut, işi birden fazla işlemcide parçalayarak cevaba daha hızlı ulaşabilmenizdir; bunun gerçekleşip gerçekleşmediği, veri kümenizin boyutu, her öğe üzerinde ne kadar hesaplama yaptığınız, hesaplamanın doğası (özellikle, bir öğenin işlenmesi diğerlerinin işlenmesi ile etkileşime giriyor mu?) , kullanılabilir işlemci sayısı ve bu işlemciler için rekabet eden diğer görevlerin sayısı.

Ayrıca, paralelliğin sıklıkla, sıralı uygulamalar tarafından sıklıkla gizlenen hesaplamada genellikle belirsizliği ortaya çıkardığını unutmayın; bazen bu önemli değildir veya ilgili operasyonları kısıtlayarak hafifletilebilir (yani azaltma operatörleri vatansız ve çağrışımsal olmalıdır).

Gerçekte, bazen paralellik hesaplamanızı hızlandıracak, bazen de olmayacak, hatta bazen yavaşlatacaktır. İlk önce ardışık yürütmeyi kullanarak geliştirmek ve sonra paralellik uygulamak en iyisidir

(A) performansın artmasına gerçekten fayda olduğunu biliyorsunuz ve

(B) aslında daha yüksek performans sunacaktır.

(A) bir teknik problem değil, bir iş problemidir. Bir performans uzmanıysanız, genellikle koda bakıp (B) belirleyebileceksiniz, ancak akıllı yol ölçmektir. (Ve, (A) 'dan ikna olana kadar bile rahatsız etmeyin; kod yeterince hızlıysa, beyin döngülerinizi başka bir yere uygulamak daha iyidir.)

Paralellik için en basit performans modeli "NQ" modelidir, burada N eleman sayısıdır ve Q eleman başına hesaplamadır. Genel olarak, performans avantajı elde etmeye başlamadan önce NQ ürününün bir eşiği aşması gerekir. "1'den N'ye kadar sayıları topla" gibi düşük Q'lu bir sorun için, genellikle N = 1000 ve N = 10000 arasında bir kopuş göreceksiniz. Daha yüksek Q problemlerinde, daha düşük eşiklerde atılımlar göreceksiniz.

Ancak gerçek oldukça karmaşıktır. Uzmanlık elde edene kadar, önce sıralı işlemenin gerçekte ne zaman bir şeye mal olduğunu belirleyin ve paralelliğin yardımcı olup olmayacağını ölçün.


18
Bu yazı NQ modeli hakkında daha fazla ayrıntı veriyor: gee.cs.oswego.edu/dl/html/StreamParallelGuidance.html
Pino

4
@specializt: paralel sıralı bir akımın geçiş yapar (çoğu durumda) algoritması değiştirin. Determinizm burada bahsedilen özellikleri sizin (keyfi) operatörleri ilgili olduğunu olabilir (Akış uygulaması olduğunu bilemez) güvenmek, ama tabii olmamalıdır güveniyor. Bu cevabın o bölümünde söylemeye çalıştık. Eğer kuralları umurumda, size, (aksi paralel akışlar oldukça yararsız olduğunu) söylüyor, ama kullanırken gibi kasten izin olmayan determinizm, olasılığı da var, tıpkı bir deterministik bir sonuç olabilir findAnyyerine findFirst...
Holger

4
"İlk olarak, paralelliğin daha fazla çekirdek mevcut olduğunda daha hızlı yürütme olasılığı dışında hiçbir fayda sağlamadığını unutmayın" - veya G / Ç içeren bir eylem uyguluyorsanız (ör. myListOfURLs.stream().map((url) -> downloadPage(url))...).
Jules

6
@Pacerier Bu güzel bir teori, ama ne yazık ki naif (başlangıç ​​için otomatik paralelleştirici derleyiciler oluşturma girişimlerinin 30 yıllık geçmişine bakın). Kaçınılmaz olarak yanlış anladığımızda kullanıcıyı rahatsız etmemek için yeterince zaman tahmin etmek pratik olmadığından, yapılacak sorumlu şey, kullanıcının istediklerini söylemesine izin vermekti. Çoğu durumda, varsayılan (sıralı) doğrudur ve daha tahmin edilebilir.
Brian Goetz

2
@Jules: IO için asla paralel akışlar kullanmayın. Yalnızca CPU yoğun işlemler içindir. Paralel akışlar kullanılır ForkJoinPool.commonPool()ve engelleme görevlerinin oraya gitmesini istemezsiniz.
R2C2

68

Ben birini izledim sunumlar arasında Brian Goetz (Lambda Expressions için Java Dil Mimar & şartname kurşun) . Paraleleşmeye başlamadan önce dikkate almanız gereken aşağıdaki 4 noktayı ayrıntılı olarak açıklar:

Bölme / ayrışma maliyetleri
- Bazen bölme işi yapmaktan daha pahalıdır!
Görev dağıtımı / yönetim maliyetleri
- İşi başka bir iş parçacığına teslim etmek için gereken sürede çok iş yapabilirsiniz.
Sonuç kombinasyon maliyetleri
- Bazen kombinasyon çok sayıda verinin kopyalanmasını içerir. Örneğin, sayıları eklemek ucuzken birleştirme setleri pahalıdır.
Yerellik
- Odadaki fil. Bu herkesin özleyebileceği önemli bir noktadır. Önbellek isabetlerini dikkate almalısınız, eğer CPU önbellek isabetlerinden dolayı veri beklerse, paralelleştirme ile hiçbir şey kazanamazsınız. Bu nedenle dizi tabanlı kaynaklar, bir sonraki indeksler (geçerli dizinin yakınında) önbelleğe alındıkça ve CPU'nun önbellek kaybıyla karşılaşma olasılığının azalmasıyla en iyi paralelliği sağlar.

Paralel hızlanma şansını belirlemek için nispeten basit bir formülden bahsediyor.

NQ Modeli :

N x Q > 10000

burada,
N = veri öğesi sayısı
Q = öğe başına iş miktarı


13

JB kafasına çiviyi vurdu. Ekleyebileceğim tek şey, Java 8'in saf paralel işleme yapmaması , paraquential yapmasıdır . Evet makaleyi yazdım ve otuz yıldır F / J yapıyorum, bu yüzden sorunu anlıyorum.


10
Akışlar yinelenemez çünkü akışlar harici yerine dahili yineleme yapar. Akarsuların sebebi de bu. Akademik çalışma ile ilgili bir sorununuz varsa fonksiyonel programlama sizin için uygun olmayabilir. Fonksiyonel programlama === matematik === akademik. Ve hayır, J8-FJ kırık değil, sadece insanların çoğu ****** el kitabını okumuyor. Java dokümanları, bunun paralel bir yürütme çerçevesi olmadığını çok açık bir şekilde söylüyor. Tüm ayırıcı şeylerin nedeni budur. Evet akademik, evet nasıl kullanılacağını biliyorsanız çalışır. Evet, özel bir uygulayıcı kullanmak daha kolay olmalı
Kr0e

1
Akış, bir iterator () yöntemine sahiptir, böylece isterseniz bunları harici olarak yineleyebilirsiniz. Anladığım kadarıyla, Yinelemeyi uygulamadılar çünkü bu yineleyiciyi yalnızca bir kez kullanabilirsiniz ve kimse bunun iyi olup olmadığına karar veremezdi.
Trejkaz

14
Dürüst olmak gerekirse: tüm makaleniz muazzam, özenli bir rant gibi okuyor - ve güvenilirliğini hemen hemen reddediyor ... bunu çok daha az agresif bir alt tonla tekrar yapmayı öneriyorum, aksi takdirde pek çok insan tam olarak okumak için zahmet etmeyecek ... im just sayan
specializt

Makaleniz hakkında birkaç soru ... her şeyden önce, neden görünüşte dengeli ağaç yapılarını yönlendirilmiş asiklik grafiklerle eşitliyorsunuz? Evet, dengeli ağaçlar vardır bağlantılı listeler ve hemen hemen her nesne yönelimli veri yapısının diğer diziler daha böylece DAG'ler ama. Ayrıca, özyinelemeli ayrışmanın sadece dengeli ağaç yapıları üzerinde çalıştığını ve bu nedenle ticari olarak ilgili olmadığını söylediğinizde, bu iddiayı nasıl haklı çıkarsınız? Bana göre (kuşkusuz, konuyu derinlemesine incelemeden) , dizi tabanlı veri yapılarında da çalışması gerektiği gibi , örneğin ArrayList/ HashMap.
Jules

1
Bu konu 2013'ten beri, o zamandan beri çok şey değişti. Bu bölüm detaylı cevaplar değil yorumlar içindir.
edharned

3

Diğer yanıtlar, paralel işlemede erken optimizasyon ve genel maliyetten kaçınmak için profil oluşturmayı zaten kapsamaktadır. Bu cevap, paralel akış için ideal veri yapıları seçimini açıklar.

Kural olarak, paralellik performans kazançları üzerinden akışlarında en iyisidir ArrayList, HashMap, HashSet, ve ConcurrentHashMapörneklerini; dizileri; intaralıkları; ve longaralıklar. Bu veri yapılarının ortak yanı, hepsinin doğru ve ucuz bir şekilde istenen boyutlardaki alt kümelere ayrılabilmesidir, bu da işi paralel dişler arasında bölmeyi kolaylaştırır. Bu görevi yerine getirmek için kütüphane akımları tarafından kullanılan soyutlama tarafından döndürülen spliterator olduğu spliteratorilgili yöntem Streamve Iterable.

Bu veri yapılarının hepsinin ortak bir diğer önemli faktörü, sırayla işlendiğinde iyi ila mükemmel referans konumu sağlamalarıdır: sıralı eleman referansları bellekte birlikte saklanır. Bu referanslar tarafından atıfta bulunulan nesneler, bellekte birbirine yakın olmayabilir, bu da referans yerini değiştirir. Referans konumu, toplu işlemleri paralelleştirmek için kritik öneme sahiptir: onsuz, iş parçacıkları zamanlarının çoğunu boşta geçirir ve verilerin bellekten işlemcinin önbelleğine aktarılmasını bekler. En iyi referans lokasyonuna sahip veri yapıları ilkel dizilerdir, çünkü verinin kendisi bitişik olarak bellekte saklanır.

Kaynak: Madde # 48 Akışları Paralel Yaparken Dikkat Edin, Etkili Java 3e by Joshua Bloch


2

Sonsuz bir akışı asla sınır ile paralelleştirmeyin. İşte olanlar:

    public static void main(String[] args) {
        // let's count to 1 in parallel
        System.out.println(
            IntStream.iterate(0, i -> i + 1)
                .parallel()
                .skip(1)
                .findFirst()
                .getAsInt());
    }

Sonuç

    Exception in thread "main" java.lang.OutOfMemoryError
        at ...
        at java.base/java.util.stream.IntPipeline.findFirst(IntPipeline.java:528)
        at InfiniteTest.main(InfiniteTest.java:24)
    Caused by: java.lang.OutOfMemoryError: Java heap space
        at java.base/java.util.stream.SpinedBuffer$OfInt.newArray(SpinedBuffer.java:750)
        at ...

Eğer aynı .limit(...)

Burada açıklama: Java 8, akışta .parallel kullanmak OOM hatasına neden olur

Benzer şekilde, akış sipariş edilirse ve işlemek istediğinizden çok daha fazla öğeye sahipse paralel kullanmayın, ör.

public static void main(String[] args) {
    // let's count to 1 in parallel
    System.out.println(
            IntStream.range(1, 1000_000_000)
                    .parallel()
                    .skip(100)
                    .findFirst()
                    .getAsInt());
}

Paralel dişler çok önemli olan 0-100 yerine çok sayıda sayı aralığında çalışabileceğinden çok daha uzun sürebilir ve bu da çok uzun sürebilir.

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.