Làm thế nào để tăng kích thước ngăn xếp Java?


123

Tôi đặt câu hỏi này để biết cách tăng kích thước ngăn xếp cuộc gọi thời gian chạy trong JVM. Tôi đã có câu trả lời cho điều này và tôi cũng có nhiều câu trả lời và nhận xét hữu ích liên quan đến cách Java xử lý tình huống cần một ngăn xếp thời gian chạy lớn. Tôi đã mở rộng câu hỏi của mình với bản tóm tắt các câu trả lời.

Ban đầu tôi muốn tăng kích thước ngăn xếp JVM để các chương trình như chạy mà không có a StackOverflowError.

public class TT {
  public static long fact(int n) {
    return n < 2 ? 1 : n * fact(n - 1);
  }
  public static void main(String[] args) {
    System.out.println(fact(1 << 15));
  }
}

Cài đặt cấu hình tương ứng là java -Xss...cờ dòng lệnh có giá trị đủ lớn. Đối với chương trình TTtrên, nó hoạt động như thế này với JVM của OpenJDK:

$ javac TT.java
$ java -Xss4m TT

Một trong những câu trả lời cũng đã chỉ ra rằng các -X...cờ phụ thuộc vào việc triển khai. tôi đang sử dụng

java version "1.6.0_18"
OpenJDK Runtime Environment (IcedTea6 1.8.1) (6b18-1.8.1-0ubuntu1~8.04.3)
OpenJDK 64-Bit Server VM (build 16.0-b13, mixed mode)

Cũng có thể chỉ định một ngăn xếp lớn chỉ cho một luồng (xem một trong các câu trả lời như thế nào). Điều này được khuyến khích hơn java -Xss...để tránh lãng phí bộ nhớ cho các luồng không cần nó.

Tôi tò mò muốn biết chương trình phía trên cần một ngăn xếp lớn như thế nào, vì vậy tôi đã chạy nó ntăng lên:

  • -Xss4m có thể đủ cho fact(1 << 15)
  • -Xss5m có thể đủ cho fact(1 << 17)
  • -Xss7m có thể đủ cho fact(1 << 18)
  • -Xss9m có thể đủ cho fact(1 << 19)
  • -Xss18m có thể đủ cho fact(1 << 20)
  • -Xss35m có thể đủ cho fact(1 << 21)
  • -Xss68m có thể đủ cho fact(1 << 22)
  • -Xss129m có thể đủ cho fact(1 << 23)
  • -Xss258m có thể đủ cho fact(1 << 24)
  • -Xss515m có thể đủ cho fact(1 << 25)

Từ những con số trên, có vẻ như Java đang sử dụng khoảng 16 byte trên mỗi khung ngăn xếp cho hàm trên, điều này là hợp lý.

Việc liệt kê ở trên có thể là đủ thay vì là đủ , bởi vì yêu cầu ngăn xếp là không xác định: chạy nó nhiều lần với cùng một tệp nguồn và giống nhau -Xss...đôi khi thành công và đôi khi mang lại a StackOverflowError. Ví dụ: 1 << 20, -Xss18mlà đủ trong 7 trong tổng số 10, và -Xss19mkhông phải lúc nào cũng đủ, nhưng -Xss20mlà đủ (trong 100 lần hết 100). Có phải việc thu gom rác, JIT đang hoạt động hay thứ gì khác gây ra hành vi không xác định này không?

Dấu vết ngăn xếp được in tại a StackOverflowError(và có thể cả các trường hợp ngoại lệ khác) chỉ hiển thị 1024 phần tử gần đây nhất của ngăn xếp thời gian chạy. Một câu trả lời dưới đây minh họa cách đếm độ sâu chính xác đạt được (có thể lớn hơn rất nhiều so với 1024).

