Jak wolne są wyjątki w Javie?

Pytanie: czy obsługa wyjątków w Javie jest naprawdę powolna?

Konwencjonalna mądrość, jak również wiele wyników Google, mówi, że wyjątkowa logika nie powinna być używana do normalnego przepływu programów w Javie. Zwykle podaje się dwa powody,

  1. jest naprawdę powolny - nawet o rząd wielkości wolniejszy niż zwykły kod (podane powody są różne),

I

  1. jest bałagan, ponieważ ludzie oczekują, że tylko błędy będą obsługiwane w wyjątkowym kodzie.

To pytanie dotyczy #1.

Jako przykład, ta strona opisuje obsługę wyjątków Java jako "bardzo powolną" i odnosi powolność do tworzenia ciągu wiadomości WYJĄTKÓW - "ten łańcuch jest następnie używany do tworzenia obiektu wyjątku, który jest wyrzucany. To nie jest szybkie."Artykuł efektywna obsługa wyjątków w Javie mówi, że" powodem tego jest aspekt tworzenia obiektów obsługi wyjątków, co sprawia, że rzucanie wyjątki z natury wolne". Innym powodem jest to, że generowanie stack trace spowalnia go.

Moje testy (przy użyciu Java 1.6.0_07, Java HotSpot 10.0, na 32-bitowym Linuksie) wskazują, że obsługa wyjątków nie jest wolniejsza niż zwykły kod. Próbowałem uruchomić metodę w pętli, która wykonuje jakiś kod. Na końcu metody używam logiki, aby wskazać, czy return czy throw. W ten sposób rzeczywiste przetwarzanie jest takie samo. Próbowałem uruchomić metody w różnych kolejnościach i uśredniając moje czasy testów, myśląc, że to mogło być rozgrzewanie JVM. We wszystkich moich testach rzut był co najmniej tak szybki jak zwrot, jeśli nie szybszy(do 3,1% szybszy). Jestem całkowicie otwarty na możliwość, że moje testy były błędne, ale nie widziałem tam niczego w drodze próbki kodu, porównań testowych lub wyników w zeszłym roku lub dwóch, które pokazują obsługę wyjątków w Javie, aby być naprawdę powolne.

To, co prowadzi mnie tą ścieżką, to API musiałem użyć, że wyrzucił wyjątki jako część normalnej logiki sterowania. Chciałem poprawić ich użycie, ale teraz mogę nie być w stanie. Czy zamiast tego będę musiał chwalić ich za ich przyszłościowe myślenie?

W artykule efektywna obsługa wyjątków w Javie w kompilacji just-In-time autorzy sugerują, że obecność samych programów obsługi wyjątków, nawet jeśli nie zostaną wyrzucone wyjątki, jest wystarczająca, aby uniemożliwić kompilatorowi JIT poprawną optymalizację kodu, co spowalnia jego działanie na ziemię. Nie testowałem jeszcze tej teorii.

Author: KIN, 2008-11-18

17 answers

To zależy od implementacji WYJĄTKÓW. Najprostszym sposobem jest użycie setjmp i longjmp. Oznacza to, że wszystkie rejestry procesora są zapisywane na stosie (co już zajmuje trochę czasu) i ewentualnie muszą zostać utworzone inne dane... wszystko to dzieje się już w oświadczeniu try. Instrukcja throw wymaga rozwinięcia stosu i przywrócenia wartości wszystkich rejestrów(i możliwych innych wartości w maszynie wirtualnej). Więc spróbuj i rzucaj są równie powolne, a to jest dość powolne, jednak jeśli nie ma wyjątku jest wyrzucany, wyjście z bloku try w większości przypadków nie zajmuje czasu (ponieważ wszystko jest umieszczane na stosie, który czyści się automatycznie, jeśli metoda istnieje).

Sun i inni uznali, że jest to prawdopodobnie nieoptymalne i oczywiście maszyny wirtualne stają się z czasem coraz szybsze. Istnieje inny sposób na zaimplementowanie WYJĄTKÓW, co sprawia, że try się błyskawicznie (w zasadzie nic się nie dzieje dla try w ogóle - wszystko, co musi się wydarzyć, jest już zrobione po załadowaniu klasy przez VM) i sprawia, że rzut nie jest tak powolny. Nie wiem, który JVM używa tej nowej, lepszej techniki...

...ale piszesz w Javie, żeby później Twój kod działał tylko na jednym JVM na jednym konkretnym systemie? Skoro może kiedykolwiek działać na innej platformie lub innej wersji JVM (ewentualnie innego dostawcy), kto powiedział, że również używają szybkiej implementacji? Szybki jest bardziej skomplikowany niż powolny i nie jest łatwo możliwy we wszystkich systemach. Chcesz pozostać przenośny? Więc nie polegaj na tym, że wyjątki są szybkie.

To również duża różnica, co robisz w bloku prób. Jeśli otworzysz blok try I nigdy nie wywołasz żadnej metody z tego bloku try, blok try będzie bardzo szybki, ponieważ JIT może wtedy faktycznie traktować rzut jak prosty goto. Nie musi ani zapisywać stanu stosu, ani też nie musi odwijać stosu, jeśli zostanie wyrzucony wyjątek(musi tylko przeskoczyć do programów obsługi przechwytywania). Jednak to nie jest to, co zwykle robisz. Zazwyczaj otwierasz blok próbny a potem wywołaj metodę, która może spowodować wyjątek, prawda? I nawet jeśli po prostu użyjesz bloku try w swojej metodzie, jaki to będzie rodzaj metody, która nie wywoła żadnej innej metody? Czy po prostu obliczy liczbę? To po co Ci wyjątki? Istnieją znacznie bardziej eleganckie sposoby regulacji przepływu programu. W przypadku prawie wszystkiego innego niż prosta matematyka, będziesz musiał wywołać zewnętrzną metodę, a to już niszczy zaletę lokalnego bloku prób.

Zobacz następujący kod badania:

public class Test {
    int value;


    public int getValue() {
        return value;
    }

