tại sao kích thước bộ nhớ ngăn xếp rất hạn chế?


81

Khi bạn phân bổ bộ nhớ trên heap, giới hạn duy nhất là RAM trống (hoặc bộ nhớ ảo). Nó làm cho Gb của bộ nhớ.

Vậy tại sao kích thước ngăn xếp lại bị giới hạn (khoảng 1 Mb)? Lý do kỹ thuật nào ngăn cản bạn tạo các đối tượng thực sự lớn trên ngăn xếp?

Cập nhật : Ý định của tôi có thể không rõ ràng, tôi không muốn phân bổ các đối tượng lớn trên ngăn xếp và tôi không cần một ngăn xếp lớn hơn. Câu hỏi này chỉ là sự tò mò thuần túy.


Tại sao việc tạo các đối tượng lớn trên heap sẽ là thực tế? (Chuỗi Gọi thường đi trên stack.)
Makoto

4
Tôi nghĩ câu trả lời thực sự đơn giản hơn hầu hết các câu trả lời miêu tả: "bởi vì đó là cách chúng tôi đã luôn làm và mọi chuyện vẫn ổn cho đến nay, tại sao lại thay đổi?"
Jerry Coffin

@JerryCoffin Bạn đã đọc bất kỳ câu trả lời nào được đăng cho đến nay chưa? Có cái nhìn sâu sắc hơn về câu hỏi này.
user1202136

3
@ user1202136: Tôi đã đọc tất cả chúng - nhưng mọi người đang đoán, và tôi đoán là nhiều yếu tố mà họ trích dẫn có lẽ thậm chí không được xem xét khi đưa ra quyết định ban đầu về chủ đề này. Có một cụm từ, "đôi khi một điếu xì gà chỉ là một điếu xì gà."
Jerry Coffin

3
"Chúng ta nên đặt ngăn xếp mặc định lớn bao nhiêu?" "Ồ, tôi không biết, chúng ta có thể chạy bao nhiêu chủ đề?" "Nó nổ tung ở đâu đó trên K" "Được rồi, chúng ta sẽ gọi nó là 2K, chúng ta có 2 Gig ảo, vậy còn 1 meg thì sao?" "Ừ, OK, vấn đề tiếp theo là gì?"
Martin James

Câu trả lời:


45

Trực giác của tôi là như sau. Ngăn xếp không dễ quản lý như đống. Ngăn xếp cần được lưu trữ trong các vị trí bộ nhớ liên tục. Điều này có nghĩa là bạn không thể phân bổ ngẫu nhiên ngăn xếp khi cần thiết, nhưng ít nhất bạn cần dành các địa chỉ ảo cho mục đích đó. Kích thước của không gian địa chỉ ảo dành riêng càng lớn, bạn có thể tạo càng ít luồng.

Ví dụ, một ứng dụng 32-bit thường có không gian địa chỉ ảo là 2GB. Điều này có nghĩa là nếu kích thước ngăn xếp là 2MB (như mặc định trong pthreads), thì bạn có thể tạo tối đa 1024 luồng. Điều này có thể nhỏ đối với các ứng dụng như máy chủ web. Tăng kích thước ngăn xếp lên, chẳng hạn, 100MB (tức là bạn dự trữ 100MB, nhưng không nhất thiết phải phân bổ 100MB cho ngăn xếp ngay lập tức), sẽ giới hạn số luồng xuống khoảng 20, điều này có thể hạn chế ngay cả đối với các ứng dụng GUI đơn giản.

Một câu hỏi thú vị là, tại sao chúng ta vẫn có giới hạn này trên nền tảng 64-bit. Tôi không biết câu trả lời, nhưng tôi cho rằng mọi người đã quen với một số "phương pháp hay nhất về ngăn xếp": hãy cẩn thận phân bổ các đối tượng khổng lồ trên heap và nếu cần, hãy tăng kích thước ngăn xếp theo cách thủ công. Do đó, không ai thấy hữu ích khi thêm hỗ trợ ngăn xếp "khổng lồ" trên nền tảng 64-bit.


Nhiều máy 64-bit chỉ có địa chỉ 48-bit (được cấp độ lợi lớn hơn 32-bit, nhưng vẫn còn hạn chế). Ngay cả khi có thêm không gian, bạn vẫn phải lo lắng về cách đặt chỗ đối với các bảng trang - nghĩa là, luôn có chi phí cao khi có thêm không gian. Việc phân bổ một phân đoạn mới (mmap) có lẽ chỉ rẻ, nếu không muốn nói là rẻ hơn thay vì dành không gian ngăn xếp khổng lồ cho mỗi luồng.
edA-qa mort-hay-y