Nhiều người đã phản hồi đã chỉ ra rằng đó là một phương pháp mã hóa tốt và an toàn khi xem xét các triển khai thay thế, ít tốn ngăn xếp hơn của cùng một thuật toán. Nói chung, có thể chuyển đổi thành một tập hợp các hàm đệ quy thành các hàm lặp (sử dụng một Stackđối tượng ví dụ , được điền trên heap thay vì trên ngăn xếp thời gian chạy). Đối với factchức năng cụ thể này , nó khá dễ dàng để chuyển đổi nó. Phiên bản lặp lại của tôi sẽ giống như sau:

public class TTIterative {
  public static long fact(int n) {
    if (n < 2) return 1;
    if (n > 65) return 0;  // Enough powers of 2 in the product to make it (long)0.
    long f = 2;
    for (int i = 3; i <= n; ++i) {
      f *= i;
    }
    return f;
  }
  public static void main(String[] args) {
    System.out.println(fact(1 << 15));
  }
}

FYI, như giải pháp lặp ở trên cho thấy nó, facthàm không thể tính giai thừa chính xác của các số trên 65 (thực tế, thậm chí trên 20), bởi vì kiểu tích hợp sẵn của Java longsẽ bị tràn. Cấu trúc lại factđể nó sẽ trả về một BigIntegerthay vì longcũng sẽ mang lại kết quả chính xác cho các đầu vào lớn.


Trông đơn giản hơn nó. fact () được gọi đệ quy 32K lần. Ngăn xếp đó phải nhỏ hơn 1MB. : - /
Aaron Digulla 13/09/10

@Aaron: + Chi phí chức năng, đó là .. rất RẤT
Halfdan

4
Ngoài các vấn đề về ngăn xếp của bạn. lưu ý rằng bạn đang tăng tốc độ dài và ints của mình. 1 << 4 là giá trị tối đa tôi có thể sử dụng trước khi chuyển sang số âm và sau đó thành 0. Hãy thử sử dụng BigInteger
Sean

Mặc dù vậy, không chắc chắn rằng chi phí chức năng thực sự bị hỏng nhiều-- Tôi nghĩ bạn vẫn có thể thực hiện 2 ^ 15 cuộc gọi theo thứ tự của một vài megabyte không gian ngăn xếp.
Neil Coffey 13:10

7
Lưu ý: Bạn đang đặt kích thước ngăn xếp của mọi luồng và tạo ra kết quả vô nghĩa, tất cả đều để tránh cấu trúc lại một dòng mã. Tôi rất vui vì bạn đã sắp xếp các ưu tiên của mình. : P
Peter Lawrey 17/09/10

Câu trả lời:


78

Hmm ... nó hoạt động với tôi và với ít hơn 999MB ngăn xếp:

> java -Xss4m Test
0

(Windows JDK 7, build 17.0-b05 client VM và Linux JDK 6 - thông tin phiên bản giống như bạn đã đăng)


1
rất có thể đó là bình luận của tôi, tôi đã xóa nó khi nhận ra điều tương tự như Neil đã đăng.
Sean

Nhờ câu hỏi này và câu trả lời của bạn, tôi đã hoàn thành nhiệm vụ của mình. Hàm DFS của tôi phải đệ quy trên đồ thị có ~ 10 ^ 5 đỉnh. Cuối cùng nó đã làm việc với -Xss129m: D
bholagabbar

11

Tôi giả sử bạn đã tính toán "độ sâu 1024" bằng các dòng lặp lại trong dấu vết ngăn xếp?

Rõ ràng, độ dài mảng theo dõi ngăn xếp trong Throwable dường như bị giới hạn ở 1024. Hãy thử chương trình sau:

public class Test {

    public static void main(String[] args) {

        try {
            System.out.println(fact(1 << 15));
        }
        catch (StackOverflowError e) {
            System.err.println("true recursion level was " + level);
            System.err.println("reported recursion level was " +
                               e.getStackTrace().length);
        }
    }

    private static int level = 0;
    public static long fact(int n) {
        level++;
        return n < 2 ? n : n * fact(n - 1);
    }
}

9