    public void reset() {
        value = 0;
    }

    // Calculates without exception
    public void method1(int i) {
        value = ((value + i) / i) << 1;
        // Will never be true
        if ((i & 0xFFFFFFF) == 1000000000) {
            System.out.println("You'll never see this!");
        }
    }

    // Could in theory throw one, but never will
    public void method2(int i) throws Exception {
        value = ((value + i) / i) << 1;
        // Will never be true
        if ((i & 0xFFFFFFF) == 1000000000) {
            throw new Exception();
        }
    }

    // This one will regularly throw one
    public void method3(int i) throws Exception {
        value = ((value + i) / i) << 1;
        // i & 1 is equally fast to calculate as i & 0xFFFFFFF; it is both
        // an AND operation between two integers. The size of the number plays
        // no role. AND on 32 BIT always ANDs all 32 bits
        if ((i & 0x1) == 1) {
            throw new Exception();
        }
    }

    public static void main(String[] args) {
        int i;
        long l;
        Test t = new Test();

        l = System.currentTimeMillis();
        t.reset();
        for (i = 1; i < 100000000; i++) {
            t.method1(i);
        }
        l = System.currentTimeMillis() - l;
        System.out.println(
            "method1 took " + l + " ms, result was " + t.getValue()
        );

        l = System.currentTimeMillis();
        t.reset();
        for (i = 1; i < 100000000; i++) {
            try {
                t.method2(i);
            } catch (Exception e) {
                System.out.println("You'll never see this!");
            }
        }
        l = System.currentTimeMillis() - l;
        System.out.println(
            "method2 took " + l + " ms, result was " + t.getValue()
        );

        l = System.currentTimeMillis();
        t.reset();
        for (i = 1; i < 100000000; i++) {
            try {
                t.method3(i);
            } catch (Exception e) {
                // Do nothing here, as we will get here
            }
        }
        l = System.currentTimeMillis() - l;
        System.out.println(
            "method3 took " + l + " ms, result was " + t.getValue()
        );
    }
}

Wynik:

method1 took 972 ms, result was 2
method2 took 1003 ms, result was 2
method3 took 66716 ms, result was 2

Spowolnienie z bloku try jest zbyt małe, aby wykluczyć czynniki zakłócające, takie jak procesy w tle. Ale blok połowu zabił wszystko i uczynił go 66 razy wolniejszym!

Jak powiedziałem, wynik nie będzie tak źle, jeśli umieścić try / catch i rzucić wszystko w ramach tej samej metody (method3), ale jest to specjalna optymalizacja JIT Nie będę polegać. I nawet podczas korzystania z tej optymalizacji, rzut jest nadal dość powolny. Więc Nie wiem, co próbujesz tutaj zrobić, ale na pewno jest lepszy sposób na zrobienie tego niż użycie try/catch/throw.

 308
Author: Mecki,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2015-06-17 10:31:43

Dla twojej wiadomości, rozszerzyłem eksperyment, który zrobił Mecki:

method1 took 1733 ms, result was 2
method2 took 1248 ms, result was 2
method3 took 83997 ms, result was 2
method4 took 1692 ms, result was 2
method5 took 60946 ms, result was 2
method6 took 25746 ms, result was 2

Pierwsze 3 są takie same jak u Meckiego (mój laptop jest oczywiście wolniejszy).

Method4 jest identyczny z method3 z tym wyjątkiem, że tworzy new Integer(1) zamiast robić throw new Exception().

Method5 jest podobny do method3 z tym wyjątkiem, że tworzy new Exception() bez rzucania go.

Method6 jest podobny do method3 z tym wyjątkiem, że rzuca wstępnie utworzony wyjątek (zmienną instancji) zamiast tworzyć nowy.

W Javie koszt wyrzucenia wyjątku to czas spędzony na zbieraniu śladu stosu, który występuje podczas tworzenia obiektu wyjątku. Rzeczywisty koszt wyrzucenia wyjątku, choć duży, jest znacznie mniejszy niż koszt utworzenia wyjątku.

 220
Author: Hot Licks,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2011-11-05 23:14:35

Aleksey Shipilëv wykonał bardzo dokładną analizę , w której porównuje wyjątki Javy pod różnymi kombinacjami warunków:

  • nowo utworzone wyjątki vs wstępnie utworzone wyjątki
  • Stack trace enabled vs disabled
  • stack trace requested vs never requested
  • Caught at the top level vs retrown at every level vs chained / wrapped at every level
  • Różne poziomy głębokości stosu wywołań Javy
  • No inlining optimizations vs extreme inlining vs domyślne ustawienia
  • pola zdefiniowane przez użytkownika odczytywane vs Nie odczytywane

Porównuje je również do wydajności sprawdzania kodu błędu na różnych poziomach częstotliwości błędów.

Wnioski (cytowane dosłownie z jego postu) były następujące:

  1. Wyjątki są naprawdę wyjątkowe. jeśli używasz ich zgodnie z przeznaczeniem i komunikujesz tylko naprawdę wyjątkowe przypadki wśród przeważającej liczby przypadków nie wyjątkowych obsługiwane przez zwykły kod, a następnie za pomocą wyjątków jest wydajność win.

  2. Koszt wydajności WYJĄTKÓW składa się z dwóch głównych składników: Budowa śledzenia stosu gdy wyjątek jest utworzony i odwijanie stosu podczas wyrzucania WYJĄTKÓW.

  3. Koszty budowy śladu stosu są proporcjonalne do głębokości stosu w momencie wystąpienia wyjątku. To już jest złe, bo kto na Ziemi zna głębokość stosu, na której to metoda rzucania by się nazywała? Nawet jeśli wyłączysz generowanie śledzenia stosu i / lub buforujesz wyjątki, możesz pozbyć się tylko tej części kosztów wydajności.

  4. Koszty odwijania stosu zależą od tego, jak dobrze nam idzie przybliżenie obsługi wyjątków w skompilowanym kodzie. staranna struktura kodu, aby uniknąć głębokiego wyszukiwania procedur obsługi wyjątków, prawdopodobnie pomaga nam osiągnąć szczęście.

  5. Czy należy wyeliminować oba efekty, wydajność koszt WYJĄTKÓW to koszt oddziału lokalnego. bez względu na to, jak pięknie to brzmi, nie oznacza to, że powinieneś używać WYJĄTKÓW jako zwykłego przepływu sterowania, ponieważ w takim przypadku jesteś na łasce optymalizacji kompilatora! powinieneś używać ich tylko w naprawdę wyjątkowych przypadkach, gdzie częstotliwość WYJĄTKÓW amortyzuje ewentualny pechowy koszt podniesienia faktycznego wyjątku.

  6. Optymistyczna zasada wydaje się być 10^-4 częstość występowania wyjątki są wystarczająco wyjątkowe. To oczywiście zależy od ciężaru samych WYJĄTKÓW, dokładnych działań podejmowanych w programach obsługi wyjątków itp.

