Право на анализ выхода / распределение стека с помощью Java 7 - PullRequest
10 голосов
/ 27 января 2012

Я выполняю некоторые тесты с escape-анализом в Java 7, чтобы лучше понять, какие объекты имеют право на выделение стека.

Вот код, который я написал для тестирования выделения стека:

import java.util.ArrayList;
import java.util.Iterator;


public class EscapeAnalysis {

    private static final long TIME_TO_TEST = 10L * 1000L; // 10s

    static class Timestamp {
        private long millis;
        public Timestamp(long millis) {
            this.millis = millis;
        }
        public long getTime() {
            return millis;
        }
        public void setTime(long time) {
            millis = time;
        }
    }

    public static void main(String[] args) {
        long r = 0;
        System.out.println("test1");
        r += test1();
        System.out.println("test2");
        r += test2();
        System.out.println("test3");
        r += test3();
        System.out.println("test4");
        r += test4();
        System.out.println("test5");
        r += test5();
        System.out.println("test6");
        r += test6();
        System.out.println(r);
    }

    public static long test1() {
        long r = 0;
        long start = System.currentTimeMillis();
        while (System.currentTimeMillis() - start < TIME_TO_TEST) {
            r += new Timestamp(System.currentTimeMillis()).getTime();
        }
        return r;
    }

    public static long test2() {
        ArrayList<Integer> l = new ArrayList<Integer>(1000);
        for (int i = 0; i < 1000; ++i) {
            l.add(i);
        }
        long r = 0;
        long start = System.currentTimeMillis();
        while (System.currentTimeMillis() - start < TIME_TO_TEST) {
            for (Iterator<Integer> it = l.iterator(); it.hasNext(); ) {
                r += it.next().longValue();
            }
        }
        return r;
    }

    public static long test3() {
        long r = 0;
        long start = System.currentTimeMillis();
        while (System.currentTimeMillis() - start < TIME_TO_TEST) {
            Timestamp ts = new Timestamp(System.currentTimeMillis());
            ts.setTime(42);
            r += ts.getTime();
        }
        return r;
    }

    public static long test4() {
        ArrayList<Integer> l = new ArrayList<Integer>(1000);
        for (int i = 0; i < 1000; ++i) {
            l.add(i);
        }
        long r = 0;
        long start = System.currentTimeMillis();
        while (System.currentTimeMillis() - start < TIME_TO_TEST) {
            Iterator<Integer> it = l.iterator();
            r += it.next().longValue();
            r += it.next().longValue();
            r += it.next().longValue();
            r += it.next().longValue();
        }
        return r;
    }

    public static long test5() {
        ArrayList<Integer> l = new ArrayList<Integer>(1000);
        for (int i = 0; i < 1000; ++i) {
            l.add(i);
        }
        long r = 0;
        long start = System.currentTimeMillis();
        while (System.currentTimeMillis() - start < TIME_TO_TEST) {
            Iterator<Integer> it = l.iterator();
            for (int i = 0; i < l.size(); ++i) {
                r += it.next().longValue();
            }
        }
        return r;
    }

    public static long test6() {
        long r = 0;
        long start = System.currentTimeMillis();
        while (System.currentTimeMillis() - start < TIME_TO_TEST) {
            for (Timestamp ts = new Timestamp(System.currentTimeMillis());
                    ts.getTime() > 0;
                    ts.setTime(ts.getTime() + System.currentTimeMillis())) {
                r += ts.getTime();
            }
        }
        return r;
    }

}

И вот что он выводит с Java 7 в Linux

java -server -version                                                 
java version "1.7.0_02"
Java(TM) SE Runtime Environment (build 1.7.0_02-b13)
Java HotSpot(TM) 64-Bit Server VM (build 22.0-b10, mixed mode)

java -server -verbose:gc -XX:CompileThreshold=1 -cp bin EscapeAnalysis
test1
test2
[GC 15616K->352K(59776K), 0,0014270 secs]
[GC 15968K->288K(59776K), 0,0011790 secs]
[GC 15904K->288K(59776K), 0,0018170 secs]
[GC 15904K->288K(59776K), 0,0011100 secs]
[GC 15904K->288K(57152K), 0,0019790 secs]
[GC 15520K->320K(56896K), 0,0011670 secs]
[GC 15232K->284K(56256K), 0,0011440 secs]
test3
test4
test5
[GC 14876K->348K(55936K), 0,0005340 secs]
[GC 14620K->348K(56000K), 0,0004560 secs]
[GC 14300K->316K(55296K), 0,0004680 secs]
[GC 13948K->316K(55488K), 0,0003590 secs]
[GC 13692K->316K(54784K), 0,0004580 secs]
[GC 13436K->316K(54976K), 0,0005430 secs]
[GC 13180K->316K(54272K), 0,0004500 secs]
[GC 12924K->316K(54464K), 0,0005090 secs]
[GC 12668K->316K(53760K), 0,0004490 secs]
[GC 12412K->316K(53888K), 0,0004350 secs]
[GC 12156K->316K(53312K), 0,0005060 secs]
test6
6737499643744733086