Nếu bạn muốn chơi với kích thước ngăn xếp luồng, bạn sẽ muốn xem tùy chọn -Xss trên Hotspot JVM. Nó có thể là một cái gì đó khác nhau trên máy ảo không Hotspot vì các tham số -X cho JVM là phân phối cụ thể, IIRC.

Trên Hotspot, điều này giống như java -Xss16Mnếu bạn muốn tạo kích thước 16 megs.

Nhập java -X -helpnếu bạn muốn xem tất cả các thông số JVM cụ thể của bản phân phối mà bạn có thể nhập vào. Tôi không chắc liệu điều này có hoạt động tương tự trên các JVM khác hay không, nhưng nó in ra tất cả các thông số cụ thể của Hotspot.

Đối với những gì nó đáng giá - tôi khuyên bạn nên hạn chế sử dụng các phương thức đệ quy trong Java. Nó không quá tuyệt vời trong việc tối ưu hóa chúng - đối với một JVM không hỗ trợ đệ quy đuôi (xem JVM có ngăn chặn tối ưu hóa lệnh gọi đuôi không? ). Hãy thử cấu trúc lại mã giai thừa của bạn ở trên để sử dụng vòng lặp while thay vì gọi phương thức đệ quy.


8

Cách duy nhất để kiểm soát kích thước của ngăn xếp trong quy trình là bắt đầu một ngăn xếp mới Thread. Nhưng bạn cũng có thể kiểm soát bằng cách tạo một quy trình Java con tự gọi với -Xsstham số.

public class TT {
    private static int level = 0;

    public static long fact(int n) {
        level++;
        return n < 2 ? n : n * fact(n - 1);
    }

    public static void main(String[] args) throws InterruptedException {
        Thread t = new Thread(null, null, "TT", 1000000) {
            @Override
            public void run() {
                try {
                    level = 0;
                    System.out.println(fact(1 << 15));
                } catch (StackOverflowError e) {
                    System.err.println("true recursion level was " + level);
                    System.err.println("reported recursion level was "
                            + e.getStackTrace().length);
                }
            }

        };
        t.start();
        t.join();
        try {
            level = 0;
            System.out.println(fact(1 << 15));
        } catch (StackOverflowError e) {
            System.err.println("true recursion level was " + level);
            System.err.println("reported recursion level was "
                    + e.getStackTrace().length);
        }
    }

}

Cảm ơn vì câu trả lời đầy đủ thông tin này, rất vui khi biết thêm về các tùy chọn java -Xss....
pts

1
Tôi rất hào hứng với điều này, nhưng sau đó khi đọc qua docs.oracle.com/javase/6/docs/api/java/lang/Thread.html#Thread - hàm tạo kích thước ngăn xếp - thì sự phấn khích đã biến mất.
kellogs

Tôi ngạc nhiên mà nền tảng là họ khi tài liệu chỉ nói - "Trên một số nền tảng"
Dennis C

3

Thêm tùy chọn này

--driver-java-options -Xss512m

với lệnh spark-submit của bạn sẽ khắc phục sự cố này.


2

Thật khó để đưa ra một giải pháp hợp lý vì bạn muốn tránh mọi cách tiếp cận lành mạnh. Cấu trúc lại một dòng mã là giải pháp khả thi.

Lưu ý: Sử dụng -Xss đặt kích thước ngăn xếp của mọi luồng và là một ý tưởng rất tồi.

Một cách tiếp cận khác là thao tác mã byte để thay đổi mã như sau;

public static long fact(int n) { 
    return n < 2 ? n : n > 127 ? 0 : n * fact(n - 1); 
}

đưa ra mọi câu trả lời cho n> 127 là 0. Điều này tránh thay đổi mã nguồn.


1
Cảm ơn bạn đã chỉ ra rằng việc đặt kích thước ngăn xếp cao sẽ lãng phí bộ nhớ cho các luồng không cần nó. Cũng cảm ơn bạn đã chỉ ra rằng facthàm trong câu hỏi có thể được cấu trúc lại để sử dụng ít không gian ngăn xếp hơn nhiều.
pts

