Tại sao kích thước ngăn xếp trong C # chính xác là 1 MB?


102

PC ngày nay có dung lượng RAM vật lý lớn nhưng kích thước ngăn xếp của C # chỉ là 1 MB đối với quy trình 32 bit và 4 MB đối với quy trình 64 bit ( Dung lượng ngăn xếp trong C # ).

Tại sao kích thước ngăn xếp trong CLR vẫn rất hạn chế?

Và tại sao nó lại chính xác là 1 MB (4 MB) (chứ không phải 2 MB hoặc 512 KB)? Tại sao nó lại quyết định sử dụng những số tiền này?

Tôi quan tâm đến những cân nhắc và lý do đằng sau quyết định đó .


6
Kích thước ngăn xếp mặc định cho các quy trình 64 bit là 4MB, nó là 1MB cho các quy trình 32 bit. Bạn có thể sửa đổi kích thước ngăn xếp luồng chính bằng cách thay đổi giá trị trong tiêu đề PE của nó. Bạn cũng có thể chỉ định kích thước ngăn xếp bằng cách sử dụng quá tải bên phải của hàm Threadtạo. NHƯNG, điều này đặt ra câu hỏi, tại sao bạn cần một ngăn xếp lớn hơn?
Yuval Itzchakov

2
Cảm ơn, đã chỉnh sửa. :) Câu hỏi không phải là về cách sử dụng kích thước ngăn xếp lớn hơn, mà tại sao kích thước ngăn xếp được quyết định là 1 MB (4 MB) .
Nikolay Kostov

8
Bởi vì mỗi luồng sẽ nhận được kích thước ngăn xếp này theo mặc định, và hầu hết các luồng không cần nhiều như vậy. Tôi vừa khởi động PC của mình và hệ thống hiện đang chạy 1200 luồng. Bây giờ hãy làm toán;)
Lucas Trzesniewski

2
@LucasTrzesniewski Không chỉ vậy, nó phải dễ lây lan trong trí nhớ . Lưu ý rằng kích thước của ngăn xếp càng lớn, thì quy trình của bạn có thể tạo ra ít luồng hơn trong không gian địa chỉ ảo của nó.
Yuval Itzchakov

Không chắc chắn về "chính xác" 1 MB: trên Windows 8.1 của tôi, ứng dụng bảng điều khiển .NET Core 3.1 có 1572864kích thước ngăn xếp mặc định là byte (Được truy xuất bằng API GetCurrentThreadStackLimits Win32). Tôi có thể stackallocxấp xỉ 1500000byte mà không có StackOverflowException.
George Chakhidze

Câu trả lời:


210

nhập mô tả hình ảnh ở đây

Bạn đang nhìn vào anh chàng đã đưa ra lựa chọn đó. David Cutler và nhóm của ông đã chọn một megabyte làm kích thước ngăn xếp mặc định. Không có gì để làm với .NET hoặc C #, điều này đã được đóng đinh khi họ tạo Windows NT. Một megabyte là những gì nó chọn khi tiêu đề EXE của một chương trình hoặc lệnh gọi CreateThread () winapi không chỉ định kích thước ngăn xếp một cách rõ ràng. Đó là cách bình thường, hầu như bất kỳ lập trình viên nào để hệ điều hành chọn kích thước.

Sự lựa chọn đó có lẽ đã có từ trước khi thiết kế Windows NT, lịch sử quá mờ mịt về điều này. Sẽ rất tuyệt nếu Cutler viết một cuốn sách về nó, nhưng anh ấy chưa bao giờ là một nhà văn. Anh ấy có ảnh hưởng cực kỳ lớn đến cách máy tính hoạt động. Thiết kế hệ điều hành đầu tiên của ông là RSX-11M, một hệ điều hành 16 bit dành cho máy tính DEC (Digital Equipment Corporation). Nó ảnh hưởng nặng nề đến CP / M của Gary Kildall, hệ điều hành tốt đầu tiên dành cho vi xử lý 8-bit. Ảnh hưởng nặng nề đến MS-DOS.