4
@ EDA-qamort-ora-y: Câu trả lời này không nói về phân bổ , nó nói về reserveration ảo bộ nhớ , đó là gần như miễn phí, và chắc chắn nhiều nhanh hơn mmap.
Vịt kêu vào

33

Một khía cạnh mà chưa ai đề cập đến:

Kích thước ngăn xếp hạn chế là một cơ chế phát hiện và ngăn chặn lỗi.

Nói chung, công việc chính của ngăn xếp trong C và C ++ là theo dõi ngăn xếp cuộc gọi và các biến cục bộ, và nếu ngăn xếp phát triển vượt quá giới hạn, thì hầu như luôn luôn là lỗi trong thiết kế và / hoặc hành vi của ứng dụng. .

Nếu ngăn xếp được phép tăng lớn tùy ý, những lỗi này (như đệ quy vô hạn) sẽ bị bắt rất muộn, chỉ sau khi tài nguyên hệ điều hành cạn kiệt. Điều này được ngăn chặn bằng cách đặt giới hạn tùy ý cho kích thước ngăn xếp. Kích thước thực tế không quan trọng lắm, ngoài việc nó đủ nhỏ để ngăn chặn sự xuống cấp của hệ thống.


Bạn có thể gặp vấn đề tương tự với các đối tượng được cấp phát (vì một số cách để thay thế đệ quy là xử lý ngăn xếp theo cách thủ công). Hạn chế đó buộc phải sử dụng các cách khác (không cần thiết an toàn hơn / đơn giản hơn / ..) (Lưu ý số lượng nhận xét về việc triển khai danh sách (đồ chơi) std::unique_ptrđể viết hàm hủy (và không dựa vào con trỏ thông minh)).
Jarod42

15

Nó chỉ là một kích thước mặc định. Nếu bạn cần nhiều hơn, bạn có thể nhận được nhiều hơn - thường xuyên nhất bằng cách yêu cầu trình liên kết phân bổ thêm không gian ngăn xếp.

Nhược điểm của việc có nhiều ngăn xếp là nếu bạn tạo nhiều luồng, mỗi luồng sẽ cần một ngăn xếp. Nếu tất cả các ngăn xếp đang phân bổ nhiều MB, nhưng không sử dụng nó, không gian sẽ bị lãng phí.

Bạn phải tìm sự cân bằng thích hợp cho chương trình của mình.


Một số người, như @BJovke, tin rằng bộ nhớ ảo về cơ bản là miễn phí. Đúng là bạn không cần phải có bộ nhớ vật lý hỗ trợ tất cả bộ nhớ ảo. Ít nhất bạn phải có khả năng cung cấp địa chỉ cho bộ nhớ ảo.

Tuy nhiên, trên một PC 32 bit điển hình, kích thước của bộ nhớ ảo cũng giống như kích thước của bộ nhớ vật lý - bởi vì chúng ta chỉ có 32 bit cho bất kỳ địa chỉ nào, dù là ảo hay không.

Bởi vì tất cả các luồng trong một tiến trình chia sẻ cùng một không gian địa chỉ, chúng phải phân chia nó giữa chúng. Và sau khi hệ điều hành thực hiện xong, chỉ còn lại 2-3 GB cho một ứng dụng. Và kích thước đó là giới hạn cho cả bộ nhớ vật lý bộ nhớ ảo, bởi vì không còn địa chỉ nào nữa.


Vấn đề lớn nhất của luồng là bạn không thể dễ dàng báo hiệu các đối tượng ngăn xếp cho các luồng khác. Chuỗi nhà sản xuất phải đợi đồng bộ chuỗi người tiêu dùng giải phóng đối tượng hoặc các bản sao sâu đắt tiền và gây tranh cãi phải được thực hiện.
Martin James

2
@MartinJames: Không ai nói rằng tất cả các đối tượng phải nằm trong ngăn xếp, chúng tôi đang thảo luận tại sao kích thước ngăn xếp mặc định lại nhỏ.
Vịt kêu vào

Không gian sẽ không bị lãng phí, kích thước ngăn xếp chỉ là sự đặt trước của không gian địa chỉ ảo liên tục. Vì vậy, nếu bạn đặt kích thước ngăn xếp là 100 MB, dung lượng RAM sẽ thực sự được sử dụng tùy thuộc vào mức tiêu thụ ngăn xếp trong luồng.
BJovke

1
@BJovke - Nhưng không gian địa chỉ ảo sẽ vẫn được sử dụng hết. Trong quy trình 32-bit, điều này được giới hạn ở một vài GB, vì vậy chỉ cần lưu trữ 20 * 100MB sẽ gây ra sự cố cho bạn.
Bo Persson

7

