2015-05-28 20 views
11

Tôi đang cố gắng hiểu hồ bơi bộ nhớ để quản lý bộ nhớ, nhưng tôi không thể tìm thấy nhiều về nó, mặc dù nó có vẻ là một cơ chế rất phổ biến.Các bộ nhớ hoạt động như thế nào?

Tất cả tôi biết về việc này là "hồ nhớ, cũng gọi là phân bổ khối kích thước cố định" như nói với Wikipedia, và tôi có thể sử dụng những khối để cấp phát bộ nhớ cho các đối tượng của tôi>

Có bất kỳ tiêu chuẩn thông số kỹ thuật về bộ nhớ hồ bơi?

Tôi muốn biết làm thế nào để làm việc này trên heap, cách này có thể được thực hiện, và làm thế nào điều này nên được sử dụng?

Một ví dụ đơn giản để hiển thị như thế nào để sử dụng chúng sẽ được đánh giá

EDIT

Pool là gì?

phân bổ Pool là một chương trình bộ nhớ phân bổ đó là rất nhanh, nhưng hạn chế sử dụng của nó. Để biết thêm thông tin về phân bổ hồ bơi (cũng là được gọi là lưu trữ riêng biệt đơn giản, hãy xem khái niệm khái niệm và Đơn giản Bộ nhớ được tách biệt).

từ this question

tôi có thể hiểu được những gì ông muốn nói, nhưng điều đó không giúp tôi hiểu làm thế nào tôi có thể sử dụng chúng và cách hồ bộ nhớ có thể giúp ứng dụng của tôi, làm thế nào nó được thực hiện

+1

