Pytanie o TCO i stacktrace

0

ZTCW to w .NET jest TCO (tail call optimizaiton). Moje pytanie dotyczy jego obserwowalnych efektów poza wydajnością i przepełnieniami stosu tzn dokładnie chodzi mi o to, czy włączanie i wyłączanie TCO wpływa na raportowane przez Exceptiony stacktrace. Np mam funkcję:

bleble(n) = {
  if (n == 0) {
    throw new Exception("dupa");
  } else {
    bleble(n - 1);
  }
}

I teraz pytanie czy jak jest włączone TCO to czy złapany Exception będzie pokazywał, że metoda była n razy wywołana rekurencyjnie?

Analogicznie chciałbym wiedzieć jak sprawa wygląda w przypadku bardziej skomplikowanych wywołań ogonowych, nie tylko rekurencja ogonowa, ale także np wzajemna rekurencja ogonowa N funkcji.

Do tego jeszcze pytanie ogólniejsze (zakładając, że .NET nie spełnia powyższych oczekiwań):
Czy istnieje jakakolwiek platforma programistyczna, która przeprowadzałaby TCO w taki sposób, żeby zachować raportowane stacktrace takie jakie były przed TCO?

0

Nie wiem jaka jest odpowiedź na pytanie, ale w TCO chodzi raczej o to, by wyeliminować użycie stosu, więc o jakim stacktrace właściwie mówimy :-)
Jakiekolwiek zapisywanie bieżącego stanu przed wywołaniem rekurencyjnym sprowadza się do braku TCO...

0

No właśnie chodzi o to, że np rekurencję ogonową można zamienić na pętlę i w niej dodać licznik wywołań które by się wykonały bez TCO. Podobnie z bardziej zaawansowanymi grafami wywołań - można by w jakiś kompaktowy sposób przechowywać oryginalny stacktrace gdzieś z boku stosu. Sprawa na pewno nietrywialna, ale moim zdaniem możliwa.

0

A jaki problem sprawdzic w disasmie bytecodu czy zostalo zoptymalizowane i wyprintowac sobie stack trace'a?

2

I teraz pytanie czy jak jest włączone TCO to czy złapany Exception będzie pokazywał, że metoda była n razy wywołana rekurencyjnie?

Jasne że nie, przecież na tym ta rekurencja ogonowa polega że funkcja jest właściwie równoważna pętli (i do takiej postaci kompilowana). A stack trace to szczegół implementacyjny.
I tak swoją drogą... Na co Ci stack trace z kilkoma milionami elementów?

Do tego jeszcze pytanie ogólniejsze (zakładając, że .NET nie spełnia powyższych oczekiwań):
Czy istnieje jakakolwiek platforma programistyczna, która przeprowadzałaby TCO w taki sposób, żeby zachować raportowane stacktrace takie jakie były przed TCO?

Patrz wyżej - po co? Przecież to przeczy całej idei TCO ('lekkiej' rekurencji).

Analogicznie chciałbym wiedzieć jak sprawa wygląda w przypadku bardziej skomplikowanych wywołań ogonowych, nie tylko rekurencja ogonowa, ale także np wzajemna rekurencja ogonowa N funkcji.

Tym się nie musisz przejmować, bo bardziej skomplikowane przypadki po prostu nie są wspierane (byłoby to dość męczące/czasochłonne do robienia dla JIT - a biorąc pod uwagę stosunek ilości 'zwykłych' programistów .NET do 'funkcyjnych', nie opłaca się w to inwestować).

0

Pytanie nie brzmiało "po co?" tylko "czy coś takiego jest?".

Wywołania ogonowe wcale nie muszą oznaczać stosów na miliony elementów. Jeśli stosujemy np delimited continuations i JIT wytnie stacktrace całkowicie to tracimy sporo informacji. Wywołania ogonowe można znaleźć też w niefunkcyjnym kodzie, na pewno nie tak często jak w funkcyjnym, no ale przecież nie powiecie, że się nie natknęliście. Więcej powodów nie chce mi się wymyślać, bo nie o tym temat.