Thứ nhất, ngăn xếp là liên tục, vì vậy nếu bạn phân bổ 12MB, bạn phải xóa 12MB khi muốn xuống dưới bất kỳ thứ gì bạn đã tạo. Ngoài ra, việc di chuyển các đối tượng xung quanh trở nên khó khăn hơn nhiều. Đây là một ví dụ thực tế có thể giúp mọi thứ dễ hiểu hơn:

Giả sử bạn đang xếp các hộp xung quanh phòng. Cái nào dễ quản lý hơn:

  • chồng các hộp có trọng lượng bất kỳ lên nhau, nhưng khi bạn cần lấy thứ gì đó ở phía dưới, bạn phải hoàn tác toàn bộ đống của mình. Nếu bạn muốn lấy một vật ra khỏi đống và đưa cho người khác, bạn phải tháo tất cả các hộp và chuyển hộp sang chồng của người khác (Chỉ xếp chồng lên nhau)
  • Bạn đặt tất cả các hộp của mình (ngoại trừ các hộp thực sự nhỏ) ở một khu vực đặc biệt, nơi bạn không xếp chồng lên các thứ khác và viết ra nơi bạn đặt nó trên một tờ giấy (một con trỏ) và đặt giấy lên Đống. Nếu bạn cần đưa chiếc hộp cho người khác, bạn chỉ cần đưa cho họ tờ giấy từ đống giấy của bạn, hoặc chỉ cần đưa cho họ một bản sao của tờ giấy và để nguyên bản chính nó trong đống giấy của bạn. (Ngăn xếp + đống)

Hai ví dụ đó là những khái quát tổng quát và có một số điểm sai một cách trắng trợn trong phép loại suy nhưng nó đủ gần để hy vọng nó sẽ giúp bạn thấy được lợi thế trong cả hai trường hợp.


@MooingDuck Có, nhưng bạn đang làm việc trong bộ nhớ ảo trong chương trình của mình, Nếu tôi nhập một chương trình con, hãy đặt một cái gì đó vào ngăn xếp, sau đó quay lại từ chương trình con, tôi sẽ cần phải khử cấp phát hoặc di chuyển đối tượng tôi đã tạo trước khi tôi có thể thư giãn ngăn xếp để quay trở lại nơi tôi xuất phát.
Scott Chamberlain

1
mặc dù nhận xét của tôi là do hiểu sai (và tôi đã xóa nó), tôi vẫn không đồng ý với câu trả lời này. Loại bỏ 12MB khỏi đầu ngăn xếp đúng là một opcode. Về cơ bản nó miễn phí. Ngoài ra, các trình biên dịch có thể và thực hiện ăn gian quy tắc "ngăn xếp", vì vậy không, họ không phải sao chép / di chuyển đối tượng trước khi giải nén để trả lại nó. Vì vậy, tôi nghĩ rằng nhận xét của bạn cũng không chính xác.
Vịt kêu vào

Chà, việc phân bổ 12MB thường không quan trọng lắm khi phân bổ 12MB lấy một opcode trên ngăn xếp hơn 100 trên heap - nó có thể dưới mức nhiễu khi thực sự xử lý bộ đệm 12MB. Nếu các trình biên dịch muốn gian lận khi họ nhận thấy rằng một đối tượng cực kỳ lớn đang được trả về, (ví dụ: bằng cách di chuyển SP trước lệnh gọi để biến đối tượng trở thành một phần của ngăn xếp các trình gọi), thì tốt thôi, TBH, các nhà phát triển trả về các đối tượng, (chứ không phải là con trỏ / tham chiếu), có phần khó lập trình.
Martin James

@MartinJames: Thông số C ++ cũng nói rằng hàm thường có thể đặt dữ liệu trực tiếp vào bộ đệm đích và không sử dụng tạm thời, vì vậy nếu bạn cẩn thận, không có chi phí nào để trả về bộ đệm 12MB theo giá trị.
Vịt kêu vào

3

Hãy nghĩ về ngăn xếp theo thứ tự từ gần đến xa. Các thanh ghi ở gần CPU (nhanh), ngăn xếp xa hơn một chút (nhưng vẫn tương đối gần) và đống ở xa (truy cập chậm).

Tất nhiên, ngăn xếp tồn tại trên heap, nhưng vẫn còn, vì nó được sử dụng liên tục, nó có thể không bao giờ rời khỏi (các) bộ nhớ cache của CPU, làm cho nó nhanh hơn so với chỉ truy cập heap thông thường. Đây là một lý do để giữ cho ngăn xếp có kích thước hợp lý; để giữ nó được lưu trong bộ nhớ cache nhiều nhất có thể. Phân bổ các đối tượng ngăn xếp lớn (có thể tự động thay đổi kích thước ngăn xếp khi bạn bị tràn) đi ngược lại nguyên tắc này.