Thiết kế tiếp theo của ông là VMS, một hệ điều hành dành cho bộ vi xử lý 32-bit có hỗ trợ bộ nhớ ảo. Rất thành công. Chiếc tiếp theo của anh đã bị DEC hủy bỏ vào khoảng thời gian công ty bắt đầu tan rã, không thể cạnh tranh với phần cứng PC giá rẻ. Cue Microsoft, họ đã đưa cho anh một lời đề nghị mà anh không thể từ chối. Nhiều đồng nghiệp của anh ấy cũng tham gia. Họ đã làm việc trên VMS v2, hay còn gọi là Windows NT. DEC đã thất vọng về điều đó, tiền đã được thay thế để giải quyết nó. Tôi không biết VMS đã chọn một megabyte hay chưa, tôi chỉ biết RSX-11 là đủ. Nó không khó.

Lịch sử đủ rồi. Một megabyte là rất nhiều , một luồng thực hiếm khi tiêu thụ nhiều hơn một vài kilobyte. Vì vậy, một megabyte thực sự khá lãng phí. Tuy nhiên, đó là loại lãng phí bạn có thể mua được trên hệ điều hành bộ nhớ ảo phân trang theo yêu cầu, megabyte chỉ là bộ nhớ ảo . Chỉ số cho bộ xử lý, mỗi số cho mỗi 4096 byte. Bạn không bao giờ thực sự sử dụng bộ nhớ vật lý, RAM trong máy, cho đến khi bạn thực sự xử lý nó.

Nó quá thừa trong chương trình .NET vì kích thước một megabyte ban đầu được chọn để chứa các chương trình gốc. Có xu hướng tạo các khung ngăn xếp lớn, lưu trữ các chuỗi và bộ đệm (mảng) trên ngăn xếp. Nổi tiếng là vectơ tấn công phần mềm độc hại, lỗi tràn bộ đệm có thể thao túng chương trình bằng dữ liệu. Không phải cách các chương trình .NET hoạt động, các chuỗi và mảng được phân bổ trên GC heap và việc lập chỉ mục được kiểm tra. Cách duy nhất để phân bổ không gian trên ngăn xếp với C # là sử dụng từ khóa stackalloc không an toàn .

Cách sử dụng không tầm thường duy nhất của ngăn xếp trong .NET là do jitter. Nó sử dụng ngăn xếp luồng của bạn để biên dịch MSIL thành mã máy trong thời gian ngắn. Tôi chưa bao giờ thấy hoặc kiểm tra xem nó yêu cầu bao nhiêu dung lượng, nó phụ thuộc vào bản chất của mã và liệu trình tối ưu hóa có được bật hay không, nhưng vài chục kilobyte là một phỏng đoán sơ bộ. Đó là cách khác mà trang web này có tên như vậy, tràn ngăn xếp trong chương trình .NET là khá nghiêm trọng. Không còn đủ dung lượng (ít hơn 3 kilobyte) để vẫn JIT một cách đáng tin cậy bất kỳ mã nào cố gắng bắt ngoại lệ. Kaboom cho máy tính để bàn là lựa chọn duy nhất.

Cuối cùng nhưng không kém phần quan trọng, một chương trình .NET thực hiện một điều gì đó khá kém hiệu quả với ngăn xếp. CLR sẽ cam kết ngăn xếp của một luồng. Đó là một từ đắt có nghĩa là nó không chỉ bảo lưu kích thước của ngăn xếp mà còn đảm bảo rằng không gian được dành riêng trong tệp hoán trang của hệ điều hành để ngăn xếp luôn có thể được hoán đổi khi cần thiết. Không thực hiện là một lỗi nghiêm trọng và chấm dứt chương trình vô điều kiện. Điều đó chỉ xảy ra trên máy có rất ít RAM chạy hoàn toàn quá nhiều quy trình, một máy như vậy sẽ chuyển sang trạng thái nóng trước khi các chương trình bắt đầu chết. Một vấn đề có thể xảy ra cách đây hơn 15 năm, không phải ngày nay. Các lập trình viên điều chỉnh chương trình của họ để hoạt động giống như một chiếc xe đua F1 sử dụng <disableCommitThreadStack>phần tử trong tệp .config của họ.

