Tại sao chúng ta cần Heap nếu mọi thứ có thể được thực hiện hiệu quả hơn trên Stack?


24

Điều này thực sự có liên quan đến câu hỏi tôi đã hỏi ngày hôm qua về lý do tại sao cả Stack Heap đều cần thiết trong các ứng dụng chúng ta sử dụng ngày nay (và tại sao chúng ta không thể đi với Heap thay vì cả hai, để đơn giản & tiêu chuẩn số ít để đi theo).

Tuy nhiên, nhiều câu trả lời chỉ ra rằng Stack không thể thay thế được do thực tế là nhanh hơn hàng trăm (hoặc hàng nghìn) lần so với việc cố gắng phân bổ / tham chiếu Heap. Tôi biết có vấn đề với phân bổ lưu trữ động nếu chúng ta loại bỏ Heap, nhưng không có cách nào khác, hoặc có lẽ, cách cải thiện trên Stack để có thể xử lý cấp phát bộ nhớ động?


4
Hai trích đoạn từ câu hỏi trước của bạn: "nhược điểm quan trọng nhất là nó có không gian hạn chế, và vì vậy việc giữ các vật thể lớn trong đó, hoặc cố gắng sử dụng nó cho các đối tượng tồn tại lâu, đều là những ý tưởng tồi" và "ngăn xếp là một cách cực kỳ hiệu quả cấu trúc để quản lý dữ liệu tuân theo quy tắc LIFO (cuối cùng ra trước) ".
Cascabel

2
Tiền đề của bạn bị lỗi - không phải mọi thứ đều có thể được thực hiện hiệu quả hơn trên stack. Điều này không mâu thuẫn với câu trả lời bạn nhận được - rằng những gì có thể được thực hiện trên ngăn xếp có thể được thực hiện nhanh hơn nhiều ở đó.
Ingo

... Giả sử phần cứng của bạn có ngăn xếp hoặc địa chỉ liên quan đến ngăn xếp.
Ritch Melton

3
Tôi đã bị thuyết phục. Tôi nói làm đi.
JeffO

Câu trả lời:


25

Vấn đề với ngăn xếp là bạn không thể "giải phóng" bộ nhớ trừ khi nó nằm trên cùng của ngăn xếp. Chẳng hạn, giả sử bạn đã phân bổ 3 thứ với các kích cỡ khác nhau:

a = allocate(2000000); // 2000000 bytes
b = allocate(1);
c = allocate(5000000);

Ngăn xếp sẽ có aở dưới cùng, bở giữa và ctrên cùng. Điều này trở nên có vấn đề nếu chúng ta muốn giải phóng b:

free(b); // b is not on top! We have to wait until c is freed!

Cách giải quyết là di chuyển tất cả dữ liệu sau bvà dịch chuyển nếu để nó đến sau a. Điều này hoạt động, nhưng sẽ yêu cầu 5000000 bản trong trường hợp này - một cái gì đó sẽ chậm hơn nhiều so với một đống.

Đây là lý do tại sao chúng ta có một đống. Mặc dù phân bổ có thể chậm hơn so với ngăn xếp ( O(log n)so với O(1)), heaps cho phép giải phóng bộ nhớ tại một vị trí tùy ý nhanh - O(log n)so với ngăn xếpO(n)


4
Liên quan đến vấn đề này là Facebook không xóa nội dung khỏi đĩa của nó khi bạn yêu cầu xóa nó, nó chỉ xóa con trỏ tới nó. Rõ ràng là chi phí cố gắng chống phân mảnh hoặc cố gắng tìm khoảng trống tương đương trên đĩa quá tốn thời gian với tốc độ họ đang ghi dữ liệu, vì vậy họ chỉ cần thêm mọi thứ ở mức nước cao của đĩa.
Paul Tomblin

Chà, đĩa của facebook có thể được xem là đống. Và tôi khá chắc chắn rằng họ có một số loại bộ sưu tập rác cho các đĩa.
deadalnix

2
@deadalnix Thật ra đó là một ví dụ về việc sử dụng một ngăn xếp khổng lồ thay vì heap thường được sử dụng cho số lượng bộ nhớ lớn hơn. Facebook là một trường hợp đặc biệt. Dữ liệu được thêm vào nhanh hơn nhiều so với dữ liệu bị xóa mà việc phân bổ không tạo ra sự khác biệt đáng kể về tốc độ tăng trưởng - bạn có thể cố tình đưa rò rỉ bộ nhớ vào thiết kế để có phân bổ O (1).
Tom Clarkson

7
@PaulTomblin Lý do chính FB không xóa nội dung là để họ có thể khai thác nó vì lợi nhuận của họ ...
quant_dev

5
Facebook doesn't remove content from its disk when you ask it to remove it, it just removes the pointer to it- Đó thực chất là những gì xảy ra khi bạn xóa một tệp thông thường trên bất kỳ hệ điều hành nào.
Robert Harvey