Hãy xem [boost :: pool] (http://www.boost.org/doc/libs/1_58_0/libs/pool/doc/html/index.html) – rds504

+0

Xem thêm: http: // stackoverflow.com/questions/16378306/c11-memory-pool-design-pattern – NathanOliver

+0

Có thể điều này sẽ giúp: [Quản lý bộ nhớ] (https://msdn.microsoft.com/en-us/library/windows/desktop/aa366779%28v = vs.85% 29.aspx) – Zero

Trả lời

12

Bất kỳ loại "hồ bơi" nào thực sự là tài nguyên bạn đã mua/khởi tạo trước để chúng sẵn sàng hoạt động, không được cấp phát khi có yêu cầu của từng khách hàng. Khi khách hàng kết thúc bằng cách sử dụng chúng, tài nguyên sẽ trở lại hồ bơi thay vì bị hủy.

Bộ nhớ cơ bản chỉ là bộ nhớ bạn đã phân bổ trước (và thường là trong các khối lớn). Ví dụ: bạn có thể cấp trước 4 kilobyte bộ nhớ. Khi một khách hàng yêu cầu 64 byte bộ nhớ, bạn chỉ cần đưa cho họ một con trỏ tới một không gian không sử dụng trong nhóm bộ nhớ đó để họ đọc và viết bất cứ thứ gì họ muốn. Khi khách hàng được thực hiện, bạn chỉ có thể đánh dấu phần bộ nhớ đó là không được sử dụng lại.

Như một ví dụ cơ bản mà không bận tâm với sự liên kết, an toàn, hoặc trở về không sử dụng (giải phóng) bộ nhớ trở lại hồ bơi:

class MemoryPool 
{ 
public: 
    MemoryPool(): ptr(mem) 
    { 
    } 

    void* allocate(int mem_size) 
    { 
     assert((ptr + mem_size) <= (mem + sizeof mem) && "Pool exhausted!"); 
     void* mem = ptr; 
     ptr += mem_size; 
     return mem; 
    } 

private: 
    MemoryPool(const MemoryPool&); 
    MemoryPool& operator=(const MemoryPool&); 
    char mem[4096]; 
    char* ptr; 
}; 

... 
{ 
    MemoryPool pool; 

    // Allocate an instance of `Foo` into a chunk returned by the memory pool. 
    Foo* foo = new(pool.allocate(sizeof(Foo))) Foo; 
    ... 
    // Invoke the dtor manually since we used placement new. 
    foo->~Foo(); 
} 

này được hiệu quả chỉ tổng hợp bộ nhớ từ ngăn xếp.Việc triển khai nâng cao hơn có thể chặn các khối lại với nhau và thực hiện phân nhánh để xem liệu khối có đầy đủ để tránh hết bộ nhớ hay không, xử lý các khối có kích thước cố định là công đoàn (liệt kê các nút khi rảnh, bộ nhớ cho ứng dụng khi được sử dụng) và nó chắc chắn cần phải đối phó với sự liên kết (cách dễ nhất là chỉ cần tối đa sắp xếp các khối bộ nhớ và thêm đệm vào từng đoạn để sắp xếp thứ tự tiếp theo).

Ưa thích hơn sẽ là người phân bổ, bảng, người dùng áp dụng thuật toán phù hợp, vv Thực hiện phân bổ không khác với cấu trúc dữ liệu, nhưng bạn có đầu gối sâu trong bit và byte thô, phải suy nghĩ về những thứ như căn chỉnh và không thể xáo trộn nội dung xung quanh (không thể vô hiệu hóa các con trỏ hiện có đối với bộ nhớ đang được sử dụng). Giống như các cấu trúc dữ liệu, không có một tiêu chuẩn vàng nào nói rằng, "bạn phải làm điều này". Có rất nhiều trong số chúng, mỗi loại đều có điểm mạnh và điểm yếu riêng, nhưng có một số thuật toán đặc biệt phổ biến để phân bổ bộ nhớ.

Trình phân bổ triển khai thực sự là điều mà tôi thực sự muốn giới thiệu cho nhiều nhà phát triển C và C++ chỉ để phù hợp với cách quản lý bộ nhớ hoạt động tốt hơn một chút. Nó có thể giúp bạn hiểu rõ hơn về cách bộ nhớ được yêu cầu kết nối với các cấu trúc dữ liệu bằng cách sử dụng chúng, và cũng mở ra một cánh cửa hoàn toàn mới của các cơ hội tối ưu hóa mà không cần sử dụng bất kỳ cấu trúc dữ liệu mới nào. Nó cũng có thể làm cho các cấu trúc dữ liệu như các danh sách được liên kết thường không hiệu quả hơn nhiều và hữu ích hơn và giảm sự cám dỗ để làm cho các loại mờ đục/trừu tượng ít mờ đục hơn để tránh chi phí cao. Tuy nhiên, có thể có một sự phấn khích ban đầu mà có thể muốn khiến bạn phải phân bổ tùy chỉnh cho mọi thứ, chỉ để sau đó hối hận về gánh nặng bổ sung (đặc biệt nếu, trong sự phấn khích của bạn, bạn quên đi các vấn đề như an toàn luồng và căn chỉnh). Nó có giá trị mang nó dễ dàng ở đó. Như với bất kỳ tối ưu hóa vi mô nào, nó thường được áp dụng tốt nhất một cách kín đáo, trong nhận thức muộn màng và với một hồ sơ trong tay.

+1

Cảm ơn bạn, đây chính là câu trả lời tôi đang tìm kiếm !! –

1

Về cơ bản , bộ nhớ cho phép bạn tránh một số chi phí phân bổ bộ nhớ trong một chương trình phân bổ và giải phóng bộ nhớ thường xuyên. Những gì bạn làm là phân bổ một phần lớn bộ nhớ lúc bắt đầu thực hiện và tái sử dụng cùng một bộ nhớ cho các phân bổ khác nhau mà không chồng chéo tạm thời. Bạn phải có một số cơ chế để theo dõi bộ nhớ nào có sẵn và sử dụng bộ nhớ đó để phân bổ. Khi bạn đã hoàn thành bộ nhớ, thay vì giải phóng bộ nhớ, hãy đánh dấu nó là có sẵn một lần nữa.

Nói cách khác, thay vì các cuộc gọi đến new/mallocdelete/free, thực hiện cuộc gọi đến các chức năng cấp phát/deallocator tự xác định của bạn.

Việc làm này cho phép bạn chỉ làm một phân bổ (giả sử bạn biết khoảng bao nhiêu bộ nhớ bạn sẽ cần trong tổng số) trong quá trình thực hiện. Nếu chương trình của bạn có độ trễ hơn là bộ nhớ bị ràng buộc, bạn có thể viết hàm phân bổ thực hiện nhanh hơn malloc với chi phí sử dụng bộ nhớ.

5

Khái niệm cơ bản của một hồ bơi bộ nhớ là phải phân bổ một phần lớn bộ nhớ cho ứng dụng của bạn, và, sau đó, thay vì sử dụng đơn giản new để yêu cầu bộ nhớ từ O/S, bạn quay lại một đoạn của giao trước đây bộ nhớ thay thế.

Để thực hiện việc này, bạn cần phải quản lý sử dụng bộ nhớ bản thân và không thể dựa vào các O/S; tức là, bạn cần phải triển khai các phiên bản newdelete của riêng mình và chỉ sử dụng các phiên bản gốc khi phân bổ, giải phóng hoặc có khả năng định lại kích thước nhóm bộ nhớ của riêng bạn.

Cách tiếp cận đầu tiên là xác định Lớp của riêng một gói bộ nhớ và cung cấp các phương thức tùy chỉnh thực hiện ngữ nghĩa của newdelete, nhưng lấy bộ nhớ từ nhóm được phân bổ trước. Hãy nhớ rằng, hồ bơi này là không có gì nhiều hơn một khu vực bộ nhớ đã được phân bổ bằng cách sử dụng new và có một kích thước tùy ý. Phiên bản new/delete trả lại của hồ bơi. lấy con trỏ. Phiên bản đơn giản nhất có thể trông giống như mã C:

void *MyPool::malloc(const size_t &size) 
void MyPool::free(void *ptr) 

Bạn có thể thêm tiêu đề này với mẫu để tự động thêm chuyển đổi, ví dụ:

template <typename T> 
T *MyClass::malloc(); 

template <typename T> 
void MyClass::free(T *ptr); 

Chú ý rằng, nhờ vào các đối số mẫu, lập luận size_t size thể được bỏ qua kể từ khi trình biên dịch cho phép bạn gọi sizeof(T) trong malloc().

Trả về một con trỏ đơn giản có nghĩa là hồ bơi của bạn chỉ có thể phát triển khi có bộ nhớ liền kề và chỉ thu nhỏ nếu bộ nhớ trong vùng ở "đường viền" của nó không được lấy. Cụ thể hơn, bạn không thể di chuyển hồ bơi vì điều đó sẽ làm mất hiệu lực tất cả các con trỏ hàm malloc của bạn trả lại.

Cách khắc phục giới hạn này là trả lại con trỏ cho con trỏ, tức là trả lại T** thay vì chỉ đơn giản là T*. Điều đó cho phép bạn thay đổi con trỏ bên dưới trong khi phần người dùng phải đối mặt vẫn giữ nguyên. Ngẫu nhiên, điều đó đã được thực hiện cho NeXT O/S, nơi nó được gọi là "xử lý". Để truy cập vào nội dung của tay cầm, người ta phải gọi (*handle)->method() hoặc (**handle).method(). Cuối cùng, Maf Vosburg đã phát minh ra một toán tử giả khai thác toán tử ưu tiên để loại bỏ cú pháp (*handle)->method(): handle[0]->method(); Cú pháp này được gọi là sprong operator.

Những lợi ích của hoạt động này là: Thứ nhất, bạn tránh được những chi phí của một cuộc gọi thông thường để newdelete, và thứ hai, hồ bơi bộ nhớ của bạn đảm bảo rằng một đoạn tiếp giáp bộ nhớ được sử dụng bởi ứng dụng của bạn, ví dụ, nó tránh bộ nhớ phân mảnh và do đó tăng số lần truy cập bộ nhớ cache CPU.

Vì vậy, về cơ bản, một nhóm bộ nhớ cung cấp cho bạn tốc độ bạn đạt được với nhược điểm của mã ứng dụng phức tạp hơn. Nhưng sau đó một lần nữa, có một số hiện thực của các hồ bơi bộ nhớ được chứng minh và có thể chỉ đơn giản được sử dụng, chẳng hạn như boost::pool.

+0

Tôi không nghĩ rằng mã phức tạp hơn thực sự là nhược điểm chính. Nếu được thực hiện đúng cách, mô-đun cấp phát phải ít nhiều tự chứa (như được chứng minh bằng cách tăng :: nhóm). Tôi nghĩ nhược điểm chính là sử dụng bộ nhớ hình phạt cần thiết để thực hiện các bộ nhớ. – Daniel

+0

Bạn có một số mã của việc triển khai đơn giản các vùng bộ nhớ không? –

+1

Tôi chưa thử nghiệm, nhưng điều này có vẻ hợp lý: https://github.com/cacay/MemoryPool/blob/master/C-11/MemoryPool.tcc – Technaton

Các vấn đề liên quan