Reasumując,:to TCO w .NETu działa w ten sposób, że jak mu 'się zachce' (tzn czytałem, że TCO jest stosowane nawet bez hintów w bajtkodzie) to zamieni wywołania ogonowe na skoki i obetnie zarazem stacktrace, tak?

0

To ja sie spytam moze inaczej - czy znasz jakikolwiek jezyk, ktory przeprowadza TCO i printuje stos tak jakby nie bylo optymalizacji? Czy moze wymysliles sobie teoretyczne zachowanie i postanowiles spytac czy nie ma tego akurat w C#? [rotfl]
Takie zachowanie bylo by nawiasem mowiac bardzo bez sensu - utrudnilo by debugowanie. Przykladowo mialbys dwie funkcje rekurencyjne, jedna zostala zoptymalizowana druga nie, do tego jedna wywoluje druga. Teraz pytanie: ktora przepelnila stos. Nie jestes w stanie stwierdzic na podstawie stack trace'a.
Ale rozumiem, ze taki argument, ze cos jest bez sensu zeby to implementowac nie zadowala cie, wiec jak chcesz dowodu - to po prostu sprawdz sobie empirycznie.

0

Nie znam i dlatego się pytam. .NET podałem jako przykład dlatego, że zarówno dokonuje TCO jak i podaje stacktrace, więc to już był jakiś kandydat.

Takie zachowanie bylo by nawiasem mowiac bardzo bez sensu - utrudnilo by debugowanie. Przykladowo mialbys dwie funkcje rekurencyjne, jedna zostala zoptymalizowana druga nie, do tego jedna wywoluje druga. Teraz pytanie: ktora przepelnila stos. Nie jestes w stanie stwierdzic na podstawie stack trace'a.

Nie rozumiem w jaki sposób obcinanie stosu miałoby ułatwić złapanie funkcji która przepełnia stos. Przecież to która funkcja wyląduje na stacktrace zależy od tego która zostanie zoptymalizowana.

Po co jest stacktrace to chyba nie muszę się rozpisywać. A jeśli miałbym kompaktową formę stosu wywołań w pamięci, to mógłbym również stos pokazać w kompaktowym formacie.

Naprawdę nie interesuje mnie czy wg was ma to sens, pytam tylko czy coś takiego jest. A wprost odpowiedzi do tej pory nie uzyskałem.

PS:
Machnąłem się przy tagach w temacie, chodziło o .NET a nie C#.

0

Stacktrace pokazuje co sie dokladnie dzialo w programie. Np. na podstawie tego mozna stwierdzic, czy kompilator zrobil TCO czy nie. Jak masz jakis udawany Stacktrace to przestajesz wiedziec co sie stalo. Przyklad masz taki udawany stack trace:

func2 1000 razy
func1 1000 razy
func2 1000 razy

Ktora funkcja jest odpowiedzialna za przepelnienie stosu? Jezeli func2 zostala zoptymalizowana to odpowiedzialna jest func1. Jezeli func1 zostala zoptymalizowana to odpowiedzialna jest func2. Jezeli zadna nie zostala zoptymalizowana to func2 jest odpowiedzialna.
Ale oczywiscie na podstawie udawanego stack trace'a nie jestes w stanie stwierdzic, ktora zostala zoptymalizowana. Mozesz sobie spojrzec w zdekompilowany kod (co nie jest wygodne) i miec nadzieje, ze jezyk nie przeprowadza jakichs runtimeowych TCO.
Gdy masz prawdziwy Stacktrace to od razu wiesz, ktora funkcja jest odpowiedzialna za przepelnienie stosu i co trzeba poprawic.

0

Czy wszystkie błędy polegają na przepełnieniu stosu? Co jeśli mam wywołania typu:

fun1
fun2
fun3
fun4
fun2
fun3
fun5
exception

a potem kompilator zastosuje TCO i zostanę z takim stacktrace

fun1
fun3
fun3
fun5
exception

?

0

Jak zastosuje TCO to nie ma szans, ze zostaniesz z takim stacktracem:
fun1
fun3
fun3
fun5
exception