Wynikiem jest to, że gdy wyjątek nie jest wyrzucony, nie ponosisz kosztów, więc gdy wyjątek jest wystarczająco Rzadki, obsługa wyjątków jest szybsza niż używanie if za każdym razem. Cały post jest bardzo wart przeczytania.

 47
Author: Doval,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2018-07-30 19:45:36

Moja odpowiedź, niestety, jest po prostu zbyt długa, aby pisać tutaj. Więc pozwól, że podsumuję tutaj i odsyłam do http://www.fuwjax.com/how-slow-are-java-exceptions / dla drobiazgów.

Prawdziwym pytaniem nie jest "jak powolne są błędy zgłaszane jako wyjątki" w porównaniu do "kodu, który nigdy nie zawodzi"?"jak może Pan uwierzyć w zaakceptowaną odpowiedź. Zamiast tego pytanie powinno brzmieć :" jak powolne są "awarie zgłaszane jako wyjątki" w porównaniu do awarii zgłaszanych w inny sposób?" Ogólnie rzecz biorąc, dwa inne sposoby zgłaszania awarii są albo z wartościami sentinel lub z opakowaniami wyników.

Wartości Sentinel są próbą zwrócenia jednej klasy w przypadku sukcesu, a drugiej w przypadku niepowodzenia. Możesz myśleć o tym prawie jak o zwróceniu wyjątku zamiast rzucania. Wymaga to współdzielonej klasy rodzica z obiektem success, a następnie sprawdzenia "instanceof" i kilku rzutów, aby uzyskać informacje o sukcesie lub niepowodzeniu.

Okazuje się, że w ryzyko bezpieczeństwa typu, wartości Sentinel są szybsze niż wyjątki, ale tylko o współczynnik około 2x. teraz, to może wydawać się dużo, ale że 2x pokrywa tylko koszt różnicy wdrożenia. W praktyce czynnik ten jest znacznie niższy, ponieważ nasze metody, które mogą zawieść, są o wiele bardziej interesujące niż kilka operatorów arytmetycznych, jak w przykładowym kodzie w innym miejscu na tej stronie.

Owijarki wynikowe, z drugiej strony, nie poświęcają bezpieczeństwa typu w ogóle. Owijają sukces i porażkę informacje w jednej klasie. Tak więc zamiast" instanceof "dostarczają one" isSuccess () " i gettery zarówno dla obiektów success, jak i failure. Jednak obiekty wynikowe są mniej więcej 2x wolniejsze niż używanie WYJĄTKÓW. Okazuje się, że tworzenie nowego obiektu wrappera za każdym razem jest znacznie droższe niż rzucanie czasem WYJĄTKÓW.

Poza tym, wyjątki są językiem dostarczonym w sposób wskazujący, że metoda może się nie powieść. Nie ma innego sposobu, aby stwierdzić tylko z API, których metody powinny zawsze (głównie) działać i które powinny zgłaszać błędy.

Wyjątki są bezpieczniejsze niż sentinele, szybsze niż obiekty wynikowe i mniej zaskakujące niż oba. Nie sugeruję, że try/catch zastąpi if/else, ale wyjątki są właściwym sposobem zgłaszania awarii, nawet w logice biznesowej.

Chciałbym podkreślić, że dwa najczęstsze sposoby istotnego wpływu na wydajność, z którymi się spotkałem, tworzą niepotrzebne obiekty i zagnieżdżone pętle. Jeśli masz wybór między utworzeniem wyjątku, a jego nie tworzeniem, nie twórz wyjątku. Jeśli masz wybór między tworzeniem wyjątku czasami lub tworzeniem innego obiektu przez cały czas, Utwórz wyjątek.

 35
Author: Fuwjax,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2018-07-30 20:31:19

Dodałem odpowiedzi udzielone przez @ Mecki i @ incarnate , bez wypełniania stacktrace dla Javy.

W Javie 7+ możemy używać Throwable(String message, Throwable cause, boolean enableSuppression,boolean writableStackTrace). Ale dla Java6 zobacz moja odpowiedź na to pytanie

// This one will regularly throw one
public void method4(int i) throws NoStackTraceThrowable {
    value = ((value + i) / i) << 1;
    // i & 1 is equally fast to calculate as i & 0xFFFFFFF; it is both
    // an AND operation between two integers. The size of the number plays
    // no role. AND on 32 BIT always ANDs all 32 bits
    if ((i & 0x1) == 1) {
        throw new NoStackTraceThrowable();
    }
}

// This one will regularly throw one
public void method5(int i) throws NoStackTraceRuntimeException {
    value = ((value + i) / i) << 1;
    // i & 1 is equally fast to calculate as i & 0xFFFFFFF; it is both
    // an AND operation between two integers. The size of the number plays
    // no role. AND on 32 BIT always ANDs all 32 bits
    if ((i & 0x1) == 1) {
        throw new NoStackTraceRuntimeException();
    }
}