Fwiw, Cutler không ngừng thiết kế hệ điều hành. Bức ảnh đó được thực hiện khi anh ấy làm việc trên Azure.


Cập nhật, tôi nhận thấy rằng .NET không còn cam kết ngăn xếp. Không chắc chính xác khi nào hoặc tại sao điều này xảy ra, đã quá lâu tôi không kiểm tra. Tôi đoán rằng thay đổi thiết kế này đã xảy ra ở đâu đó xung quanh .NET 4.5. Thay đổi khá hợp lý.


3
wrt đến nhận xét của bạn The only way to allocate space on the stack with C# is with the unsafe stackalloc keyword.- Các biến cục bộ, ví dụ như intmột phương thức được khai báo bên trong một phương thức không được lưu trữ trên ngăn xếp? Tôi nghĩ rằng họ là.
RBT

2
Đồng ý. Bây giờ tôi hiểu rằng một khung ngăn xếp không phải là lựa chọn lưu trữ duy nhất cho các biến cục bộ của một hàm. Nó có thể được lưu trữ trên một khung ngăn xếp mặc dù như được đề xuất trong một trong các dấu đầu dòng của bạn. Hans rất ngộ. Tôi không thể nói đủ cảm ơn bạn đã viết những bài viết sâu sắc như vậy. Thành thật mà nói ngăn xếp là một sự trừu tượng lớn đối với lập trình nói chung chỉ để tránh sự phức tạp không cần thiết.
RBT

Mô tả rất chi tiết @Hans. Tôi chỉ tự hỏi giá trị tối thiểu có thể có maxStackSizecủa một luồng là bao nhiêu? Tôi không thể tìm thấy nó trên [MSDN] ( msdn.microsoft.com/en-us/library/5cykbwz4(v=vs.110).aspx ). Dựa trên nhận xét của bạn, có vẻ như việc sử dụng ngăn xếp là tối thiểu tuyệt đối và tôi có thể sử dụng giá trị nhỏ nhất để chứa các chủ đề tối đa có thể. Cảm ơn.
MKR

1
@KFL: Bạn có thể trả lời câu hỏi của mình một cách dễ dàng bằng cách thử nó!
Eric Lippert

1
Nếu hành vi mặc định là không còn cam kết ngăn xếp, thì tệp đánh dấu này cần được chỉnh sửa github.com/dotnet/docs/blob/master/docs/framework/…
John Stewien

5

Kích thước ngăn xếp dành riêng mặc định được chỉ định bởi trình liên kết và nó có thể bị ghi đè bởi các nhà phát triển thông qua việc thay đổi giá trị PE tại thời điểm liên kết hoặc cho một luồng riêng lẻ bằng cách chỉ định dwStackSizetham số cho CreateThreadhàm WinAPI.

Nếu bạn tạo một luồng với kích thước ngăn xếp ban đầu lớn hơn hoặc bằng kích thước ngăn xếp mặc định thì nó làm tròn lên bội số gần nhất của 1 MB.

Tại sao giá trị bằng 1 MB cho các quy trình 32 bit và 4 MB cho 64 bit? Tôi nghĩ bạn nên hỏi các nhà phát triển, những người đã thiết kế Windows, hoặc đợi cho đến khi ai đó trong số họ trả lời câu hỏi của bạn.

Có lẽ Mark Russinovich biết điều đó và bạn có thể liên hệ với anh ấy. Có thể bạn có thể tìm thấy thông tin này trong sách Windows Internals của anh ấy trước ấn bản thứ sáu, trong đó mô tả ít thông tin hơn về ngăn xếp hơn là bài báo của anh ấy . Hoặc có thể Raymond Chen biết lý do vì anh ấy viết những điều thú vị về nội bộ Windows và lịch sử của nó. Anh ấy cũng có thể trả lời câu hỏi của bạn, nhưng bạn nên đăng gợi ý vào Hộp gợi ý .