Jak zastosuje inlinening funkcji to mozesz zostac z takim stacktracem.

1

Zauważ że TCO można wyłączyć, a przede wszystkim optymalizacje są domyślnie wyłączone w trybie Debug. Czyli podczas debugowania nie musisz się przejmować tym że TCO Ci zepsuje stack trace, bo po prostu go nie ma.

Pytanie nie brzmiało "po co?" tylko "czy coś takiego jest?".

Naprawdę nie interesuje mnie czy wg was ma to sens, pytam tylko czy coś takiego jest. A wprost odpowiedzi do tej pory nie uzyskałem.

No dobrze, proszę. Nie ma. Wystarczająco wprost, myślę.

Mając wiedzę o asemblerze, wiesz prawdopodobnie że stacktrace powstaje w pewnym sensie 'niejawnie', jako efekt uboczny normalnego działania programu.

Żeby stacktrace działało przy TCO, kompilator musiałby niejawnie tworzyć gdzieś jakąś listę 'ominiętych' ramek stosu i dołączać ją do stacktrace (czyli przy każdym wywołaniu ogonowym zapisujemy gdzieś informację że ono nastąpiło, żeby je później odczytać tworząc stacktrace).

Ponieważ ta lista ominiętych ramek nie może być na stosie (wtedy efektywnie TCO zmieniłoby się efektywnie w WTF), trzeba też pamiętać żeby ją gdzieś trzymać w synchronizacji z tym co się faktycznie dzieje.
Czyli jeśli foo woła ogonowo bar zapisujemy gdzieś że foo należy niejawnie do stacktrace, i dodatkowo kiedy bar wychodzi usuwamy foo z tej listy ominiętych ramek. Bar oczywiście nie wie że może być wołane ogonowo (w szczególności jeśli jest np. funkcją z biblioteki).

Koszmar implementacyjny, duży narzut obliczeniowy, rekurencja ogonowa dalej zajmuje pamięć (tylko że na stercie a nie na stosie - próby pisana czysto funkcyjnie skończą się dramatycznym memleakiem) - w imię czego?

Dodatkowo sensowny stacktrace zawiera informacje o parametrach z jakimi funkcje były wołane. Przy normalnej rekurencji odczytywanie ich jest naturalne, przy rekurencji ogonowej te wartości znikają. Je też chcesz gdzieś zapisywać?

Oczywiście jeśli Ci bardzo na tym zależy, możesz zawsze napisać własną maszynę wirtualną która będzie wykonywała wspomniane przeze mnie operacje. Jeśli chodzi o JVM wiesz co robić pewnie, do .NET istnieje rotor, nic tylko siadać i pisać ;]

0

A to ja mam takie jeszcze pytanie: jakie sa zalety takie preparowania stacktrace'a w przypadku TCO? W przypadku inlineningu mialo by sens dodawanie nazwy funkcji, w ktorej jestesmy jezeli ta zostala zinlinenowana, ale po jaka cholere robic to podczas TCO? Zeby sobie zobaczyc 1000 wywolan funkcji, ktore tak na prawde nie wystapilo?

0

Jak zastosuje TCO to nie ma szans, ze zostaniesz z takim stacktracem:
fun1
fun3
fun3
fun5
exception

Jak zastosuje inlinening funkcji to mozesz zostac z takim stacktracem.

Ale czemu nie ma szans? Załóżmy, że fun2 i fun4 są wywoływane jako ostania instrukcja przed powrotem z funkcji (tutaj fun1, fun3 i fun4) - czyli są wywoływane ogonowo - czyli są idealnymi kandydatami do TCO. Zauważ, że wywołanie ogonowe != rekurencja ogonowa. Inaczej mówiąc: każda rekurencja ogonowa jest wywołaniem ogonowym, ale nie każde wywołanie ogonowe jest rekurencją ogonową.

Mając wiedzę o asemblerze, wiesz prawdopodobnie że stacktrace powstaje w pewnym sensie 'niejawnie', jako efekt uboczny normalnego działania programu.