public static void main(String[] args) {
    int i;
    long l;
    Test t = new Test();

    l = System.currentTimeMillis();
    t.reset();
    for (i = 1; i < 100000000; i++) {
        try {
            t.method4(i);
        } catch (NoStackTraceThrowable e) {
            // Do nothing here, as we will get here
        }
    }
    l = System.currentTimeMillis() - l;
    System.out.println( "method4 took " + l + " ms, result was " + t.getValue() );


    l = System.currentTimeMillis();
    t.reset();
    for (i = 1; i < 100000000; i++) {
        try {
            t.method5(i);
        } catch (RuntimeException e) {
            // Do nothing here, as we will get here
        }
    }
    l = System.currentTimeMillis() - l;
    System.out.println( "method5 took " + l + " ms, result was " + t.getValue() );
}

Wyjście z Java 1.6.0_45, na Core i7, 8GB RAM:

method1 took 883 ms, result was 2
method2 took 882 ms, result was 2
method3 took 32270 ms, result was 2 // throws Exception
method4 took 8114 ms, result was 2 // throws NoStackTraceThrowable
method5 took 8086 ms, result was 2 // throws NoStackTraceRuntimeException

Tak więc metody, które zwracają wartości, są szybsze w porównaniu do metod rzucających wyjątki. IMHO, nie możemy zaprojektować przejrzystego API tylko za pomocą typów zwrotów zarówno dla sukcesu, jak i przepływów błędów. Metody które wyrzuca wyjątki bez stacktrace są 4-5 razy szybsze niż normalne wyjątki.

Edit: NoStackTraceThrowable.java Thanks @ Greg

public class NoStackTraceThrowable extends Throwable { 
    public NoStackTraceThrowable() { 
        super("my special throwable", null, false, false);
    }
}
 16
Author: manikanta,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2018-08-29 05:03:47

Nie wiem, czy te tematy mają związek, ale kiedyś chciałem zaimplementować jedną sztuczkę opierając się na bieżącym wątku stosu trace: chciałem odkryć nazwę metody, która wywołała instancję wewnątrz instancji klasy(yeap, pomysł jest szalony, całkowicie zrezygnowałem z niego). Odkryłem więc, że wywołanie Thread.currentThread().getStackTrace() jest ekstremalnie powolne (ze względu na natywną metodę dumpThreads, której używa wewnętrznie).

Zatem Java Throwable, odpowiednio, ma natywną metodę fillInStackTrace. Myślę, że killer - catch opisany wcześniej blok w jakiś sposób wyzwala wykonanie tej metody.

Ale opowiem ci inną historię... W Scali niektóre funkcje są kompilowane w JVM za pomocą ControlThrowable, która rozszerza Throwable i nadpisuje fillInStackTrace w następujący sposób:
override def fillInStackTrace(): Throwable = this

Więc dostosowałem powyższy test (ilość cykli jest zmniejszona o dziesięć, moja maszyna jest nieco wolniejsza:): {]}

class ControlException extends ControlThrowable

class T {
  var value = 0

  def reset = {
    value = 0
  }

  def method1(i: Int) = {
    value = ((value + i) / i) << 1
    if ((i & 0xfffffff) == 1000000000) {
      println("You'll never see this!")
    }
  }

  def method2(i: Int) = {
    value = ((value + i) / i) << 1
    if ((i & 0xfffffff) == 1000000000) {
      throw new Exception()
    }
  }

  def method3(i: Int) = {
    value = ((value + i) / i) << 1
    if ((i & 0x1) == 1) {
      throw new Exception()
    }
  }

  def method4(i: Int) = {
    value = ((value + i) / i) << 1
    if ((i & 0x1) == 1) {
      throw new ControlException()
    }
  }
}

class Main {
  var l = System.currentTimeMillis
  val t = new T
  for (i <- 1 to 10000000)
    t.method1(i)
  l = System.currentTimeMillis - l
  println("method1 took " + l + " ms, result was " + t.value)

  t.reset
  l = System.currentTimeMillis
  for (i <- 1 to 10000000) try {
    t.method2(i)
  } catch {
    case _ => println("You'll never see this")
  }
  l = System.currentTimeMillis - l
  println("method2 took " + l + " ms, result was " + t.value)

  t.reset
  l = System.currentTimeMillis
  for (i <- 1 to 10000000) try {
    t.method4(i)
  } catch {
    case _ => // do nothing
  }
  l = System.currentTimeMillis - l
  println("method4 took " + l + " ms, result was " + t.value)

  t.reset
  l = System.currentTimeMillis
  for (i <- 1 to 10000000) try {
    t.method3(i)
  } catch {
    case _ => // do nothing
  }
  l = System.currentTimeMillis - l
  println("method3 took " + l + " ms, result was " + t.value)

}

Wyniki są następujące:

method1 took 146 ms, result was 2
method2 took 159 ms, result was 2
method4 took 1551 ms, result was 2
method3 took 42492 ms, result was 2

Widzisz, jedyna różnica między method3 i method4 jest to, że rzucają różne rodzaje WYJĄTKÓW. Tak, method4 jest nadal wolniejszy niż method1 i method2, ale różnica jest o wiele bardziej akceptowalna.

 7
Author: incarnate,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2010-10-20 16:30:21

Myślę, że pierwszy artykuł odnosi się do przechodzenia stosu wywołań i tworzenia śladu stosu jako kosztownej części, i chociaż drugi artykuł tego nie mówi, Myślę, że jest to najdroższa część tworzenia obiektów. John Rose ma Artykuł, w którym opisuje różne techniki przyspieszania WYJĄTKÓW. (Prealokacja i ponowne użycie wyjątku, wyjątki bez śladów stosu, itp.)

Ale mimo to-myślę, że należy to uznać tylko za konieczne zło, ostateczność. Powodem tego jest emulowanie funkcji w innych językach, które nie są (jeszcze) dostępne w JVM. Nie powinieneś mieć zwyczaju używania WYJĄTKÓW do sterowania przepływem. Zwłaszcza ze względu na wydajność! Jak sam wspominasz w #2, ryzykujesz maskowanie poważnych błędów w kodzie w ten sposób, i będzie to trudniejsze do utrzymania dla nowych programistów.

