Przejdź do treści

Stoper i precyzyjny pomiar czasu

  • Data i czas
  • poziom: średni
  • aktualizacja: 16 sierpnia 2026

Odejmowanie dwóch Instant.now() działa do pierwszej korekty zegara systemowego — wtedy operacja potrafi „trwać” minus dwie sekundy. Odstępy mierz przez System.nanoTime(): to zegar monotoniczny, którego nikt nie cofnie w połowie pomiaru. Instant zostaw do znaczników kalendarzowych, czyli do pytania „kiedy to się wydarzyło”. Klasa poniżej zamyka tę zasadę w stoperze, który zatrzymasz i wznowisz bez gubienia zebranego czasu, oraz w metodzie zmierz, która oddaje Ci naraz wynik operacji i czas jej wykonania. Wszystko wraca jako Duration, więc nie musisz pamiętać, czy ta liczba to sekundy, milisekundy czy nanosekundy.

JAVA PomiarCzasu.java 130 linii Pobierz .java
import java.time.Duration;
import java.time.Instant;
import java.util.Locale;
import java.util.function.Supplier;
import java.util.stream.LongStream;

/**
 * Stoper i pomiar czasu wykonania — skryptyJAVA.pl
 * Zapisz jako: PomiarCzasu.java
 */
public final class PomiarCzasu {

    private static final Locale PL = Locale.forLanguageTag("pl-PL");

    /** Łączy wartość zwróconą przez operację z czasem jej wykonania. */
    public record Wynik<T>(T wartosc, Duration czas) {}

    /** Klasa narzędziowa — obiektów nie potrzebuje. */
    private PomiarCzasu() {}

    /** Mierzy funkcję zegarem monotonicznym i nie gubi zwróconej wartości. */
    public static <T> Wynik<T> zmierz(Supplier<T> operacja) {
        long poczatek = System.nanoTime();
        T wartosc = operacja.get();
        long koniec = System.nanoTime();
        return new Wynik<>(wartosc, Duration.ofNanos(koniec - poczatek));
    }

    /** Mierzy operację bez wyniku: zapis do bazy, wysyłkę pliku, czyszczenie cache. */
    public static Duration zmierz(Runnable operacja) {
        long poczatek = System.nanoTime();
        operacja.run();
        return Duration.ofNanos(System.nanoTime() - poczatek);
    }

    /** Dobiera jednostkę do skali wyniku: nanosekundy, milisekundy, sekundy albo mm:ss. */
    public static String formatuj(Duration czas) {
        if (czas.isNegative()) {
            return "-" + formatuj(czas.negated());
        }
        long sekundy = czas.getSeconds();
        if (sekundy >= 60) {
            return String.format(PL, "%d:%02d min", sekundy / 60, sekundy % 60);
        }
        if (sekundy >= 1) {
            return String.format(PL, "%.3f s", sekundy + czas.getNano() / 1_000_000_000.0);
        }
        if (czas.getNano() < 1_000_000) {
            return czas.getNano() + " ns";
        }
        return String.format(PL, "%.3f ms", czas.getNano() / 1_000_000.0);
    }

    /** Stoper, który można zatrzymać, odczytać i ponownie uruchomić. */
    public static final class Stoper {
        private long startNano;
        private long zebraneNano;
        private boolean uruchomiony;

        private Stoper() {}

        /** Tworzy i natychmiast uruchamia nowy stoper. */
        public static Stoper uruchom() {
            Stoper stoper = new Stoper();
            stoper.wznow();
            return stoper;
        }

        /** Wznawia pomiar bez kasowania wcześniej zebranego czasu. */
        public void wznow() {
            if (uruchomiony) {
                throw new IllegalStateException("Stoper jest już uruchomiony.");
            }
            startNano = System.nanoTime();
            uruchomiony = true;
        }

        /** Zatrzymuje pomiar i zwraca łączny czas wszystkich odcinków. */
        public Duration zatrzymaj() {
            if (!uruchomiony) {
                throw new IllegalStateException("Stoper jest już zatrzymany.");
            }
            zebraneNano += System.nanoTime() - startNano;
            uruchomiony = false;
            return odczytaj();
        }

        /** Zwraca aktualny wynik bez zatrzymywania stopera. */
        public Duration odczytaj() {
            long teraz = uruchomiony ? System.nanoTime() - startNano : 0L;
            return Duration.ofNanos(zebraneNano + teraz);
        }