Żeby stacktrace działało przy TCO, kompilator musiałby niejawnie tworzyć gdzieś jakąś listę 'ominiętych' ramek stosu i dołączać ją do stacktrace (czyli przy każdym wywołaniu ogonowym zapisujemy gdzieś informację że ono nastąpiło, żeby je później odczytać tworząc stacktrace).

Dokładnie sobie z tego zdaję sprawę. I dlatego napisałem, już w drugim moim poście w tym temacie:

No właśnie chodzi o to, że np rekurencję ogonową można zamienić na pętlę i w niej dodać licznik wywołań które by się wykonały bez TCO. Podobnie z bardziej zaawansowanymi grafami wywołań - można by w jakiś kompaktowy sposób przechowywać oryginalny stacktrace gdzieś z boku stosu. Sprawa na pewno nietrywialna, ale moim zdaniem możliwa.

Z naciskiem na "kompaktowy sposób".

0

Zauważ, że wywołanie ogonowe != rekurencja ogonowa

Czyli mowisz o zwyklym rozwijaniu (inlineningu) funkcji tyle tylko, ze rozwijana funkcja jest na koncu innej funkcji. Jak juz mowilem, to nie jest TCO.

0

Załóżmy przypadek ekstremalny:

fun1 = {
  print("fun1")
  a = random.nextInt(4)
  if (a == 1) {
    fun1
  } else if (a == 2) {
    fun2
  } else if (a == 3) {
    fun3
  }
}

fun2 = {
  print("fun2")
  a = random.nextInt(4)
  if (a == 1) {
    fun1
  } else if (a == 2) {
    fun2
  } else if (a == 3) {
    fun3
  }
}

fun3 analogicznie

Wszystkie wywołania tutaj są ogonowe, część z nich to rekurencja ogonowa. W każdym przypadku można zrobić TCO i jak zrobimy ten TCO to tracimy stacktrace o ile go z boku nie zapamiętujemy.

Dodatkowo sensowny stacktrace zawiera informacje o parametrach z jakimi funkcje były wołane.

W Javie nie zawiera: http://docs.oracle.com/javase/1.4.2/docs/api/java/lang/StackTraceElement.html
W .NETu chyba też nie ma.

Ponadto jeśli funkcja w Javie nie ma modyfiakatora final przy parametrach to można je zmieniać, przez co tracimy początkowe wartości. Oprócz tego obiekty same w środku mogą się zmienić i też tracimy wcześniejszy stan.

Oczywiście jeśli Ci bardzo na tym zależy, możesz zawsze napisać własną maszynę wirtualną

No właśnie mam zamiar kiedyś spróbować :] ale tymczasem chcę się upewnić, że nikt inny nie wymyślił już tego, co sam będę próbował wymyślić.

0

Z wylaczanym inliningiem nie zoptymalizuje czegos takiego. To inlining jest odpowiedzialny za calkowite tracenie informacji z jakiej funkcji poszedl exception. TCO jest odpowiedzialne tylko za tracenie informacji ile razy funkcja zostala ziterowana.

0

TCO w .NETu nie optymalizuje tylko i wyłącznie rekurencji ogonowych, ale także inne typy wywołań ogonowych. Tak przynajmniej zrozumiałem blogi z MSDNu. Tak, że .NET spokojnie mógłby zastosować TCO w podanym przeze mnie przypadku we wszystkich wywołaniach ogonowych.

Gdyby .NET optymalizował tylko i wyłącznie rekurencję ogonową i to jeszcze z obcinaniem stacktrace, to wtedy nie byłby to żaden bajer, bo takie coś można zrobić bardzo prosto i już na etapie kompilacji do bajtkodu, tzn po prostu zamienić rekurencję ogonową na pętlę. Takie coś już jest np w Clojure, Scali czy F#.

Zauważ że TCO można wyłączyć, a przede wszystkim optymalizacje są domyślnie wyłączone w trybie Debug. Czyli podczas debugowania nie musisz się przejmować tym że TCO Ci zepsuje stack trace, bo po prostu go nie ma.