Microbenchmarks w Javie są zaskakująco trudne do poprawnego (powiedziano mi), zwłaszcza gdy na terytorium JIT, więc naprawdę wątpię, że korzystanie z wyjątków jest szybsze niż "powrót" w prawdziwym życiu. Na przykład podejrzewam, że masz gdzieś między 2 a 5 klatkami stosu w teście? Teraz wyobraź sobie, że Twój kod będzie wywoływany przez komponent JSF wdrożony przez JBoss. Teraz możesz mieć ślad stosu, który ma kilka stron.

Może mógłbyś zamieścić swój kod testowy?

 7
Author: Lars Westergren,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2014-08-20 12:10:30

Zrobiłem kilka testów wydajności z JVM 1.5 i używanie WYJĄTKÓW było co najmniej 2x wolniejsze. Średnio: czas wykonania trywialnie małej metody ponad trzykrotnie (3x) z wyjątkami. Trywialnie mała pętla, która musiała złapać wyjątek, odnotowała 2x wzrost czasu własnego.

Widziałem podobne liczby w kodzie produkcyjnym, a także w mikro benchmarkach.

Wyjątki powinny być definitywnie NIE być używane do czegokolwiek, co jest często nazywane. Rzucanie tysiącami z WYJĄTKÓW drugi spowodowałby wielką szyjkę butelki.

Na przykład, używając " Integer.ParseInt(...) "aby znaleźć wszystkie złe wartości w bardzo dużym pliku tekstowym--bardzo zły pomysł. (Widziałem tę metodę użytkową kill wydajność na kodzie produkcyjnym)

Używanie wyjątku do zgłaszania złej wartości w formularzu GUI użytkownika, prawdopodobnie nie tak źle z punktu widzenia wydajności.

Niezależnie od tego, czy jest to dobra praktyka projektowa, wybrałbym regułę: jeśli błąd jest normalny/oczekiwany, użyj zwracana wartość. Jeśli jest nienormalny, użyj wyjątku. Na przykład: odczyt danych wejściowych użytkownika, złe wartości są normalne--użyj kodu błędu. Przekazując wartość do wewnętrznej funkcji narzędzia, złe wartości powinny być filtrowane przez wywołanie kodu--użyj wyjątku.

 6
Author: James Schek,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2008-11-18 16:41:13

Jakiś czas temu napisałem klasę, aby przetestować względną wydajność konwersji łańcuchów do ints przy użyciu dwóch metod: (1) call Integer.parseInt() i wyłapuje wyjątek, lub (2) dopasowuje łańcuch z wyrażeniem regularnym i wywołuje parseInt() tylko wtedy, gdy dopasowanie się powiedzie. Użyłem wyrażenia regularnego w najbardziej efektywny sposób, jaki mogłem (tj. tworząc obiekty Pattern i Matcher przed wejściem w pętlę), i nie wydrukowałem ani nie zapisałem ścieżek stosu z WYJĄTKÓW.

Na listę dziesięciu tysięcy ciągów, jeśli wszystkie były poprawnymi liczbami, podejście parseInt() było cztery razy szybsze niż podejście regex. Ale jeśli tylko 80% ciągów znaków było poprawnych, regex był dwa razy szybszy niż parseInt (). Jeśli 20% było poprawne, co oznacza, że wyjątek został wyrzucony i przechwycony 80% czasu, Wyrażenie regularne było około dwadzieścia razy szybsze niż parseInt ().

Byłem zaskoczony wynikiem, biorąc pod uwagę, że podejście regex przetwarza poprawne ciągi dwukrotnie: raz dla dopasowania i ponownie dla parseInt (). Ale rzucanie i łapanie WYJĄTKÓW bardziej niż to wynagrodziło. Tego rodzaju sytuacja nie występuje zbyt często w świecie rzeczywistym, ale jeśli tak, zdecydowanie nie powinieneś używać techniki łapania WYJĄTKÓW. Ale jeśli walidujesz tylko dane wejściowe użytkownika lub coś w tym stylu, użyj metody parseInt ().

 6
Author: Alan Moore,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2008-11-18 22:27:58

Nawet jeśli rzucanie WYJĄTKÓW nie jest powolne, to i tak złym pomysłem jest rzucanie wyjątków dla normalnego przepływu programu. Używany w ten sposób jest analogiczny do Goto...

To chyba nie odpowiada na pytanie. Wyobrażam sobie ,że 'konwencjonalna' mądrość polegająca na powolnym wyrzucaniu WYJĄTKÓW była prawdziwa we wcześniejszych wersjach Javy (

 3
Author: user38051,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2008-11-18 15:41:29

HotSpot jest w stanie usunąć kod wyjątku dla WYJĄTKÓW generowanych przez system, o ile wszystkie są wbudowane. Jednak jawnie utworzone wyjątki i te, które nie zostały usunięte, spędzają dużo czasu na tworzeniu śladu stosu. Obejdź fillInStackTrace, aby zobaczyć, jak może to wpłynąć na wydajność.

 3
Author: Tom Hawtin - tackline,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2008-11-18 16:07:25

Wydajność WYJĄTKÓW w Javie i C# pozostawia wiele do życzenia.

Jako programiści zmusza to nas do życia zgodnie z zasadą "wyjątki powinny być powodowane rzadko", po prostu ze względów praktycznych.

[2]}jednak jako informatycy powinniśmy buntować się przeciwko temu problematycznemu Państwu. Osoba tworząca funkcję często nie ma pojęcia, jak często będzie wywoływana, lub czy sukces lub porażka jest bardziej prawdopodobna. Tylko rozmówca ma te informacje. Próbując uniknąć wyjątki prowadzą do niejasnych IDOM API, gdzie w niektórych przypadkach mamy tylko czyste, ale wolne wersje wyjątków, a w innych przypadkach mamy szybkie, ale niezgrabne błędy zwracanej wartości, a w jeszcze innych przypadkach kończymy z obydwoma. Implementator biblioteki może być zmuszony do napisania i utrzymania dwóch wersji API, a wywołujący musi zdecydować, którą z dwóch wersji użyć w każdej sytuacji. Co za bałagan. Gdyby wyjątki miały lepszą wydajność, moglibyśmy uniknąć tych niezgrabnych idiomów i używać WYJĄTKÓW tak jak miały być używane... jako ustrukturyzowany system zwrotu błędów.