Nhưng tại thời điểm này, tôi sẽ cố gắng giải thích một số lý do có thể xảy ra tại sao Microsoft lại chọn các giá trị này bằng cách sử dụng blog của MSDN, Mark và Raymond.

Giá trị mặc định có những giá trị này có thể là do trong thời gian đầu, PC chạy chậm và việc phân bổ bộ nhớ trên ngăn xếp nhanh hơn nhiều so với phân bổ bộ nhớ trong heap. Và vì phân bổ ngăn xếp rẻ hơn nhiều nên chúng đã được sử dụng, nhưng nó yêu cầu kích thước ngăn xếp lớn hơn.

Vì vậy, giá trị là kích thước ngăn xếp dự trữ tối ưu cho hầu hết các ứng dụng. Nó tối ưu vì cho phép thực hiện nhiều lệnh gọi lồng nhau và cấp phát bộ nhớ trên ngăn xếp để chuyển cấu trúc cho các hàm gọi. Đồng thời nó cho phép tạo ra rất nhiều đề.

Ngày nay, các giá trị này chủ yếu được sử dụng để tương thích ngược, bởi vì các cấu trúc được chuyển làm tham số cho các hàm WinAPI vẫn được cấp phát trên ngăn xếp. Nhưng nếu bạn không sử dụng phân bổ ngăn xếp thì việc sử dụng ngăn xếp của một luồng sẽ ít hơn đáng kể so với 1 MB mặc định và điều đó thật lãng phí như Hans Passant đã đề cập. Và để ngăn chặn điều này, Hệ điều hành chỉ cam kết trang đầu tiên của ngăn xếp (4 KB), nếu trang khác không được chỉ định trong tiêu đề PE của ứng dụng. Các trang khác được phân bổ theo yêu cầu.

Một số ứng dụng ghi đè không gian địa chỉ dành riêng và ban đầu cam kết tối ưu hóa việc sử dụng bộ nhớ. Ví dụ: kích thước ngăn xếp tối đa của luồng quy trình gốc IIS là 256 KB ( KB932909 ). Và việc giảm giá trị mặc định này được khuyến nghị bởi Microsoft:

Tốt nhất là chọn kích thước ngăn xếp càng nhỏ càng tốt và cam kết ngăn xếp cần thiết để luồng hoặc sợi chạy đáng tin cậy. Mọi trang được dành riêng cho ngăn xếp không thể được sử dụng cho bất kỳ mục đích nào khác.

Nguồn:

  1. Kích thước ngăn xếp chuỗi (Microsoft Docs)
  2. Đẩy giới hạn của Windows: Quy trình và Chủ đề (Mark Russinovich)
  3. Theo mặc định, kích thước ngăn xếp tối đa của một luồng được tạo trong quy trình IIS gốc là 256 KB (KB932909)

Nếu tôi muốn kích thước ngăn xếp lớn hơn, tôi có thể đặt nó ( atalasoft.com/cs/blogs/rickm/archive/2008/04/22/… ). Tôi muốn biết những cân nhắc và lý do đằng sau quyết định đó.
Nikolay Kostov

2
Được chứ. Bây giờ tôi hiểu bạn :) Kích thước ngăn xếp mặc định phải tối ưu (xem nhận xét của @Lucas Trzesniewski) và phải được làm tròn đến bội số gần nhất của mức độ chi tiết phân bổ. Nếu kích thước ngăn xếp được chỉ định lớn hơn kích thước ngăn xếp mặc định thì nó làm tròn thành bội số gần nhất của 1MB. Vì vậy, Microsoft đã chọn kích thước này làm kích thước ngăn xếp mặc định cho tất cả các ứng dụng chế độ người dùng. Và không có lý do nào khác.
Yoh Deadfall

Bất kỳ nguồn nào? Bất kỳ tài liệu? :)
Nikolay Kostov

@Yoh liên kết thú vị. Bạn nên tóm tắt điều đó vào câu trả lời của mình.
Lucas Trzesniewski
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.