วิธีการอย่างมืออาชีพในการสร้างปัญหาใหญ่โดยไม่ต้องกรอกอาร์เรย์ขนาดใหญ่: C ++ หน่วยความจำฟรีจากส่วนหนึ่งของอาร์เรย์


20

ฉันกำลังพัฒนาแบบจำลองทางฟิสิกส์และเนื่องจากฉันค่อนข้างใหม่กับการเขียนโปรแกรมฉันยังคงพบปัญหาเมื่อผลิตโปรแกรมขนาดใหญ่ ฉันรู้เกี่ยวกับการจัดสรรและลบหน่วยความจำแบบไดนามิก (ใหม่ / ลบ ฯลฯ ) แต่ฉันต้องการวิธีที่ดีกว่าในการจัดโครงสร้างโปรแกรม

สมมติว่าฉันกำลังจำลองการทดสอบที่ใช้งานมาสองสามวันด้วยอัตราการสุ่มตัวอย่างที่ใหญ่มาก ฉันต้องการจำลองตัวอย่างเป็นพันล้านตัวอย่าง

ในฐานะเวอร์ชั่นที่เรียบง่ายที่สุดเราจะกล่าวว่าโปรแกรมใช้แรงดันไฟฟ้า V [i] และรวมเข้าด้วยกันเป็นห้า:

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

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

ดังนั้น NewV [2] = V [2] + V [3] + V [4] + V [5] + V [6] ... และสิ่งนี้จะดำเนินต่อไปเป็นตัวอย่างพันล้านตัวอย่าง

ในท้ายที่สุดฉันจะมี V [0], V [1], ... , V [1000000000] เมื่อแทนที่สิ่งเดียวที่ฉันต้องจัดเก็บสำหรับขั้นตอนต่อไปคือ 5 V สุดท้าย [i] s

ฉันจะลบ / ยกเลิกการจัดสรรส่วนหนึ่งของอาเรย์เพื่อให้หน่วยความจำสามารถใช้งานได้อีกครั้ง (เช่น V [0] หลังจากส่วนแรกของตัวอย่างที่ไม่จำเป็นต้องใช้อีกต่อไป)? มีทางเลือกอื่นในการจัดโครงสร้างของโปรแกรมเช่นนี้หรือไม่?

ฉันเคยได้ยินเกี่ยวกับ malloc / free แต่ได้ยินมาว่าพวกเขาไม่ควรใช้ใน C ++ และมีทางเลือกที่ดีกว่า

ขอบคุณมาก ๆ!

TLDR; จะทำอย่างไรกับส่วนของอาร์เรย์ (แต่ละองค์ประกอบ) ฉันไม่ต้องการอีกต่อไปที่ใช้หน่วยความจำจำนวนมาก?


2
คุณไม่สามารถยกเลิกการจัดสรรส่วนหนึ่งของอาร์เรย์ คุณสามารถจัดสรรให้กับอาร์เรย์ขนาดเล็กกว่าที่อื่น แต่สิ่งนี้อาจพิสูจน์ว่ามีราคาแพง คุณสามารถใช้โครงสร้างข้อมูลอื่นเช่นรายการเชื่อมโยงแทนได้ บางทีคุณอาจเก็บขั้นตอนไว้ในVแทนที่จะเป็นอาร์เรย์ใหม่ได้ อย่างไรก็ตามโดยพื้นฐานแล้วฉันคิดว่าปัญหาของคุณอาจอยู่ในอัลกอริธึมหรือโครงสร้างข้อมูลของคุณและเนื่องจากเราไม่มีรายละเอียดใด ๆ จึงเป็นการยากที่จะทราบวิธีการทำอย่างมีประสิทธิภาพ
Vincent Savard

4
หมายเหตุด้านข้าง: SMA ของความยาวตามอำเภอใจสามารถคำนวณได้อย่างรวดเร็วโดยเฉพาะกับความสัมพันธ์ที่เกิดซ้ำนี้: NewV [n] = NewV [n-1] - V [n-1] + V [n + 4] (เอกสารของคุณ) แต่โปรดทราบว่าตัวกรองเหล่านี้ไม่มีประโยชน์อย่างยิ่ง การตอบสนองความถี่ของพวกเขาคือ sinc ซึ่งค่อนข้างไม่เคยเป็นสิ่งที่คุณต้องการ (sidelobes สูงมาก)
Steve Cox

2
SMA = ค่าเฉลี่ยเคลื่อนที่ง่ายสำหรับทุกคนที่สงสัย
ชาร์ลส์

3
@SteveCox วิธีที่เขาเขียนมันมีตัวกรอง FIR การเกิดซ้ำของคุณเป็นรูปแบบ IIR ที่เทียบเท่ากัน ทั้งสองวิธีคุณจะได้รับการบำรุงรักษาบัฟเฟอร์แบบวงกลมของการอ่านค่า N ครั้งล่าสุด
John R. Strohm