Podczas debugowania OK, ale z tego co zrozumiałem z MSDNowych blogów, w trybie release nie da się wyłączyć pojedynczo TCO, co najwyżej można wyłączyć hurtem wszystkie optymalizacje i zostać z zamulającym kodem wykonywalnym. No chyba, że za szybko czytałem te blogi ; P

0

Moze tak moze nie trzeba by to sprawdzic. Ale przypadki, ktore podajesz sa skrajnie rzadkie. Juz znacznie bardziej przydatnym ficzerem bylo by przechowywanie informacji o aktualnie wykonywanej funkcji. Teraz inlinenowane funkcje nie pozostawiaja sladu w stack trace co jest prawdziwym bolem. I skoro nie ma nawet takiego podstawowego ficzera to nie ludz sie, ze jakas [CIACH!] z tco jest obslugiwana.

0

CLR sporo rzeczy może logować poprzez ETW (wydajne logowanie dużej ilości zdarzeń na poziomie jądra systemu). Dostarcza zdarzenia typu: MethodJitInliningSucceeded, MethodJITTailCallSucceeded czy CLRStackWalk. Być może z nimi dałoby się coś pokombinować.
http://msdn.microsoft.com/en-us/library/dd264810.aspx

0

Poza tym jest taka kwestia, gdy frame jest gubiony ze wzgledu na inlining (tudziez na ze wzgledu na TCO, o ktorym wibowit wspomnial) to jezeli zaszly dalsze optymalizacje to nie jestesmy w stanie ustalic w jakiej funkcji jestesmy. Przykladowo:
Sa funckje:
f1() {a=1;b=2;}
f2() {c=3;d=4;}
I wywolanie
f1();f2();
Kompilator inlinuje:
a=1;b=2;
c=3;d=4;
Tutaj jeszcze mozna odtworzyc gdzie zaczyna sie f1 i f2. Teraz kompilator robi sobie reordering bo stwierdza, ze tak jest optymalnie:
a=1;c=3;
b=2;d=4;
I tutaj gdzie zaczyna sie f1 i f2 nie mozna stwierdzic bo oryginalne funkcje juz nie istnieja.

0

Akurat z inline'ingiem jest dość łatwo - wystarczy obok każdego kawałka skompilowanego kodu zrobić mapę (pozycja w skompilowanym kodzie) => (z której funkcji oryginalnie pochodzi). To z TCO jest ciężko - jeśli mamy ogonowo rekurencyjną funkcję to stos wywołań będzie wyglądał identycznie bez względu na głębokość rekurencji.

Stos wywołań zawiera wskaźniki do skompilowanego kodu (lub nieskompilowanego jeśli jesteśmy w trybie interpretera), tzn dokładny adres do którego powrócić. A więc nie tylko wiemy jakie metody zostały wywoływane, ale dokładnie wiemy na jakiej pozycji wywołały kolejne.

JVM (JVMa trochę znam więc o nim piszę) i tak musi mieć mapę (pozycja w skompilowanym kodzie) => (pozycja w bajtkodzie), poza tym mapa (pozycja w bajtkodzie) => (pozycja w pliku źródłowym) jest wbudowana w pliki .class. Te mapy są potrzebne, żeby wygenerować stacktrace z ramek stosu. A ta pierwsza mapa to generalnie dokładnie ta sama o której mówiłem w pierwszym akapicie.

0

Akurat z inline'ingiem jest dość łatwo - wystarczy obok każdego kawałka skompilowanego kodu zrobić mapę (pozycja w skompilowanym kodzie) => (z której funkcji oryginalnie pochodzi).

No rzeczywiscie latwo [rotfl]. Machnijmy sobie logowanie dla kazdej insttrukcji dostaniemy stacktrace wielkosci kilka milionow(wystarczy maly przeplot instrukcji z roznych funkcji wykonany w petli) - bardzo wydajne. Wszystko po to, zeby otrzymac taki stack trace (poprzedni przyklad):
f1
f2
f1
f2

