Cách chuyên nghiệp để tạo ra một vấn đề lớn mà không cần lấp đầy các mảng lớn: C ++, bộ nhớ trống từ một phần của mảng


20

Tôi đang phát triển một mô phỏng vật lý, và vì tôi khá mới mẻ với lập trình, tôi tiếp tục gặp vấn đề khi sản xuất các chương trình lớn (chủ yếu là vấn đề bộ nhớ). Tôi biết về phân bổ và xóa bộ nhớ động (mới / xóa, v.v.), nhưng tôi cần một cách tiếp cận tốt hơn về cách tôi cấu trúc chương trình.

Giả sử tôi đang mô phỏng một thử nghiệm đang chạy trong vài ngày, với tỷ lệ lấy mẫu rất lớn. Tôi cần mô phỏng một tỷ mẫu và chạy qua chúng.

Là một phiên bản siêu đơn giản, chúng tôi sẽ nói rằng một chương trình có điện áp V [i] và tính tổng chúng trong các mệnh đề:

tức là NewV [0] = V [0] + V [1] + V [2] + V [3] + V [4]

sau đó NewV [1] = V [1] + V [2] + V [3] + V [4] + V [5]

sau đó NewV [2] = V [2] + V [3] + V [4] + V [5] + V [6] ... và điều này diễn ra trong một tỷ mẫu.

Cuối cùng, tôi có V [0], V [1], ..., V [1000000000], khi thay vào đó, những thứ duy nhất tôi cần lưu trữ cho bước tiếp theo là 5 V cuối cùng [i] S.

Làm thế nào tôi có thể xóa / sắp xếp lại một phần của mảng để bộ nhớ được sử dụng lại miễn phí (giả sử V [0] sau phần đầu tiên của ví dụ khi không còn cần thiết)? Có những lựa chọn thay thế cho cách cấu trúc một chương trình như vậy?

Tôi đã nghe nói về malloc / miễn phí, nhưng nghe nói rằng chúng không nên được sử dụng trong C ++ và có những lựa chọn thay thế tốt hơn.

Cảm ơn rất nhiều!

tldr; Phải làm gì với các phần của mảng (các phần tử riêng lẻ) Tôi không cần nữa mà đang chiếm một lượng lớn bộ nhớ?


2
Bạn không thể phân bổ một phần của một mảng. Bạn có thể phân bổ lại nó cho một mảng nhỏ hơn ở một nơi khác, nhưng điều này có thể chứng minh là đắt tiền. Thay vào đó, bạn có thể sử dụng một cấu trúc dữ liệu khác nhau như danh sách được liên kết. Có lẽ bạn cũng có thể lưu trữ các bước vào Vthay vì trong một mảng mới. Tuy nhiên, về cơ bản, tôi nghĩ vấn đề của bạn là ở thuật toán hoặc cấu trúc dữ liệu của bạn và vì chúng tôi không có bất kỳ chi tiết nào, thật khó để biết cách thực hiện hiệu quả.
Vincent Savard

4
Lưu ý bên lề: SMA có độ dài tùy ý có thể được tính đặc biệt nhanh với mối quan hệ lặp lại này: NewV [n] = NewV [n-1] - V [n-1] + V [n + 4] (ký hiệu của bạn). Nhưng hãy nhớ rằng đây không phải là những bộ lọc đặc biệt hữu ích. Đáp ứng tần số của họ là một sự chân thành, đó là khá nhiều không bao giờ những gì bạn muốn (sidelobes thực sự cao).
Steve Cox

2
SMA = trung bình di chuyển đơn giản, cho bất cứ ai thắc mắc.
Charles

3
@SteveCox, theo cách anh ấy viết, anh ấy có bộ lọc FIR. Tái phát của bạn là hình thức IIR tương đương. Dù bằng cách nào, bạn có thể duy trì bộ đệm tròn của N lần đọc cuối cùng.
John R. Strohm

@ JohnR.Strohm phản ứng thúc đẩy là giống hệt nhau và hữu hạn
Steve Cox

Câu trả lời:


58

Những gì bạn mô tả, "làm mịn bởi các cặp vợ chồng", là một bộ lọc kỹ thuật số đáp ứng xung hữu hạn (FIR). Bộ lọc như vậy được thực hiện với bộ đệm tròn. Bạn chỉ giữ các giá trị N cuối cùng, bạn giữ một chỉ mục vào bộ đệm cho bạn biết giá trị cũ nhất ở đâu, bạn ghi đè giá trị cũ nhất hiện tại với giá trị mới nhất ở mỗi bước và mỗi lần bạn lập chỉ mục.

Bạn giữ dữ liệu đã thu thập của mình, rằng bạn sẽ giảm xuống, trên đĩa.

Tùy thuộc vào môi trường của bạn, đây có thể là một trong những nơi mà bạn tốt hơn nên nhận trợ giúp có kinh nghiệm. Tại một trường đại học, bạn ghi chú lên bảng thông báo ở Khoa Khoa học Máy tính, đưa ra mức lương sinh viên (hoặc thậm chí là giá tư vấn của sinh viên) trong vài giờ làm việc, để giúp bạn xử lý dữ liệu. Hoặc có thể bạn cung cấp điểm Cơ hội nghiên cứu đại học. Hoặc một cái gì đó.


6
Một bộ đệm tròn thực sự có vẻ là những gì tôi đang tìm kiếm! Bây giờ tôi đã cài đặt các thư viện boost C ++ và bao gồm boost / circle_buffer.hpp và đang hoạt động như mong đợi. Cảm ơn, @ John
Drumermean