Chciałbym, aby mechanizmy WYJĄTKÓW były implementowane przy użyciu technik bliższych wartościom zwrotnym, abyśmy mogli mieć wydajność bliższą wartościom zwrotnym.. ponieważ do tego wracamy w kodzie wrażliwym na wydajność.

Oto przykład kodu, który porównuje wydajność WYJĄTKÓW do wydajności błędu zwracanego.

Public class TestIt {

int value;


public int getValue() {
    return value;
}

public void reset() {
    value = 0;
}

public boolean baseline_null(boolean shouldfail, int recurse_depth) {
    if (recurse_depth <= 0) {
        return shouldfail;
    } else {
        return baseline_null(shouldfail,recurse_depth-1);
    }
}

public boolean retval_error(boolean shouldfail, int recurse_depth) {
    if (recurse_depth <= 0) {
        if (shouldfail) {
            return false;
        } else {
            return true;
        }
    } else {
        boolean nested_error = retval_error(shouldfail,recurse_depth-1);
        if (nested_error) {
            return true;
        } else {
            return false;
        }
    }
}

public void exception_error(boolean shouldfail, int recurse_depth) throws Exception {
    if (recurse_depth <= 0) {
        if (shouldfail) {
            throw new Exception();
        }
    } else {
        exception_error(shouldfail,recurse_depth-1);
    }

}

public static void main(String[] args) {
    int i;
    long l;
    TestIt t = new TestIt();
    int failures;

    int ITERATION_COUNT = 100000000;


    // (0) baseline null workload
    for (int recurse_depth = 2; recurse_depth <= 10; recurse_depth+=3) {
        for (float exception_freq = 0.0f; exception_freq <= 1.0f; exception_freq += 0.25f) {            
            int EXCEPTION_MOD = (exception_freq == 0.0f) ? ITERATION_COUNT+1 : (int)(1.0f / exception_freq);            

            failures = 0;
            long start_time = System.currentTimeMillis();
            t.reset();              
            for (i = 1; i < ITERATION_COUNT; i++) {
                boolean shoulderror = (i % EXCEPTION_MOD) == 0;
                t.baseline_null(shoulderror,recurse_depth);
            }
            long elapsed_time = System.currentTimeMillis() - start_time;
            System.out.format("baseline: recurse_depth %s, exception_freqeuncy %s (%s), time elapsed %s ms\n",
                    recurse_depth, exception_freq, failures,elapsed_time);
        }
    }


    // (1) retval_error
    for (int recurse_depth = 2; recurse_depth <= 10; recurse_depth+=3) {
        for (float exception_freq = 0.0f; exception_freq <= 1.0f; exception_freq += 0.25f) {            
            int EXCEPTION_MOD = (exception_freq == 0.0f) ? ITERATION_COUNT+1 : (int)(1.0f / exception_freq);            

            failures = 0;
            long start_time = System.currentTimeMillis();
            t.reset();              
            for (i = 1; i < ITERATION_COUNT; i++) {
                boolean shoulderror = (i % EXCEPTION_MOD) == 0;
                if (!t.retval_error(shoulderror,recurse_depth)) {
                    failures++;
                }
            }
            long elapsed_time = System.currentTimeMillis() - start_time;
            System.out.format("retval_error: recurse_depth %s, exception_freqeuncy %s (%s), time elapsed %s ms\n",
                    recurse_depth, exception_freq, failures,elapsed_time);
        }
    }

    // (2) exception_error
    for (int recurse_depth = 2; recurse_depth <= 10; recurse_depth+=3) {
        for (float exception_freq = 0.0f; exception_freq <= 1.0f; exception_freq += 0.25f) {            
            int EXCEPTION_MOD = (exception_freq == 0.0f) ? ITERATION_COUNT+1 : (int)(1.0f / exception_freq);            

            failures = 0;
            long start_time = System.currentTimeMillis();
            t.reset();              
            for (i = 1; i < ITERATION_COUNT; i++) {
                boolean shoulderror = (i % EXCEPTION_MOD) == 0;
                try {
                    t.exception_error(shoulderror,recurse_depth);
                } catch (Exception e) {
                    failures++;
                }
            }
            long elapsed_time = System.currentTimeMillis() - start_time;
            System.out.format("exception_error: recurse_depth %s, exception_freqeuncy %s (%s), time elapsed %s ms\n",
                    recurse_depth, exception_freq, failures,elapsed_time);              
        }
    }
}

}

A oto wyniki:

