Büyük dizileri doldurmadan büyük bir sorun üretmenin profesyonel yolu: C ++, dizinin bir kısmından boş bellek


20

Bir fizik simülasyonu geliştiriyorum ve programlamaya yeni başladığım için, büyük programlar (çoğunlukla bellek sorunları) üretirken sorunla karşılaşıyorum. Dinamik bellek ayırma ve silme (yeni / silme, vb.) Hakkında bilgim var, ancak programı nasıl yapılandırdığım konusunda daha iyi bir yaklaşıma ihtiyacım var.

Diyelim ki çok büyük bir örnekleme oranıyla birkaç gün süren bir deneyi simüle ediyorum. Bir milyar örneği simüle etmem ve onların üzerinden geçmem gerekiyor.

Süper basitleştirilmiş bir versiyon olarak, bir programın V [i] voltajlarını aldığını ve bunları beşte topladığını söyleyeceğiz:

yani NewV [0] = V [0] + V [1] + V [2] + V [3] + V [4]

sonra NewV [1] = V [1] + V [2] + V [3] + V [4] + V [5]

sonra NewV [2] = V [2] + V [3] + V [4] + V [5] + V [6] ... ve bu bir milyar örnek için devam ediyor.

Sonunda, V [0], V [1], ..., V [1000000000] olacaktı, bunun yerine bir sonraki adım için saklamam gereken tek şey son 5 V [i] s.

Belleğin tekrar kullanılabilmesi için dizinin bir bölümünü nasıl silebilirim / yeniden konumlandırabilirim (örneğin artık ihtiyaç duyulmadığı örneğin ilk bölümünden sonra V [0] deyin)? Böyle bir programın nasıl yapılandırılacağına alternatifler var mı?

Malloc / free hakkında duydum, ancak C ++ 'da kullanılmamaları gerektiğini ve daha iyi alternatifler olduğunu duydum.

Çok teşekkürler!

TLDR; dizilerin bölümleriyle (tek tek öğeler) ne yapmam gerekiyor ki artık çok fazla bellek kaplıyor?


2
Bir dizinin bir kısmını yeniden konumlandıramazsınız. Başka bir yerde daha küçük bir diziye yeniden atayabilirsiniz, ancak bu pahalı olabilir. Bunun yerine, bağlantılı bir liste gibi farklı bir veri yapısı kullanabilirsiniz. Belki de adımları Vyeni bir dizi yerine saklayabilirsiniz . Temel olarak, ancak, sorunun ya algoritmalarınızda ya da veri yapılarınızda olduğunu düşünüyorum ve herhangi bir ayrıntıya sahip olmadığımızdan, bunu nasıl verimli bir şekilde yapacağınızı bilmek zor.
Vincent Savard

4
Yan not: keyfi uzunluktaki SMA'lar bu nüksetme ilişkisi ile özellikle hızlı bir şekilde hesaplanabilir: NewV [n] = NewV [n-1] - V [n-1] + V [n + 4] (notasyonunuz). Ancak bunların özellikle yararlı filtreler olmadığını unutmayın. Frekans tepkileri bir samimidir, ki bu neredeyse hiçbir zaman istediğiniz şey değildir (gerçekten yüksek yan loblar).
Steve Cox

2
SMA = merak eden herkes için basit hareketli ortalama.
Charles

3
@SteveCox, yazdığı gibi bir FIR filtresine sahip. Nüksünüz eşdeğer IIR formudur. Her iki durumda da, son N okumalarının dairesel bir tamponunu elde edersiniz.
John R. Strohm

@ JohnR.Strohm dürtü yanıtı aynı ve sonlu
Steve Cox

Yanıtlar:


58

"Beşleme ile yumuşatma" olarak tanımladığınız, sonlu bir dürtü yanıtı (FIR) dijital filtredir. Bu filtreler dairesel tamponlarla uygulanır. Yalnızca son N değerlerini tutarsınız, arabellek içinde en eski değerin nerede olduğunu söyleyen bir dizin tutarsınız, her adımda geçerli en eski değerin üzerine en yeni değerle yazar ve her seferinde dairesel olarak dizine basarsınız.

Sıkışacağınız toplanan verilerinizi diskte tutuyorsunuz.

Ortamınıza bağlı olarak, burası deneyimli yardım almaktan daha iyi olduğunuz yerlerden biri olabilir. Bir üniversitede, Bilgisayar Bilimleri Bölümü'ndeki bülten tahtasına not vererek, verilerinizi zorlamanıza yardımcı olmak için birkaç saatlik çalışma için öğrenci ücretleri (hatta öğrenci danışmanlık oranları) sunarsınız. Ya da Lisans Araştırma Fırsatı puanları sunabilirsiniz. Ya da başka birşey.


6
Dairesel bir tampon gerçekten aradığım şey gibi görünüyor! Şimdi boost C ++ kitaplıklarını yükledim ve boost / dairesel_buffer.hpp'yi dahil ettim ve beklendiği gibi çalışıyor. Teşekkürler, @John
Drummermean

2
yazılımda doğrudan çok kısa FIR filtreleri uygulanır ve SMA'lar neredeyse hiç olmaz.
Steve Cox

@SteveCox: Kullandığınız pencere kenarları formülü, tamsayı ve sabit nokta filtreleri için oldukça etkilidir, ancak işlemlerin değişmez olduğu kayan nokta için yanlıştır.
Ben Voigt