2
chỉ các bộ lọc FIR rất ngắn được triển khai ở dạng trực tiếp trong phần mềm và hầu như không bao giờ có SMA.
Steve Cox

@SteveCox: Công thức cạnh của cửa sổ bạn đã sử dụng khá hiệu quả đối với các bộ lọc số nguyên và điểm cố định, tuy nhiên nó không chính xác cho dấu phẩy động, trong đó các thao tác không giao hoán.
Ben Voigt

@BenVoigt Tôi nghĩ rằng bạn muốn trả lời bình luận khác của tôi, nhưng vâng, hình thức đó giới thiệu một chu kỳ giới hạn xung quanh việc lượng tử hóa có thể rất khó khăn. rất may, chu kỳ giới hạn đặc biệt này xảy ra ổn định.
Steve Cox

Bạn không thực sự cần tăng cường cho bộ đệm tròn cho việc sử dụng đó uu Bạn sẽ sử dụng nhiều bộ nhớ hơn mức cần thiết.
Nhà phát triển GameD

13

Mọi vấn đề có thể được giải quyết bằng cách thêm một mức độ bổ sung. Vì vậy, làm điều đó.

Bạn không thể xóa một phần của một mảng trong C ++. Nhưng bạn có thể tạo một mảng mới chỉ giữ dữ liệu bạn muốn giữ, sau đó xóa dữ liệu cũ. Vì vậy, bạn có thể xây dựng cấu trúc dữ liệu cho phép bạn "xóa" các yếu tố bạn không muốn từ phía trước. Những gì nó thực sự sẽ làm là tạo ra một mảng mới và sao chép các phần tử không được yêu thích sang cái mới, sau đó xóa cái cũ.

Hoặc bạn chỉ có thể sử dụng std::deque, có thể thực hiện điều này một cách hiệu quả. dequehoặc "hàng đợi hai đầu", là cấu trúc dữ liệu dành cho các trường hợp bạn xóa các phần tử từ một đầu trong khi thêm các phần tử vào đầu kia.


30
Mọi vấn đề đều có thể được giải quyết bằng cách thêm một mức độ bổ sung ... ngoại trừ nhiều cấp độ gián tiếp.
YSC

17
@YSC: và đánh vần :)
Cuộc đua nhẹ nhàng với Monica

1
cho vấn đề đặc biệt này std::dequelà con đường để đi
davidbak

7
@davidbak - Cái gì? Không cần phải liên tục phân bổ và giải phóng bộ nhớ. Một bộ đệm tròn có kích thước cố định được phân bổ một lần tại thời điểm khởi tạo sẽ phù hợp hơn với vấn đề này.
David Hammen

2
@DavidHammen: Có lẽ, nhưng 1) Thư viện tiêu chuẩn không có "bộ đệm tròn kích thước cố định" trong bộ công cụ của nó. 2) Nếu bạn thực sự cần tối ưu hóa như vậy, bạn có thể thực hiện một số công cụ phân bổ để giảm thiểu phân bổ thông qua deque. Đó là, lưu trữ và sử dụng lại phân bổ theo yêu cầu. Vì vậy, dequecó vẻ như một giải pháp hoàn toàn đầy đủ cho vấn đề.
Nicol Bolas

4

Các câu trả lời FIR và SMA mà bạn nhận được rất tốt trong trường hợp của bạn, tuy nhiên tôi muốn tận dụng cơ hội để đưa ra một cách tiếp cận chung chung hơn.

Những gì bạn có ở đây là một luồng dữ liệu: thay vì cấu trúc chương trình của bạn theo 3 bước lớn (lấy dữ liệu, tính toán, kết quả đầu ra) yêu cầu tải tất cả dữ liệu vào bộ nhớ cùng một lúc, thay vào đó bạn có thể cấu trúc nó như một đường ống dẫn .

Một đường ống bắt đầu với một luồng, biến đổi nó và đẩy nó vào một bồn rửa.

Trong trường hợp của bạn, đường ống trông giống như:

  1. Đọc các mục từ đĩa, phát ra từng mục một
  2. Nhận từng mục một, cho mỗi mục nhận được phát ra 5 mục cuối nhận được (trong đó bộ đệm tròn của bạn xuất hiện)
  3. Nhận các mục 5 tại một thời điểm, cho mỗi nhóm tính kết quả
  4. Nhận kết quả, ghi nó vào đĩa

C ++ có xu hướng sử dụng các trình vòng lặp hơn là các luồng, nhưng thành thật mà nói các luồng dễ mô hình hóa hơn (có một đề xuất cho các phạm vi tương tự như các luồng):

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

Và sau đó, đường ống trông giống như:

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

Các luồng không phải lúc nào cũng có thể áp dụng (chúng không hoạt động khi bạn cần truy cập ngẫu nhiên vào dữ liệu), nhưng khi có, chúng sẽ rung chuyển: bằng cách hoạt động trên một lượng bộ nhớ rất nhỏ, bạn giữ tất cả trong bộ nhớ cache của CPU.


Một lưu ý khác: có vẻ như vấn đề của bạn có thể là "song song lúng túng", bạn có thể muốn chia tệp lớn của mình thành từng phần (lưu ý, để xử lý bởi các cửa sổ 5, bạn cần phải có 4 yếu tố chung ở mỗi ranh giới) và sau đó xử lý các khối song song.

Nếu CPU là nút cổ chai (chứ không phải I / O), thì bạn có thể tăng tốc nó bằng cách khởi chạy một tiến trình cho mỗi lõi mà bạn có sau khi chia các tệp với số lượng gần bằng nhau.

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.