Допустим, у нас есть класс
Vector2D
, типа такого:public static final class Vector2D { private double x; private double y; public Vector2D( final double x, final double y ) { this.x = x; this.y = y; } public Vector2D add( final Vector2D other ) { return new Vector2D( x + other.x, y + other.y ); } public void addAccumulate( final Vector2D other ) { x = x + other.x; y = y + other.y; } public double dot( final Vector2D other ) { return x * other.x + y * other.y; } public double length() { return Math.sqrt( this.dot( this ) ); } }(Конкретные детали не важны, нужен класс без сложной логики и коллекций внутри)
...И мы пишем такой код:
private double allocateOnce( final ThreadLocalRandom rnd ) { return new Vector2D( rnd.nextDouble(), 3.8 ) .add( new Vector2D( 1.5, 3.4 ) ) .dot( new Vector2D( 1.9, 14.3 ) ); }Скаляризует ли JIT эти 3 объекта?
Ответ: да, скаляризует (по крайней мере, на тестируемых мной jdk 1.7.0_25 и 1.8.0_73).
Это ожидаемо, но слишком просто. Давайте добавим немного жизни:
private double allocateInLoop( final ThreadLocalRandom rnd ) { final Vector2D v = new Vector2D( rnd.nextDouble(), rnd.nextDouble() ); for( int i = 0; i < SIZE; i++ ) { final Vector2D addition = new Vector2D( i, i * 2 ); v.addAccumulate( addition ); } return v.length(); }Здесь создается
SIZE+1
объектов, SIZE
из которых в теле цикла, и один проходит через цикл как аккумулятор. Что произойдет здесь?
Ответ: все эти SIZE+1
объектов успешно скаляризуются. Это уже менее ожидаемо (я лично не был уверен), и поэтому особенно приятно.
Однако это еще далеко не все, что способен измыслить опытный программист:
private double replaceInLoop( final ThreadLocalRandom rnd ) { Vector2D v = new Vector2D( rnd.nextDouble(), rnd.nextDouble() ); for( int i = 0; i < SIZE; i++ ) { v = v.add( new Vector2D( 1, 2 ) ); } return ( long ) v.length(); }Здесь все так же создается
2*SIZE+1
объектов, 2*SIZE
из которых в теле цикла, и один входит в цикл как начальное значение. И этот код — не скаляризуется.
(Точнее, для SIZE = 1
все-таки скаляризуется — как я уже упоминал, циклы размера 1 EA как-то ухитряется разворачивать. Для SIZE > 1
аллокации скаляризуются частично: new Vector2D( 1, 2 )
в цикле скаляризуется, а вот остальные SIZE+1
аллокаций — нет.)
Это уже не очень приятно, хотя, пожалуй, ожидаемо. Не то, чтобы я ожидал такого поведения именно в этом случае, но интуитивно понятно, что чем сложнее код, тем больше шансов запутать EA.
Однако это еще не конец. Давайте уберем цикл:
private double allocateConditionally( final ThreadLocalRandom rnd ) { final Vector2D v; if( rnd.nextBoolean() ) { v = new Vector2D( 1, rnd.nextDouble() ); } else { v = new Vector2D( rnd.nextDouble(), 1 ); } return v.length(); }Вопрос: скаляризуется ли этот простой код? Ответ: неожиданно, но нет. Почему? Причина вырисовывается чуть яснее, если заметить, что вот такой код
private double allocateUnConditionally( final ThreadLocalRandom rnd ) { final double x; final double y; if( rnd.nextBoolean() ) { x = 1; y = rnd.nextDouble(); } else { x = rnd.nextDouble(); y = 1; } final Vector2D v = new Vector2D( x, y ); return v.length(); }...успешно скаляризуется, ровно как и вот такой:
private double allocateUnConditionally2( final ThreadLocalRandom rnd ) { if( rnd.nextBoolean() ) { final Vector2D v = new Vector2D( 1, rnd.nextDouble() ); return v.length(); } else { final Vector2D v = new Vector2D( rnd.nextDouble(), 1 ); return v.length(); } }Как я уже писал, после скаляризации ссылки на объект уже не остается. И поэтому алгоритм скаляризации весьма не любит, когда ссылками на потенциально скаляризуемый объект начинают жонглировать — ему приходится отслеживать, какой объект через какую ссылку в итоге окажется доступен, а это задача непростая. Однако
allocateConditionally
довольно тривиально преобразуется в allocateUnConditionally2
, и все-таки оптимизатор оказывается не способен этого сделать...
Комментариев нет:
Отправить комментарий