5

Stack là trên mỗi luồng, Heap là toàn bộ quá trình

Nếu có 100 luồng xử lý tất cả các mục công việc tôi đặt vào hàng đợi, chính xác thì tôi sẽ phân bổ các mục công việc ở đâu để bất kỳ trong số 100 luồng có thể nhìn thấy chúng?

Có nhiều loại bộ nhớ khác nữa

Ví dụ: các tệp ánh xạ bộ nhớ, bộ nhớ dùng chung, ánh xạ I / O (chế độ kernel). Đối số hiệu quả là loại tranh luận trong những tình huống này.


4

Một ngăn xếp là một cấu trúc LIFO (từ trước đến trước), đến đỉnh của một con trỏ tham chiếu được giữ (thường được hỗ trợ bởi phần cứng). Đã nói điều này, bất cứ điều gì bạn đang cố gắng phân bổ trên ngăn xếp thay vì heap sẽ phải là một biến cục bộ trong mọi hàm, ở đầu ngăn xếp này. Vì vậy, lý do chính chống lại ngăn xếp là vì thói quen chính () của bạn sẽ cần phân bổ lại tất cả các cấu trúc dữ liệu mà chương trình của bạn sử dụng (dự định sẽ tồn tại trong suốt thời gian hoàn thành chương trình của bạn) trước khi tất cả các cấu trúc dữ liệu được phân bổ trong các lệnh gọi hàm cuối cùng sẽ bị xóa khi các lệnh gọi hàm đó trả về và các khung hoặc bản ghi kích hoạt của chúng được bật ra khỏi ngăn xếp.


3
LIFO, không phải là FIFO.
Pubby

đôi khi cũng được gọi là FILO;)
oenone

3

Ngăn xếp hoạt động tuyệt vời cho việc phân bổ bộ nhớ tuân theo quy tắc Last in First out (LIFO), nghĩa là bạn giải phóng bộ nhớ theo thứ tự ngược chính xác mà bạn phân bổ. LIFO là một mô hình phân bổ bộ nhớ rất phổ biến, có lẽ là phổ biến nhất. Nhưng đó không phải là mẫu duy nhất, hoặc thậm chí là mẫu chung duy nhất. Để viết chương trình hiệu quả có thể giải quyết nhiều vấn đề khác nhau, chúng ta phải tạo ra trợ cấp cho các mẫu ít phổ biến hơn, ngay cả khi nó có nghĩa là một cơ sở hạ tầng phức tạp hơn.

Nếu tôi có thể nhận được tất cả meta cho một đoạn văn: bạn là người mới bắt đầu, là người mới bắt đầu, bạn đánh giá cao sự đơn giản và các quy tắc đen trắng. Tuy nhiên, là người mới bắt đầu, bạn chỉ có một cái nhìn lén về phạm vi các vấn đề và ràng buộc phải được cung cấp bởi các chương trình máy tính. Bạn đang bước vào một công nghệ đã được phát triển tích cực trong 75 năm. Không có gì sai khi hỏi tại sao mọi thứ lại như vậy, nhưng câu trả lời thường là "Vâng, chúng tôi đã thử phương pháp đơn giản, đơn giản 50 năm trước, và hóa ra nó không hoạt động tốt cho toàn bộ các lớp vấn đề, vì vậy chúng tôi phải làm một cái gì đó phức tạp hơn ". Khi một công nghệ tiến bộ, sự đơn giản thường phải nhường chỗ cho hiệu quả và tính linh hoạt.


0

Cho một ví dụ khác, đóng cửa. Nếu bạn xếp chồng pop, thì bạn có thể mất hồ sơ kích hoạt đóng cửa của mình. Vì vậy, nếu bạn muốn chức năng ẩn danh đó và dữ liệu của nó tồn tại, bạn phải lưu trữ nó ở một nơi khác ngoài ngăn xếp thời gian chạy.


0

Trong số nhiều lý do khác để phân bổ lưu trữ heap.

Nếu bạn muốn chuyển địa chỉ của một đối tượng trở lại chương trình gọi, bạn không nên chuyển địa chỉ của biến ngăn xếp, vì, bộ lưu trữ ngăn xếp sẽ được sử dụng lại và có thể được ghi đè bởi hàm tiếp theo được gọi. Bạn cần nhận được dung lượng lưu trữ cần thiết bằng cách sử dụng malloc () để đảm bảo nó sẽ không bị ghi đè bởi các lệnh gọi đến các hàm tiếp theo.

Bạn có thể chuyển địa chỉ của các mục ngăn xếp từ chức năng của mình sang các chức năng bạn gọi, bởi vì bạn có thể đảm bảo chúng sẽ tồn tại cho đến khi chương trình của bạn "return ()". Nhưng ngay sau khi chức năng của bạn trả về tất cả các lưu trữ ngăn xếp đã sẵn sàng để lấy.

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.