baseline: recurse_depth 2, exception_freqeuncy 0.0 (0), time elapsed 683 ms
baseline: recurse_depth 2, exception_freqeuncy 0.25 (0), time elapsed 790 ms
baseline: recurse_depth 2, exception_freqeuncy 0.5 (0), time elapsed 768 ms
baseline: recurse_depth 2, exception_freqeuncy 0.75 (0), time elapsed 749 ms
baseline: recurse_depth 2, exception_freqeuncy 1.0 (0), time elapsed 731 ms
baseline: recurse_depth 5, exception_freqeuncy 0.0 (0), time elapsed 923 ms
baseline: recurse_depth 5, exception_freqeuncy 0.25 (0), time elapsed 971 ms
baseline: recurse_depth 5, exception_freqeuncy 0.5 (0), time elapsed 982 ms
baseline: recurse_depth 5, exception_freqeuncy 0.75 (0), time elapsed 947 ms
baseline: recurse_depth 5, exception_freqeuncy 1.0 (0), time elapsed 937 ms
baseline: recurse_depth 8, exception_freqeuncy 0.0 (0), time elapsed 1154 ms
baseline: recurse_depth 8, exception_freqeuncy 0.25 (0), time elapsed 1149 ms
baseline: recurse_depth 8, exception_freqeuncy 0.5 (0), time elapsed 1133 ms
baseline: recurse_depth 8, exception_freqeuncy 0.75 (0), time elapsed 1117 ms
baseline: recurse_depth 8, exception_freqeuncy 1.0 (0), time elapsed 1116 ms
retval_error: recurse_depth 2, exception_freqeuncy 0.0 (0), time elapsed 742 ms
retval_error: recurse_depth 2, exception_freqeuncy 0.25 (24999999), time elapsed 743 ms
retval_error: recurse_depth 2, exception_freqeuncy 0.5 (49999999), time elapsed 734 ms
retval_error: recurse_depth 2, exception_freqeuncy 0.75 (99999999), time elapsed 723 ms
retval_error: recurse_depth 2, exception_freqeuncy 1.0 (99999999), time elapsed 728 ms
retval_error: recurse_depth 5, exception_freqeuncy 0.0 (0), time elapsed 920 ms
retval_error: recurse_depth 5, exception_freqeuncy 0.25 (24999999), time elapsed 1121   ms
retval_error: recurse_depth 5, exception_freqeuncy 0.5 (49999999), time elapsed 1037 ms
retval_error: recurse_depth 5, exception_freqeuncy 0.75 (99999999), time elapsed 1141   ms
retval_error: recurse_depth 5, exception_freqeuncy 1.0 (99999999), time elapsed 1130 ms
retval_error: recurse_depth 8, exception_freqeuncy 0.0 (0), time elapsed 1218 ms
retval_error: recurse_depth 8, exception_freqeuncy 0.25 (24999999), time elapsed 1334  ms
retval_error: recurse_depth 8, exception_freqeuncy 0.5 (49999999), time elapsed 1478 ms
retval_error: recurse_depth 8, exception_freqeuncy 0.75 (99999999), time elapsed 1637 ms
retval_error: recurse_depth 8, exception_freqeuncy 1.0 (99999999), time elapsed 1655 ms
exception_error: recurse_depth 2, exception_freqeuncy 0.0 (0), time elapsed 726 ms
exception_error: recurse_depth 2, exception_freqeuncy 0.25 (24999999), time elapsed 17487   ms
exception_error: recurse_depth 2, exception_freqeuncy 0.5 (49999999), time elapsed 33763   ms
exception_error: recurse_depth 2, exception_freqeuncy 0.75 (99999999), time elapsed 67367   ms
exception_error: recurse_depth 2, exception_freqeuncy 1.0 (99999999), time elapsed 66990 ms
exception_error: recurse_depth 5, exception_freqeuncy 0.0 (0), time elapsed 924 ms
exception_error: recurse_depth 5, exception_freqeuncy 0.25 (24999999), time elapsed 23775  ms
exception_error: recurse_depth 5, exception_freqeuncy 0.5 (49999999), time elapsed 46326 ms
exception_error: recurse_depth 5, exception_freqeuncy 0.75 (99999999), time elapsed 91707 ms
exception_error: recurse_depth 5, exception_freqeuncy 1.0 (99999999), time elapsed 91580 ms
exception_error: recurse_depth 8, exception_freqeuncy 0.0 (0), time elapsed 1144 ms
exception_error: recurse_depth 8, exception_freqeuncy 0.25 (24999999), time elapsed 30440 ms
exception_error: recurse_depth 8, exception_freqeuncy 0.5 (49999999), time elapsed 59116   ms
exception_error: recurse_depth 8, exception_freqeuncy 0.75 (99999999), time elapsed 116678 ms
exception_error: recurse_depth 8, exception_freqeuncy 1.0 (99999999), time elapsed 116477 ms

Sprawdzanie i propagowanie wartości zwrotnych dodaje trochę kosztu w porównaniu z wywołaniem baseline-null, a koszt ten jest proporcjonalny do głębokości wywołania. Przy głębokości łańcucha wywołań 8, wersja sprawdzająca wartość błędu była o około 27% wolniejsza niż wersja basline, która nie sprawdzała wartości zwracanych.

Wydajność wyjątków, dla porównania, nie jest funkcją głębokości wywołania, ale częstotliwości WYJĄTKÓW. Jednak degradacja wraz ze wzrostem częstotliwości wyjątków jest znacznie bardziej dramatyczna. Przy zaledwie 25% częstotliwości błędów kod działał 24 razy wolniej. Przy częstotliwości błędu 100% wersja exception jest prawie 100-krotnie wolniejsza.

Sugeruje mi to, że być może dokonują niewłaściwych kompromisów w naszych implementacjach WYJĄTKÓW. Wyjątki mogą być szybsze, albo przez unikanie kosztownych spacerów po łodydze,albo przez zamianę ich w sprawdzanie wartości zwracanej przez kompilator. Dopóki tego nie zrobią, unikamy ich, gdy chcemy, aby nasz kod działał szybko.

 3
Author: David Jeske,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2012-03-18 22:01:15

Po prostu porównaj powiedzmy liczbę całkowitą.parseInt do następującej metody, która po prostu zwraca wartość domyślną w przypadku nieparsowalnych danych zamiast rzucania wyjątku:

  public static int parseUnsignedInt(String s, int defaultValue) {
    final int strLength = s.length();
    if (strLength == 0)
      return defaultValue;
    int value = 0;
    for (int i=strLength-1; i>=0; i--) {
      int c = s.charAt(i);
      if (c > 47 && c < 58) {
        c -= 48;
        for (int j=strLength-i; j!=1; j--)
          c *= 10;
        value += c;
      } else {
        return defaultValue;
      }
    }
    return value < 0 ? /* übergebener wert > Integer.MAX_VALUE? */ defaultValue : value;
  }

