C ++ 'da iş parçacıkları arasında hızlı mesaj iletimi için bellek yönetimi


9

Birbirine eşzamansız olarak veri mesajları göndererek iletişim kuran iki iş parçacığı olduğunu varsayalım. Her iş parçacığının bir tür mesaj kuyruğu vardır.

Sorum çok düşük: Hafızayı yönetmenin en etkili yolu ne olabilir? Birkaç çözüm düşünebilirim:

  1. Gönderen, nesneyi üzerinden oluşturur new. Alıcı aramaları delete.
  2. Bellek havuzu oluşturma (belleği gönderene geri aktarmak için)
  3. Çöp toplama (ör. Boehm GC)
  4. (nesneler yeterince küçükse) yığın tahsisini tamamen önlemek için değere göre kopyalayın

1) en belirgin çözüm, bu yüzden onu bir prototip için kullanacağım. Şansı zaten yeterince iyi olması. Ancak özel sorunumdan bağımsız olarak, performansı optimize ediyorsanız hangi tekniğin en umut verici olduğunu merak ediyorum.

Özellikle iş parçacıkları arasındaki bilgi akışı hakkında ek bilgi kullanabileceğiniz için, havuzlamanın teorik olarak en iyi olmasını beklerim. Ancak, doğru olmanın en zor yolu olduğundan korkuyorum. Bir sürü ayar ... :-(

Çöp toplama işleminden sonra (çözüm 1'den sonra) eklemek oldukça kolay olmalı ve çok iyi performans göstermesini beklerdim. Yani, sanırım 1) çok verimsiz olursa en pratik çözüm budur.

Nesneler küçük ve basitse, değere göre kopyala en hızlısı olabilir. Ancak, desteklenen mesajların uygulanmasında gereksiz sınırlamalar getirmesinden korkuyorum, bu yüzden bundan kaçınmak istiyorum.

Yanıtlar:


9

Nesneler küçük ve basitse, değere göre kopyala en hızlısı olabilir. Ancak, desteklenen mesajların uygulanmasında gereksiz sınırlamalar getirmesinden korkuyorum, bu yüzden bundan kaçınmak istiyorum.

Bir üst sınır öngörüyorsanız char buf[256], örneğin nadir durumlarda yalnızca yığın ayırmalarını çağıramazsanız pratik bir alternatif:

struct Message
{
    // Stores the message data.
    char buf[256];

    // Points to 'buf' if it fits, heap otherwise.
    char* data;
};

3

Bu, kuyrukları nasıl uyguladığınıza bağlı olacaktır.

Bir dizi (yuvarlak robin stili) ile giderseniz, çözüm 4 için bir üst sınır ayarlamanız gerekir. Bağlantılı bir kuyrukla giderseniz, ayrılmış nesneler gerekir.

Sonra, kaynak havuzu sadece yeni değiştirip ile sildiğinizde kolaylıkla yapılabilir AllocMessage<T>ve freeMessage<T>. Benim önerim, potansiyel büyüklüklerin miktarını sınırlamak ve Tbeton tahsis ederken yuvarlamak olacaktır messages.

Düz çöp toplama işe yarayabilir, ancak büyük bir kısmı toplaması gerektiğinde uzun duraklamalara neden olabilir ve (sanırım) yeni / silme işleminden biraz daha kötü performans gösterecektir.


3

C ++ 'da ise, sadece akıllı işaretçilerden birini kullanın - unique_ptr , hiç kimse üzerinde bir tutamaç olana kadar alttaki nesneyi silmeyeceği için sizin için iyi çalışır. Ptr nesnesini değere göre alıcıya iletirsiniz ve asla hangi iş parçacığının onu silmesi gerektiği konusunda endişelenmenize gerek yoktur (alıcının nesneyi almadığı durumlarda).

Hala iş parçacıkları arasındaki kilitleme işlemek gerekir ama hiçbir bellek kopyalanmaz gibi performans iyi olacaktır (sadece küçük ptr nesnesinin kendisi).

Öbek üzerinde bellek ayırmak şimdiye kadarki en hızlı şey değil, bu yüzden havuzu çok daha hızlı hale getirmek için kullanılıyor. Bir sonraki bloğu bir havuzdaki önceden boyutlandırılmış bir öbekten alırsınız, bunun için sadece mevcut bir kütüphaneyi kullanın .


2
Kilitleme genellikle bellek kopyalama işleminden çok daha büyük bir sorundur. Sadece söylüyorum.
tdammers

Yazarken unique_ptr, sanırım demek istiyorsun shared_ptr. Ancak, akıllı bir işaretçi kullanmanın kaynak yönetimi için iyi olduğuna şüphe olmasa da, bir tür bellek ayırma ve yeniden yerleştirme kullandığınız gerçeğini değiştirmez. Bence bu soru daha düşük seviyeli.
5gon12eder

3

Bir nesneyi bir iş parçacığından diğerine iletirken ortaya çıkan en büyük performans, bir kilidi yakalama yüküdür. Bu birkaç mikrosaniyedir, bu da bir çiftin new/ sürenin ortalama süresinden delete(yüz nanosaniye civarında) önemli ölçüde daha fazladır . Aklı newbaşında uygulamalar, performanslarının düşmesini önlemek için neredeyse tüm maliyetlerle kilitlenmeyi önlemeye çalışır.

Bununla birlikte, nesneleri bir iş parçacığından diğerine iletirken kilit almanıza gerek kalmamasını sağlamak istiyorsunuz. Bunu başarmak için iki genel yöntem biliyorum. Her ikisi de yalnızca bir gönderen ile bir alıcı arasında tek yönlü çalışır:

  1. Bir halka tamponu kullanın. Her iki işlem de bir tamponu bu tamponun içinde kontrol eder, biri okuma işaretçisidir, diğeri yazma işaretçisidir.

    • Gönderen önce işaretçileri karşılaştırarak öğe eklemek için yer olup olmadığını kontrol eder, ardından öğeyi ekler, ardından yazma işaretçisini artırır.

    • Alıcı, işaretçileri karşılaştırarak okunacak bir öğe olup olmadığını kontrol eder, ardından öğeyi okur, ardından okuma işaretçisini artırır.

    İşaretçiler, iplikler arasında paylaşıldıklarından atomik olmalıdırlar. Ancak, her işaretçi yalnızca bir iş parçacığı tarafından değiştirilir, diğerinin işaretçiye yalnızca okuma erişimi gerekir. Arabellekteki öğeler işaretçilerin kendileri olabilir, bu da halka tamponunuzu göndereni engellemeyecek bir boyuta kolayca boyutlandırmanıza olanak tanır.

  2. Her zaman en az bir öğe içeren bağlantılı bir liste kullanın. Alıcının ilk öğeye işaretçisi, gönderenin son öğeye işaretçisi vardır. Bu işaretçi paylaşılmıyor.

    • Gönderen, nextişaretçi olarak ayarlanan bağlı liste için yeni bir düğüm oluşturur nullptr. Ardından next, yeni öğeye işaret etmek için son öğenin işaretçisini günceller . Son olarak, yeni öğeyi kendi işaretçisinde saklar.

    • Alıcı next, yeni verilerin olup olmadığını görmek için ilk öğenin işaretçisini izler . Öyleyse, eski ilk öğeyi siler, geçerli öğeyi işaret etmek için kendi işaretçisini ilerletir ve işlemeye başlar.

    Bu kurulumda, nextişaretçilerin atomik olması gerekir ve gönderen, nextişaretçisini ayarladıktan sonra ikinci son öğenin referansını kaldırmamaya dikkat edin . Avantajı elbette gönderenin asla engellememesi gerektiğidir.

Her iki yaklaşım da herhangi bir kilit tabanlı yaklaşımdan çok daha hızlıdır, ancak doğru olması için dikkatli bir şekilde uygulanması gerekir. Ve elbette, işaretçi yazma / yüklerin yerel donanım atomikliğine ihtiyaç duyarlar; senin eğer atomic<>uygulama dahili olarak bir kilit kullanır, hemen hemen mahkumdur.

Aynı şekilde, birden fazla okuyucunuz ve / veya yazarınız varsa, hemen hemen mahvoldunuz: Kilitsiz bir plan bulmaya çalışabilirsiniz, ancak en iyi şekilde uygulamak zor olacaktır. Bu durumların kilitle kullanılması çok daha kolaydır. Ancak, bir kilit yakaladıktan sonra new/ deleteperformans hakkında endişelenmeyi bırakabilirsiniz .


+1 CAS döngüleri kullanarak eşzamanlı kuyruklara alternatif olarak bu halka tampon çözeltisine bakmam gerekiyor. Kulağa çok umut verici geliyor.
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.