Vì vậy, đó là một mô hình tốt cho hiệu suất, không chỉ là một thứ còn sót lại từ thời xưa.


4
Mặc dù tôi tin rằng bộ nhớ đệm đóng một vai trò lớn trong lý do giảm kích thước ngăn xếp một cách giả tạo, tôi phải sửa bạn về câu lệnh "ngăn xếp tồn tại trên heap". Cả ngăn xếp và đống đều tồn tại trong bộ nhớ (thực tế hoặc vật lý).
Sebastian Hojas

"Gần hay xa" liên quan đến tốc độ truy cập như thế nào?
Minh Nghĩa

@ MinhNghĩa Chà, các biến trong RAM được lưu trong bộ nhớ L2, sau đó được lưu vào bộ nhớ L1, và sau đó thậm chí các biến đó sẽ được lưu vào bộ nhớ trong thanh ghi. Truy cập vào RAM chậm, đến L2 nhanh hơn, L1 vẫn nhanh hơn và đăng ký nhanh nhất. Ý tôi nghĩ OP là các biến được lưu trữ trong ngăn xếp phải được truy cập nhanh chóng, vì vậy CPU sẽ cố gắng hết sức để giữ các biến ngăn xếp gần với nó, do đó bạn muốn làm cho nó nhỏ lại, do đó CPU có thể truy cập các biến nhanh hơn.
157 239

1

Nhiều thứ bạn nghĩ rằng bạn cần một ngăn xếp lớn, có thể được thực hiện theo cách khác.

"Thuật toán" của Sedgewick có một vài ví dụ điển hình về việc "loại bỏ" đệ quy khỏi các thuật toán đệ quy như QuickSort, bằng cách thay thế đệ quy bằng phép lặp. Trong thực tế, thuật toán vẫn là đệ quy, và vẫn có ngăn xếp, nhưng bạn phân bổ ngăn xếp sắp xếp trên heap, thay vì sử dụng ngăn xếp thời gian chạy.

(Tôi thích ấn bản thứ hai, với các thuật toán được đưa ra trong Pascal. Nó có thể được sử dụng với giá tám đô la.)

Một cách khác để xem xét nó, là nếu bạn nghĩ rằng bạn cần một ngăn xếp lớn, thì mã của bạn không hiệu quả. Có một cách tốt hơn là sử dụng ít ngăn xếp hơn.


-8

Tôi không nghĩ rằng có bất kỳ lý do kỹ thuật nào, nhưng nó sẽ là một ứng dụng kỳ lạ chỉ tạo ra một siêu vật thể khổng lồ trên ngăn xếp. Các đối tượng ngăn xếp thiếu tính linh hoạt trở nên khó khăn hơn khi tăng kích thước - bạn không thể quay lại mà không hủy chúng và bạn không thể xếp chúng vào các luồng khác.


1
Không ai nói rằng tất cả các đối tượng phải ở trên ngăn xếp, chúng tôi đang thảo luận tại sao kích thước ngăn xếp mặc định lại nhỏ.
Vịt kêu vào

Nó không phải là nhỏ! Bạn sẽ phải thực hiện bao nhiêu lệnh gọi hàm để sử dụng hết 1MB ngăn xếp? Mặc định dù sao cũng có thể dễ dàng thay đổi trong trình liên kết và vì vậy, chúng ta còn lại với câu hỏi 'tại sao lại sử dụng ngăn xếp thay vì đống?'
Martin James

3
một cuộc gọi hàm. int main() { char buffer[1048576]; } Đó là một vấn đề rất phổ biến của người mới. Chắc chắn có một cách giải quyết dễ dàng, nhưng tại sao chúng ta phải giải quyết kích thước ngăn xếp?
Vịt kêu vào

Vâng, có một điều, tôi sẽ không muốn yêu cầu ngăn xếp 12MB (hoặc thực sự là 1MB) gây ra trên ngăn xếp của mọi luồng gọi hàm bị ảnh hưởng. Điều đó nói rằng, tôi phải đồng ý rằng 1MB là một chút keo kiệt. Tôi sẽ hài lòng với 100MB mặc định, sau tất cả, không có gì ngăn cản tôi giảm nó xuống 128K giống như cách mà không có gì ngăn cản các nhà phát triển khác tăng nó lên.
Martin James

1
Tại sao bạn không muốn tạo ra 12MB ngăn xếp trên chuỗi của mình? Lý do duy nhất cho điều đó là vì ngăn xếp nhỏ. Đó là một đối số đệ quy.
Vịt kêu vào
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.