A co jesli instrukcja nie nalezy do zadnej funkcji, tylko zostala wygenerowana przez polaczenie instrukcji z dwoch roznych funkcji?
Przyklad:
f1() {a++;}
f2() {a++;}
f1();f2();
Po iniliningu
a++;
a++;
Po optymalizacji
a += 2

0

Nie zrozumiałeś. Nie napisałem o żadnym logowaniu jeśli chodzi o inline. Po prostu te mapy są tworzone przy kompilacji do kodu natywnego i wykorzystywane przy budowaniu stacktrace z surowych ramek stosu.

Poza tym stacktrace jest budowane, gdy jest to potrzebne, czyli np:

  • w trybie debugowania jeśli mamy breakpointa - w takim przypadku JVM może wyłączyć inline w okolicach breakpointa,
  • rzucanie exceptionów, np ArrayOutOfBoundsException - inline raczej nie spowoduje, że wyjątki zostaną rzucone w nie tej kolejności co trzeba, a w miejscach gdzie mogą zostać rzucone wyjątki możną wyłączyć ten mechanizm mieszania instrukcji,

Jeśli w trybie produkcyjnym VM sobie zamieni a++; a++; (pochodzące z różnych funkcji) na a += 2; to nic to absolutnie nie zmieni, bo i tak w tych miejscach nie ma szans polecieć Exception,

Problem w tym, że schodzimy coraz bardziej w teoretyczne rozważania, bo ja nie wiem jak to jest pod maską robione i ty też zapewne nie wiesz. No chyba, że jesteś w stanie udowodnić, że inline'ing może mieć wpływ na raportowany stacktrace.

http://stackoverflow.com/questions/7218575/how-can-methods-throwing-exceptions-be-inlined

0

Jeśli w trybie produkcyjnym VM sobie zamieni a++; a++; (pochodzące z różnych funkcji) na a += 2; to nic to absolutnie nie zmieni, bo i tak w tych miejscach nie ma szans polecieć stacktrace,