@BenVoigt diğer yorumuma cevap vermek istediğinizi düşünüyorum, ama evet, bu form çok zor olabilen bir nicemleme etrafında bir sınır döngüsü getiriyor. Neyse ki, bu belirli sınır döngüsü istikrarlı olur.
Steve Cox

Bu kullanım için dairesel bir arabellek için gerçekten desteğe ihtiyacınız yoktur uu Gerekenden çok daha fazla bellek kullanacaksınız.
GameDeveloper

13

Her problem, ilave bir dolaylama seviyesi eklenerek çözülebilir. Öyleyse yap.

C ++ 'da bir dizinin bir bölümünü silemezsiniz. Ancak, yalnızca saklamak istediğiniz verileri tutan yeni bir dizi oluşturabilir, ardından eskisini silebilirsiniz. Böylece istemediğiniz öğeleri önden "kaldırmanıza" izin veren bir veri yapısı oluşturabilirsiniz. Aslında yapacağı şey, yeni bir dizi oluşturmak ve kaldırılmamış öğeleri yenisine kopyalamak, sonra eski olanı silmek.

Ya da sadece std::dequebunu etkili bir şekilde yapabilen kullanabilirsiniz . dequeveya "çift uçlu kuyruk", öğeleri bir uçtan diğerine silerken, diğer uçtan öğeler eklediğiniz durumlar için tasarlanmış bir veri yapısıdır.


30
Her problem ilave bir dolaylılık seviyesi eklenerek çözülebilir ... pek çok dolaylı aktarma seviyesi hariç.
YSC

17
@YSC: ve yazım :)
Monica ile Hafiflik Yarışları

1
Bu özel sorun std::dequeiçin gitmek için yol
davidbak

7
@davidbak - Ne? Sürekli bellek ayırmaya ve serbest bırakmaya gerek yoktur. Başlatma zamanında bir kez atanan sabit boyutlu dairesel bir tampon bu soruna çok daha uygundur.
David Hammen

2
@DavidHammen: Belki, ama 1) Standart kütüphanenin araç setinde "sabit boyutlu dairesel tampon" yoktur. 2) Gerçekten böyle bir optimizasyona ihtiyacınız varsa, yeniden tahsisleri en aza indirmek için bazı ayırıcı şeyler yapabilirsiniz deque. Yani, tahsisleri istendiği gibi depolamak ve yeniden kullanmak. Bu yüzden dequesoruna mükemmel bir çözüm gibi görünüyor.
Nicol Bolas

4

Aldığınız FIR ve SMA cevapları sizin durumunuzda iyidir, ancak daha genel bir yaklaşımı ileriye götürme fırsatını değerlendirmek istiyorum.

Burada bir veri akışı var : programınızı tüm verileri bir kerede belleğe yüklemeyi gerektiren 3 büyük adımda (veri alma, hesaplama, çıktı sonucu) yapılandırmak yerine, bunu bir boru hattı olarak yapılandırabilirsiniz .

Bir boru hattı bir akışla başlar, dönüştürür ve bir lavaboya iter.

Sizin durumunuzda, boru hattı şöyle görünür:

  1. Diskteki öğeleri okuyun, öğeleri teker teker yayınlayın
  2. Öğeleri birer birer alın, alınan her öğe için alınan son 5 yayını (dairesel arabelleğinizin geldiği yerde) yayar
  3. Her grup için her seferinde 5 ürün alın, sonucu hesaplayın
  4. Sonucu alın, diske yazın

C ++, akışlar yerine yineleyicileri kullanma eğilimindedir, ancak dürüst olmak gerekirse akışların modellenmesi daha kolaydır ( akışlara benzer olan aralıklara yönelik bir teklif vardır ):

template <typename T>
class Stream {
public:
    virtual boost::optional<T> next() = 0;
    virtual ~Stream() {}
};

class ReaderStream: public Stream<Item> {
public:
    boost::optional<Item> next() override final;

private:
    std::ifstream file;
};

class WindowStream: public Stream<Window> {
public:
    boost::optional<Window> next() override final;

private:
    Window window;
    Stream<Item>& items;
};

class ResultStream: public Stream<Result> {
public:
    boost::optional<Result> next() override final;

private:
    Stream<Window>& windows;
};

Ve sonra, boru hattı şöyle görünür:

ReaderStream itemStream("input.txt");
WindowStream windowStream(itemsStream, 5);
ResultStream resultStream(windowStream);
std::ofstream results("output.txt", std::ios::binary);

while (boost::optional<Result> result = resultStream.next()) {
    results << *result << "\n";
}

Akışlar her zaman uygulanabilir değildir (verilere rastgele erişime ihtiyacınız olduğunda çalışmazlar), ancak olduklarında sallanırlar: çok az miktarda bellekle çalışarak hepsini CPU önbelleğinde tutarsınız.


Başka bir not: Sorununuz "utanç verici bir şekilde paralel" olabilir, büyük dosyanızı parçalar halinde bölmek isteyebilirsiniz (her sınırda 4 ortak öğeye sahip olmanız gerektiğini unutmayın) ve daha sonra parçaları paralel olarak işleyin.

CPU darboğaz (G / Ç değil) ise, dosyaları kabaca eşit miktarlarda böldükten sonra sahip olduğunuz çekirdek başına bir işlem başlatarak hızlandırabilirsiniz.

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.