        /** Kasuje zebrany czas i nie rusza stanu: biegnący stoper liczy dalej od zera. */
        public void wyzeruj() {
            zebraneNano = 0L;
            startNano = System.nanoTime();
        }
    }

    private static long sumaKwadratow(int limit) {
        return LongStream.rangeClosed(1, limit).map(liczba -> liczba * liczba).sum();
    }

    // ── PRZYKŁADY ───────────────────────────────────────────
    public static void main(String[] args) throws InterruptedException {
        Instant znacznikStartu = Instant.now();
        Thread.sleep(30);
        Instant znacznikKonca = Instant.now();
        System.out.println("Od " + znacznikStartu + " do " + znacznikKonca);
        // Samo Duration.toString() dałoby zapis ISO-8601 (PT0.03S) — człowiekowi pokaż formatuj().
        System.out.println("Odstęp: " + formatuj(Duration.between(znacznikStartu, znacznikKonca)));

        Wynik<Long> wynik = zmierz(() -> sumaKwadratow(2_000_000));
        System.out.println("Suma: " + wynik.wartosc() + ", obliczenia: " + formatuj(wynik.czas()));
        System.out.println("Wypchnięcie bufora: " + formatuj(zmierz(() -> System.out.flush())));

        Stoper stoper = Stoper.uruchom();
        Thread.sleep(20);
        System.out.println("Odczyt w biegu: " + formatuj(stoper.odczytaj()));
        stoper.zatrzymaj();
        Thread.sleep(10); // Pauza między odcinkami nie jest doliczana.
        stoper.wznow();
        Thread.sleep(20);
        System.out.println("Dwa odcinki: " + formatuj(stoper.zatrzymaj()));
        stoper.wyzeruj();
        System.out.println("Po wyzerowaniu: " + formatuj(stoper.odczytaj()));
        System.out.println("Dłuższy odcinek: " + formatuj(Duration.ofMillis(93_500)));
    }
}

Jak użyć tego skryptu

  1. Zapisz kod w pliku PomiarCzasu.java (w Javie nazwa pliku idzie za nazwą klasy publicznej) i uruchom przez java PomiarCzasu.java. Zobaczysz znaczniki kalendarzowe, pomiar obliczeń i stoper z pauzą.
  2. Funkcję zwracającą wartość mierz tak: Wynik<Dane> wynik = PomiarCzasu.zmierz(() -> wczytajDane()) — dostajesz osobno wartosc() i czas(). Operację, która nic nie zwraca (zapis, wysyłka), przekaż tak samo i odbierz samo Duration.
  3. Przy kilku etapach utwórz Stoper.uruchom(), w trakcie zaglądaj przez odczytaj(), a przerwy rób parą zatrzymaj() i wznow(). Metoda wyzeruj() kasuje zebrany czas i nie rusza stanu — biegnący stoper liczy dalej od zera.
  4. Trzymaj czas jako Duration i dopiero na granicy aplikacji zamieniaj go na liczbę: toMillis(), toSeconds() albo formatuj, które samo dobiera jednostkę do rzędu wielkości.

Warto wiedzieć

  • Instant.now() i System.currentTimeMillis() opisują czas kalendarzowy, który system może w każdej chwili skorygować. Do czasu trwania używaj różnicy dwóch odczytów System.nanoTime(); jego wartości bezwzględnej nie zapisuj jako daty — sama z siebie nic nie znaczy.
  • Nazwa nanoTime opisuje jednostkę wyniku, a nie gwarantowaną rozdzielczość zegara. Na danym systemie kolejne odczyty mogą różnić się o więcej niż jedną nanosekundę.
  • Pojedynczy pomiar nie jest wiarygodnym benchmarkiem Javy: wpływają na niego rozgrzewanie JIT, odśmiecanie pamięci i inne procesy. Do porównywania wydajności produkcyjnego kodu użyj JMH z rozgrzewką i wieloma iteracjami.
  • Jeśli mierzona lambda rzuci wyjątek, zmierz przepuści go dalej i czasu nie zobaczysz — w monitoringu produkcyjnym odczytuj stoper w bloku finally, żeby rejestrować także nieudane operacje. Samego Stopera nie współdziel między wątkami: jego pola nie są synchronizowane, więc każdy wątek potrzebuje własnego obiektu.
#java.time #formatowanie #wydajność #lambda #benchmark

Podobne skrypty