@ JohnR.Strohm การตอบสนองต่อแรงกระตุ้นเหมือนกันและ จำกัด
Steve Cox

คำตอบ:


58

สิ่งที่คุณอธิบายว่า "การทำให้ราบรื่นโดย fives" คือตัวกรองดิจิตัลการตอบสนองต่อแรงกระตุ้น จำกัด (FIR) ตัวกรองดังกล่าวถูกนำไปใช้กับบัฟเฟอร์แบบวงกลม คุณเก็บเฉพาะค่า N ล่าสุดคุณเก็บดัชนีไว้ในบัฟเฟอร์ที่บอกคุณว่าค่าที่เก่าที่สุดคืออะไรคุณเขียนทับค่าที่เก่าที่สุดในปัจจุบันด้วยค่าที่ใหม่ที่สุดในแต่ละขั้นตอนและคุณทำดัชนีเป็นวงกลมทุกครั้ง

คุณเก็บข้อมูลที่เก็บรวบรวมไว้ของคุณไว้ในดิสก์

ขึ้นอยู่กับสภาพแวดล้อมของคุณนี่อาจเป็นหนึ่งในสถานที่ที่คุณจะได้รับการช่วยเหลือที่ดีกว่า ที่มหาวิทยาลัยคุณจดบันทึกไว้ในกระดานข่าวในแผนกวิทยาศาสตร์คอมพิวเตอร์โดยเสนอค่าจ้างนักเรียน (หรือแม้แต่อัตราการให้คำปรึกษาของนักเรียน) ในการทำงานสองสามชั่วโมงเพื่อช่วยคุณบีบข้อมูลของคุณ หรือคุณอาจเสนอคะแนนโอกาสในการวิจัยระดับปริญญาตรี หรือบางสิ่งบางอย่าง.


6
บัฟเฟอร์วงกลมดูเหมือนจะเป็นสิ่งที่ฉันกำลังมองหา! ตอนนี้ฉันได้ติดตั้งไลบรารีเพิ่ม C ++ และรวมถึง boost / circular_buffer.hpp แล้วและทำงานได้ตามที่คาดไว้ ขอบคุณ
@John

2
มีเพียงตัวกรอง FIR สั้น ๆ เท่านั้นที่นำมาใช้ในรูปแบบโดยตรงในซอฟต์แวร์และ SMA แทบจะไม่เคยเป็น
Steve Cox

@SteveCox: สูตร edge-of-window ที่คุณใช้นั้นค่อนข้างมีประสิทธิภาพสำหรับตัวกรองจำนวนเต็มและจุดคงที่ แต่มันไม่ถูกต้องสำหรับ floating-point ซึ่งการดำเนินการไม่ได้สลับกัน
Ben Voigt

@ BenVoigt ฉันคิดว่าคุณหมายถึงการตอบสนองต่อความคิดเห็นอื่น ๆ ของฉัน แต่ใช่รูปแบบนั้นจะแนะนำรอบการ จำกัด รอบการหาปริมาณซึ่งอาจยุ่งยากมาก แต่โชคดีที่วงจรลิมิตที่ จำกัด นี้มีความเสถียร
Steve Cox

คุณไม่จำเป็นต้องเพิ่มบูทบัฟเฟอร์แบบวงกลมจริงๆสำหรับการใช้งาน uu คุณจะใช้หน่วยความจำมากกว่าที่จำเป็น
GameDeveloper

13

ทุกปัญหาสามารถแก้ไขได้โดยการเพิ่มระดับการอ้อมไปอีกระดับ ทำเช่นนั้น

คุณไม่สามารถลบบางส่วนของอาร์เรย์ใน C ++ แต่คุณสามารถสร้างอาร์เรย์ใหม่ที่เก็บเฉพาะข้อมูลที่คุณต้องการเก็บไว้แล้วลบอาร์เรย์เก่า ดังนั้นคุณสามารถสร้างโครงสร้างข้อมูลที่อนุญาตให้คุณ "ลบ" องค์ประกอบที่คุณไม่ต้องการจากด้านหน้า สิ่งที่จะทำจริง ๆ คือสร้างอาร์เรย์ใหม่และคัดลอกองค์ประกอบที่ไม่ได้ย้ายไปยังองค์ประกอบใหม่จากนั้นลบองค์ประกอบเก่า

หรือคุณสามารถใช้std::dequeซึ่งทำได้อย่างมีประสิทธิภาพแล้ว dequeหรือ "คิวคู่สิ้นสุด" เป็นโครงสร้างข้อมูลที่มีไว้สำหรับกรณีที่คุณกำลังลบองค์ประกอบจากปลายด้านหนึ่งขณะที่เพิ่มองค์ประกอบลงในอีกด้านหนึ่ง