Tak długo, jak zastosujesz obie metody do "ważnych" danych, obie będą działać w przybliżeniu z tą samą szybkością (nawet jeśli liczba całkowita.parseInt radzi sobie z bardziej złożonymi danymi). Ale jak tylko spróbujesz parsować nieprawidłowe dane (np. parsować "abc" 1.000.000 razy), różnica w wydajności powinna być niezbędne.

 2
Author: inflamer,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2011-01-04 17:26:57

Zmieniłem powyższą odpowiedź @Mecki, aby method1 zwracała wartość logiczną i czek w wywołującej metodzie, ponieważ nie można zastąpić wyjątku niczym. Po dwóch biegach method1 była nadal najszybsza lub równie szybka jak method2.

Oto migawka kodu:

// Calculates without exception
public boolean method1(int i) {
    value = ((value + i) / i) << 1;
    // Will never be true
    return ((i & 0xFFFFFFF) == 1000000000);

}
....
   for (i = 1; i < 100000000; i++) {
            if (t.method1(i)) {
                System.out.println("Will never be true!");
            }
    }

I wyniki:

Run 1

method1 took 841 ms, result was 2
method2 took 841 ms, result was 2
method3 took 85058 ms, result was 2

Bieg 2

method1 took 821 ms, result was 2
method2 took 838 ms, result was 2
method3 took 85929 ms, result was 2
 0
Author: inder,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2011-11-08 17:55:39

Świetny post o wydajności WYJĄTKÓW to:

Https://shipilev.net/blog/2014/exceptional-performance/

Instantiating vs reusing existing, with stack trace and without, etc:

Benchmark                            Mode   Samples         Mean   Mean error  Units

dynamicException                     avgt        25     1901.196       14.572  ns/op
dynamicException_NoStack             avgt        25       67.029        0.212  ns/op
dynamicException_NoStack_UsedData    avgt        25       68.952        0.441  ns/op
dynamicException_NoStack_UsedStack   avgt        25      137.329        1.039  ns/op
dynamicException_UsedData            avgt        25     1900.770        9.359  ns/op
dynamicException_UsedStack           avgt        25    20033.658      118.600  ns/op

plain                                avgt        25        1.259        0.002  ns/op
staticException                      avgt        25        1.510        0.001  ns/op
staticException_NoStack              avgt        25        1.514        0.003  ns/op
staticException_NoStack_UsedData     avgt        25        4.185        0.015  ns/op
staticException_NoStack_UsedStack    avgt        25       19.110        0.051  ns/op
staticException_UsedData             avgt        25        4.159        0.007  ns/op
staticException_UsedStack            avgt        25       25.144        0.186  ns/op

W zależności od głębokości śledzenia stosu:

Benchmark        Mode   Samples         Mean   Mean error  Units

exception_0000   avgt        25     1959.068       30.783  ns/op
exception_0001   avgt        25     1945.958       12.104  ns/op
exception_0002   avgt        25     2063.575       47.708  ns/op
exception_0004   avgt        25     2211.882       29.417  ns/op
exception_0008   avgt        25     2472.729       57.336  ns/op
exception_0016   avgt        25     2950.847       29.863  ns/op
exception_0032   avgt        25     4416.548       50.340  ns/op
exception_0064   avgt        25     6845.140       40.114  ns/op
exception_0128   avgt        25    11774.758       54.299  ns/op
exception_0256   avgt        25    21617.526      101.379  ns/op
exception_0512   avgt        25    42780.434      144.594  ns/op
exception_1024   avgt        25    82839.358      291.434  ns/op

Aby dowiedzieć się więcej (w tym asemblera x64 z JIT) przeczytaj oryginalny wpis na blogu.

To oznacza, że Hibernate / Spring / etc-EE-shit są powolne z powodu WYJĄTKÓW (xD) i przepisywanie kontroli aplikacji z dala od WYJĄTKÓW (zastąp go continure / break i zwracanie boolean FLAG jak w C z wywołania metody) poprawiają wydajność Twojej aplikacji 10X-100X, w zależności jak często je rzucasz))

 0
Author: gavenkoa,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2018-03-21 19:38:56

Moja opinia o prędkości wyjątków a programowym sprawdzaniu danych.

Wiele klas miało konwerter łańcuchów do wartości (skaner / parser), szanowane i dobrze znane biblioteki;)

Zwykle ma formę

class Example {
public static Example Parse(String input) throws AnyRuntimeParsigException
...
}

Nazwa wyjątku to tylko przykład, zwykle nie jest zaznaczona (runtime) , więc deklaracja throws to tylko mój obrazek

Czasami istnieje druga forma:

public static Example Parse(String input, Example defaultValue)

Nigdy nie rzucaj

Gdy drugi ins nie jest dostępny (lub programista czyta zbyt mniej dokumentów i używać tylko najpierw), napisać taki kod za pomocą wyrażenia regularnego. Wyrażenia regularne są fajne, poprawne politycznie itp:

Xxxxx.regex(".....pattern", src);
if(ImTotallySure)
{
  Example v = Example.Parse(src);
}

Z tym kodem programiści nie kosztują WYJĄTKÓW. Ale ma porównywalny bardzo wysoki koszt wyrażeń regularnych zawsze w porównaniu z małym kosztem WYJĄTKÓW czasami.

Używam prawie zawsze w takim kontekście

try { parse } catch(ParsingException ) // concrete exception from javadoc
{
}
Bez analizy stacktrace itp. uważam, że po twoich wykładach dość szybko.

Nie bój się WYJĄTKÓW

 -3
Author: Jacek Cz,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2015-09-12 18:00:04

Dlaczego wyjątki powinny być wolniejsze niż zwykłe zwroty?

Dopóki nie wydrukujesz stosu na terminalu, nie zapiszesz go do pliku lub czegoś podobnego, blok catch-block nie wykonuje więcej pracy niż inne bloki kodu. Więc nie mogę sobie wyobrazić, dlaczego "throw new my_cool_error ()" powinno być tak powolne.

Dobre pytanie i czekam na dalsze informacje na ten temat!

 -5
Author: qualbeen,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2013-09-28 12:48:18