Я использую журналы GC, чтобы узнать, были ли объекты размещены в стеке (идея из Escapeанализ в Java ), который не может быть на 100% надежным, но, кажется, дает хорошие подсказки.

Опираясь на вывод, распределение стека работает для test1, test3, test4 и test6 и не работает для test2и тест5.Я не понимаю, почему это не работает с итератором в цикле for, хотя он работает

  • с итератором вне цикла for (см. Test4),
  • сдругой объект внутри цикла for (см. test6).

Я прочитал код для итератора ArrayList , и я не понимаю, почему он не будет иметь право на стекраспределение в тестах 2 и 5, поскольку оно не экранирует ни текущий метод, ни текущий поток.

Есть идеи?

Ответы [ 3 ]

8 голосов
/ 27 января 2012

EA - это то, что анализирует компилятор C2 на основе IR, который он генерирует, поэтому вам необходимо скомпилировать метод, чтобы воспользоваться преимуществами.Каждый тест вызывается только один раз, поэтому у него нет шансов для его компиляции.Подробная информация о EA и C2 IR в вики внутренней части горячей точки (https://wikis.oracle.com/display/HotSpotInternals/Overview+of+Ideal,+C2's+high+level+intermediate+representation и https://wikis.oracle.com/display/HotSpotInternals/EscapeAnalysis)

) Вот версия, которая пытается показать влияние

import com.sun.management.ThreadMXBean;

import java.lang.management.ManagementFactory;
import java.util.ArrayList;
import java.util.Iterator;


public class EscapeAnalysisTest {

    private static final long TIME_TO_TEST = 10L * 1000L; // 10s

    static class Timestamp {
        private long millis;

        public Timestamp(long millis) {
            this.millis = millis;
        }

        public long getTime() {
            return millis;
        }

        public void setTime(long time) {
            millis = time;
        }
    }

    public static void main(String[] args) {
        System.out.println("****");
        doIt();
        System.out.println("****");
        doIt();
        System.out.println("****");
        doIt();
        System.out.println("****");
        doIt();
        System.out.println("****");
    }

    private static void doIt() {
        final ThreadMXBean mxbean = (ThreadMXBean) ManagementFactory.getThreadMXBean();
        final long tid = Thread.currentThread().getId();
        long r = 0;
        final long allocPre = mxbean.getThreadAllocatedBytes(tid);
        r += test1();
        long alloc1 = mxbean.getThreadAllocatedBytes(tid);
        System.out.println("test1 - " + (alloc1 - allocPre));
        r += test2();
        final long alloc2 = mxbean.getThreadAllocatedBytes(tid);
        System.out.println("test2 - " + (alloc2 - alloc1));
        r += test3();
        final long alloc3 = mxbean.getThreadAllocatedBytes(tid);
        System.out.println("test3 - " + (alloc3 - alloc2));
        r += test4();
        final long alloc4 = mxbean.getThreadAllocatedBytes(tid);
        System.out.println("test4 - " + (alloc4 - alloc3));
        r += test5();
        final long alloc5 = mxbean.getThreadAllocatedBytes(tid);
        System.out.println("test5 - " + (alloc5 - alloc4));
        r += test6();
        final long alloc6 = mxbean.getThreadAllocatedBytes(tid);
        System.out.println("test6 - " + (alloc6 - alloc5));
        System.out.println(r);
    }

    public static long test1() {
        long r = 0;
        long start = System.currentTimeMillis();
        while (System.currentTimeMillis() - start < TIME_TO_TEST) {
            r += new Timestamp(System.currentTimeMillis()).getTime();
        }
        return r;
    }

    public static long test2() {
        ArrayList<Integer> l = new ArrayList<Integer>(1000);
        for (int i = 0; i < 1000; ++i) {
            l.add(i);
        }
        long r = 0;
        long start = System.currentTimeMillis();
        while (System.currentTimeMillis() - start < TIME_TO_TEST) {
            for (Iterator<Integer> it = l.iterator(); it.hasNext(); ) {
                r += it.next().longValue();
            }
        }
        return r;
    }