1
@pts, cảm ơn của bạn được ghi nhận. Tôi nghĩ đây là một câu hỏi hợp lý với một trường hợp sử dụng phức tạp hơn nhiều, nhưng những trường hợp đó rất hiếm.
Peter Lawrey

0

Kỳ dị! Bạn đang nói rằng bạn muốn tạo một đệ quy có độ sâu 1 << 15 ??? !!!!

Tôi khuyên bạn KHÔNG nên thử nó. Kích thước của ngăn xếp sẽ là 2^15 * sizeof(stack-frame). Tôi không biết kích thước khung xếp chồng là bao nhiêu, nhưng 2 ^ 15 là 32,768. Khá nhiều ... Chà, nếu nó dừng lại ở 1024 (2 ^ 10), bạn sẽ phải làm cho nó lớn hơn 2 ^ 5 lần, tức là, lớn hơn 32 lần so với cài đặt thực tế của bạn.


0

Các áp phích khác đã chỉ ra cách tăng trí nhớ và bạn có thể ghi nhớ các cuộc gọi. Tôi đề nghị rằng đối với nhiều ứng dụng, bạn có thể sử dụng công thức của Stirling để tính gần đúng n lớn! rất nhanh chóng mà hầu như không có dấu chân bộ nhớ.

Hãy xem qua bài đăng này, có một số phân tích về chức năng và mã:

http://threebrothers.org/brendan/blog/stirlings-approximation-formula-clojure/


0

Tôi đã thực hiện Anagram excersize , giống như bài toán Count Change nhưng với mệnh giá 50 000 (xu). Tôi không chắc rằng nó có thể được thực hiện lặp đi lặp lại , tôi không quan tâm. Tôi chỉ biết rằng tùy chọn -xss không có tác dụng - tôi luôn thất bại sau 1024 khung xếp chồng (có thể là scala thực hiện công việc không tốt khi phân phối tới giới hạn java hoặc printStackTrace. Tôi không biết). Đây là lựa chọn không tốt, như đã giải thích. Bạn không muốn tất cả các chủ đề trong ứng dụng trở nên quái dị. Tuy nhiên, tôi đã thực hiện một số thử nghiệm với Thread (kích thước ngăn xếp) mới. Điều này thực sự hoạt động,

  def measureStackDepth(ss: Long): Long = {
    var depth: Long = 0
      val thread: Thread = new Thread(null, new Runnable() {
        override def run() {
          try {
          def sum(n: Long): Long = {depth += 1; if (n== 0) 0 else sum(n-1) + 1}
          println("fact = " + sum(ss * 10))
          } catch {
            case e: StackOverflowError => // eat the exception, that is expected
          }
        }
      }, "deep stack for money exchange", ss)
      thread.start()
      thread.join()
    depth
  }                                               //> measureStackDepth: (ss: Long)Long


  for (ss <- (0 to 10)) println("ss = 10^" +  ss + " allows stack of size " -> measureStackDepth((scala.math.pow (10, ss)).toLong) )
                                                  //> fact = 10
                                                  //| (ss = 10^0 allows stack of size ,11)
                                                  //| fact = 100
                                                  //| (ss = 10^1 allows stack of size ,101)
                                                  //| fact = 1000
                                                  //| (ss = 10^2 allows stack of size ,1001)
                                                  //| fact = 10000
                                                  //| (ss = 10^3 allows stack of size ,10001)
                                                  //| (ss = 10^4 allows stack of size ,1336)
                                                  //| (ss = 10^5 allows stack of size ,5456)
                                                  //| (ss = 10^6 allows stack of size ,62736)
                                                  //| (ss = 10^7 allows stack of size ,623876)
                                                  //| (ss = 10^8 allows stack of size ,6247732)
                                                  //| (ss = 10^9 allows stack of size ,62498160)

Bạn thấy rằng ngăn xếp có thể phát triển sâu hơn theo cấp số nhân với nhiều ngăn xếp hơn theo cấp số nhân được phân bổ cho chủ đề.

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.