Ma szanse chociazby wtedy kiedy zmienna sie przepelni (w Javie tego nie ma, w C# mozna sobie w opcja projektu to ustawic). Poza tym to tylko prosty przyklad, zeby zobrazowac problem.

0

Problem w tym, że schodzimy coraz bardziej w teoretyczne rozważania, bo ja nie wiem jak to jest pod maską robione i ty też zapewne nie wiesz. No chyba, że jesteś w stanie udowodnić, że inline'ing może mieć wpływ na raportowany stacktrace.

To chyba oczywiste, ze moge to udowodnic [rotfl]. Wystarczy sobie rzucic exception ze zinlineowanej funkcji - w stacktrace nie bedzie zinlinenowanej funkcji.

0

Proszę o przykład.

Ma szanse chociazby wtedy kiedy zmienna sie przepelni (w Javie tego nie ma, w C# mozna sobie w opcja projektu to ustawic). Poza tym to tylko prosty przyklad, zeby zobrazowac problem.

Hmm, chcesz powiedzieć, że w C# mogę dostać Exceptiona w którym nie będzie podanego miejsca jego wystąpienia albo zostanie podane złe miejsce?

0

Przyklad jest banalny:

    class Program
    {
        static void func()
        {
            throw new Exception("Inner") ;
        }

        static void Main(string[] args)
        {
            try
            {
                func();
            }
            catch (Exception e)
            {
                Console.WriteLine(e);
            }
        }
    }

Na Debug:
System.Exception: Inner
w ConsoleApplication3.Program.func() w C:\Users\ssilver\Documents\Visual Stud
io 2010\Projects\ConsoleApplication3\ConsoleApplication3\Program.cs:wiersz 14
w ConsoleApplication3.Program.Main(String[] args) w C:\Users\ssilver\Document
s\Visual Studio 2010\Projects\ConsoleApplication3\ConsoleApplication3\Program.cs
:wiersz 21

Na release:
System.Exception: Inner
w ConsoleApplication3.Program.Main(String[] args) w C:\Users\ssilver\Document
s\Visual Studio 2010\Projects\ConsoleApplication3\ConsoleApplication3\Program.cs
:wiersz 21

A dyskusja robi sie teoretyczna bo wymysliles sobie teoretyczny mechanizm, ktory nie jest zaimplementowany w zadnym jezyku i do tego nawet nie ma szans dzialac. Do tego nie raczyles sobie zainstalowac C# zeby sprawdzic tak banalny program jak ten powyzej.

0

Hmm, chcesz powiedzieć, że w C# mogę dostać Exceptiona w którym nie będzie podanego miejsca jego wystąpienia albo zostanie podane złe miejsce?

Ale co Cie tak w tym dziwi? W JITowanej Javie nie mozna dostac obcietego Stacktrace'a?

0

Nie spotkałem się z obcinaniem stacktrace w Javie. Java w sumie oficjalnie nie daje żadnych gwarancji na to, że stacktrace będzie pełny, ale mimo wszystko mam wrażenie, że obecnie zawsze jest, tzn pod HotSpotem konkretnie.

Próbowałem na różne dziwne sposoby wywołać obcinanie stacktrace w HotSpocie ale mi się nie udawało. Przykładowy kod:

import java.util.Arrays;


public class Main {
    
    static int[] tablica = new int[] {0, 0, 0, 0, 0, 0, 0};
    static Exception exception = null;

    static int a = 5;
    static boolean suppress = true;

    void f(int x) {
        final int mask = 0xff;
        a += x;
        if ((a & mask) == (-1 & mask) && !suppress) {
            throw new RuntimeException("lol" + x);
        }
    }

    void run() {
        for (int j = 0; j < 2; j++) {
            for (int i = 0; i < 123456789; i++) {
                try {
                    f(i);
                } catch (Exception e) {
                    tablica[e.getStackTrace().length]++;
                    exception = e;
                }
            }
            suppress = false;
        }
    }

    public static void main(final String[] args) {
        new Main().run();
        System.out.println(a);
        System.out.println(Arrays.toString(tablica));
        exception.printStackTrace();
        a = 5;
        for (int j = 0; j < 2; j++) {
            for (int i = 0; i < 123456789; i++) {
                a += i;
            }
        }
        System.out.println(a);
    }
}

I wynik:

piotrek@p5q-pro:~/NetBeansProjects/IdeoneJava$ java -XX:+PrintCompilation -jar dist/IdeoneJava.jar 
     56    1             Main::f (59 bytes)
     60    1 % !         Main::run @ 9 (58 bytes)
    320    1 % !         Main::run @ -2 (58 bytes)   made not entrant
    321    1             Main::f (59 bytes)   made not entrant
    321    2 % !         Main::run @ 9 (58 bytes)
    325    2             Main::f (59 bytes)
    346    3  s          java.lang.Throwable::getOurStackTrace (80 bytes)
    351    4     n       java.lang.Throwable::getStackTraceElement (0 bytes)   
    375    5             java.lang.Object::<init> (1 bytes)
    383    6     n       java.lang.Object::clone (0 bytes)   
    383    7  s          java.lang.Throwable::fillInStackTrace (29 bytes)
    383    8     n       java.lang.Throwable::fillInStackTrace (0 bytes)   
    384   12     n       java.lang.Throwable::getStackTraceDepth (0 bytes)   
    385    9             java.lang.Throwable::<init> (34 bytes)
    385   10             java.lang.Exception::<init> (6 bytes)
    387   11             java.lang.Throwable::getStackTrace (11 bytes)
    389   13             java.lang.RuntimeException::<init> (6 bytes)
-1881352535
[0, 0, 0, 482253, 0, 0, 0]
java.lang.RuntimeException: lol123456592
   2012    3  s          java.lang.Throwable::getOurStackTrace (80 bytes)   made not entrant
	at Main.f(Main.java:17)
	at Main.run(Main.java:25)
	at Main.main(Main.java:36)
   2013    3 %           Main::main @ 50 (86 bytes)
-1881352535

Jak widać za każdym razem stacktrace miało odpowiednią głębokość.

1 użytkowników online, w tym zalogowanych: 0, gości: 1