    public static long test3() {
        long r = 0;
        long start = System.currentTimeMillis();
        while (System.currentTimeMillis() - start < TIME_TO_TEST) {
            Timestamp ts = new Timestamp(System.currentTimeMillis());
            ts.setTime(42);
            r += ts.getTime();
        }
        return r;
    }

    public static long test4() {
        ArrayList<Integer> l = new ArrayList<Integer>(1000);
        for (int i = 0; i < 1000; ++i) {
            l.add(i);
        }
        long r = 0;
        long start = System.currentTimeMillis();
        while (System.currentTimeMillis() - start < TIME_TO_TEST) {
            Iterator<Integer> it = l.iterator();
            r += it.next().longValue();
            r += it.next().longValue();
            r += it.next().longValue();
            r += it.next().longValue();
        }
        return r;
    }

    public static long test5() {
        ArrayList<Integer> l = new ArrayList<Integer>(1000);
        for (int i = 0; i < 1000; ++i) {
            l.add(i);
        }
        long r = 0;
        long start = System.currentTimeMillis();
        while (System.currentTimeMillis() - start < TIME_TO_TEST) {
            Iterator<Integer> it = l.iterator();
            for (int i = 0; i < l.size(); ++i) {
                r += it.next().longValue();
            }
        }
        return r;
    }

    public static long test6() {
        long r = 0;
        long start = System.currentTimeMillis();
        while (System.currentTimeMillis() - start < TIME_TO_TEST) {
            for (Timestamp ts = new Timestamp(System.currentTi());
                 ts.getTime() > 0;
                 ts.setTime(ts.getTime() + System.currentTimeMillis())) {
                r += ts.getTime();
            }
        }
        return r;
    }

}

, которое генерируетследующий вывод при запуске с -server -XX:CompileThreshold=1

****
test1 - 109048
test2 - 89243416
test3 - 16664
test4 - 42840
test5 - 71982168
test6 - 1400
-5351026995119026839
****
test1 - 16432
test2 - 85921464
test3 - 16664
test4 - 42840
test5 - 66777600
test6 - 1368
7844020592566674506
****
test1 - 48
test2 - 18256
test3 - 272
test4 - 18264
test5 - 18264
test6 - 272
-2137858376905291730
****
test1 - 48
test2 - 18256
test3 - 272
test4 - 18264
test5 - 18264
test6 - 272
3273987624143297143
****

одна опасность здесь состоит в том, что компиляция этого метода изменила его более фундаментально, я не пытался защититься от этого, поэтому некоторое использование LogCompilation илиPrintCompilation может потребоваться проверить.

2 голосов
/ 25 марта 2017

Я только что исследовал то же самое, но для Java 8. Я поместил свой ответ в дубликат вопроса, так как я не нашел его вовремя.

Резюме от полный ответ :

Прежде всего, это зависит от реализации. Этот ответ относится к OpenJDK 1.8 и, возможно, также к Oracle JVM 1.8.

Во-вторых, как уже говорили другие, распределение стека происходит только тогда, когда метод компилируется компилятором C2, что происходит только после того, как метод был вызван достаточно раз.

Если это так, объекты могут быть размещены в стеке, если

  • все вызовы методов, которые его используют, являются встроенными
  • оно никогда не присваивается никаким статическим или объектным полям, только локальным переменным (параметры для встроенных вызовов методов становятся локальными переменными)
  • в каждой точке программы, локальные переменные которой содержат ссылки на объект, должны определяться во времени JIT и не зависеть от непредсказуемого потока условного управления.
  • Если объект является массивом, его размер должен быть постоянным времени JIT, а для индексации в нем должны использоваться постоянные времени JIT.

Встраивание особенно непредсказуемо, если вы не знаете некоторые специфические особенности Горячей точки. Смотрите связанный ответ для некоторых деталей.

Редактировать: я пытался запустить ваш тест на Java 8 (OpenJDK), и там все встроено. Таким образом, существуют различия в распределении стека между Java 7 и 8.

2 голосов
/ 08 мая 2012

Escape-анализ в значительной степени опирается на встраивание вызовов функций.

Как и в случае с любым другим микробенчмарком - особенно на виртуальной машине сервера - требуется прогрев.Если вы удалите -XX:CompileThreshold=1 и выполните свой основной тест в цикле, вы заметите, что после 1-2 итераций он прекратит сбор мусора, потому что компилятор собрал достаточно информации для профилирования, чтобы встроить методы, а затем выполнить escape-анализ.

...