30
ทุกปัญหาสามารถแก้ไขได้โดยการเพิ่มระดับการอ้อมอีกหนึ่ง ... ยกเว้นหลายระดับทางอ้อม
YSC

17
@YSC: และการสะกดคำ :)
การแข่งขัน Lightness กับโมนิก้า

1
สำหรับปัญหานี้โดยเฉพาะstd::dequeคือวิธีการเดินทาง
davidbak

7
@davidbak - อะไรนะ ไม่จำเป็นต้องจัดสรรและปล่อยหน่วยความจำอย่างต่อเนื่อง บัฟเฟอร์ทรงกลมขนาดคงที่ที่ถูกจัดสรรครั้งเดียวในเวลาเริ่มต้นจะเหมาะสมกับปัญหานี้มากขึ้น
เดวิดแฮมเมน

2
@DavidHammen: บางที แต่ 1) ไลบรารีมาตรฐานไม่มี "บัฟเฟอร์แบบวงกลมขนาดคงที่" ในชุดเครื่องมือ 2) dequeหากคุณต้องการเพิ่มประสิทธิภาพดังกล่าวจริงๆคุณสามารถทำบางสิ่งที่จัดสรรเพื่อลดการจัดสรรผ่าน นั่นคือจัดเก็บและใช้การจัดสรรซ้ำตามที่ร้องขอ ดังนั้นdequeดูเหมือนจะเป็นทางออกที่เพียงพอที่จะแก้ไขปัญหา
Nicol Bolas

4

คำตอบของ FIR และ SMA ที่คุณได้รับนั้นดีในกรณีของคุณ แต่ฉันต้องการใช้โอกาสที่จะผลักดันวิธีการทั่วไปให้มากขึ้น

สิ่งที่คุณมีอยู่ที่นี่คือกระแสข้อมูล: แทนที่จะจัดโครงสร้างโปรแกรมของคุณใน 3 ขั้นตอนใหญ่ ๆ (รับข้อมูลคำนวณผลลัพธ์ผลลัพธ์) ซึ่งต้องการโหลดข้อมูลทั้งหมดในหน่วยความจำพร้อมกันคุณสามารถจัดโครงสร้างเป็นท่อได้

ไปป์ไลน์เริ่มต้นด้วยสตรีมแปลงและผลักไปที่ซิงก์

ในกรณีของคุณท่อดูเหมือนว่า:

  1. อ่านรายการจากดิสก์ปล่อยรายการทีละรายการ
  2. รับรายการทีละรายการสำหรับแต่ละรายการที่ได้รับปล่อย 5 ครั้งสุดท้ายที่ได้รับ (ที่บัฟเฟอร์แบบวงกลมของคุณเข้ามา)
  3. รับไอเท็มครั้งละ 5 สำหรับแต่ละกลุ่มคำนวณผลลัพธ์
  4. รับผลลัพธ์เขียนลงดิสก์

C ++ มีแนวโน้มที่จะใช้ตัววนซ้ำมากกว่าสตรีม แต่การเป็นสตรีมแบบซื่อสัตย์นั้นง่ายกว่าในการสร้างแบบจำลอง (มีข้อเสนอสำหรับช่วงซึ่งจะคล้ายกับสตรีม):

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;
};

จากนั้นท่อจะมีลักษณะดังนี้:

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";
}

สตรีมไม่สามารถใช้งานได้ตลอดเวลา (มันไม่ทำงานเมื่อคุณต้องการเข้าถึงข้อมูลแบบสุ่ม) แต่เมื่อมันเข้ามาก็จะสั่นเมื่อทำงานบนหน่วยความจำขนาดเล็กมากที่คุณเก็บไว้ใน CPU cache


ในหมายเหตุอื่น: ดูเหมือนว่าปัญหาของคุณอาจเป็น "ขนานที่น่าอับอาย" คุณอาจต้องการแยกไฟล์ขนาดใหญ่เป็นชิ้น ๆ (โปรดจำไว้ว่าสำหรับการประมวลผลโดยหน้าต่าง 5 ซึ่งคุณต้องมีองค์ประกอบทั่วไป 4 ข้อในแต่ละขอบเขต) จากนั้นประมวลผลชิ้นส่วนแบบขนาน

หาก CPU เป็นคอขวด (ไม่ใช่ I / O) คุณสามารถเร่งความเร็วได้โดยเปิดหนึ่งกระบวนการต่อหนึ่งคอร์ที่คุณมีหลังจากแยกไฟล์ในจำนวนที่เท่ากันโดยประมาณ

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.