Re[5]: Смотрел на Java код. ...много думал.
От: VladD2 Российская Империя www.nemerle.org
Дата: 30.10.05 23:43
Оценка: 14 (3) +1
Здравствуйте, c-smile, Вы писали:

CS>А что нельзя ссылочный объект сделать read-only для снаружи?


Объект нельзя. Можно класс проектировать как анмьютебл. Но иногда ведь и чужими классами приходится пользоваться, а иногда бывают и другие соображения.

Приведенный тобой паттерн довольно распространент.

VD>>Медленно говоришь? Ну, что же подождем Явы 6 (1.6) ...


CS> Влад, я уже этот форум читаю года четыре. И вот это твой оптимизм "подождем светлого будущего..."

CS>просто перманентно греет душу.

Я не оптимист. Я осведомленный писимист.

http://www-128.ibm.com/developerworks/java/library/j-jtp09275.html

Выделение памяти в стеке
C++ дает программистам выбор между размещением объектов в куче или в стеке. Размещение в стеке эффективнее: выделение памяти дешевле, освобождение совсем ничего не стоит, а язык предоставляет поддержку при разметке сроков жизни объектов, снижая риск забыть освободить объект. С другой стороны, в C++ нужно быть очень внимательным при публикации или совместном использовании ссылок на размещенные в стеке объекты, поскольку они автоматически освобождаются при раскрутке фрейма стека, что приводит к появлению "висячих" указателей.
Еще одно достоинство размещения в стеке состоит в том, что оно куда лучше работает с кешем. На современных процессорах стоимость промаха мимо кеша весьма существенна, так что если язык и runtime могут помочь программе достичь лучшей локальности данных, производительность также повысится. Вершина стека в кеше практически всегда "горячая", а вершина кучи – "холодная" (поскольку с момента последнего использования памяти, скорее всего, прошло немало времени). В результате размещение объекта в куче скорее приведет к большему числу промахов мимо Кеша, чем размещение объекта в стеке.
Что еще хуже, промах мимо кеша при размещении объекта в куче приводит к весьма грязной работе с памятью. При выделении памяти из кучи содержание этой памяти является мусором – битами, оставшимися с момента последнего использования памяти. При выделении не хранящегося в кеше блока памяти из кучи, исполнение приостановится на время переноса содержимого этой памяти в кеш. Затем эти значения будут немедленно переписаны нулями или другими исходными значениями. Все это вместе означает большой объем напрасной работы с памятью (некоторые процессоры, например, Azul Vega, аппаратно ускоряют выделение памяти из кучи).
Escape-анализ
Язык Java не предлагает никакого способа явно разместить объект в стеке, но это не мешает JVM при случае использовать размещение в стеке. JVM могут использовать технику, именуемую escape-анализом (escape analysis), который может определить, что определенные объекты остаются прикованными к определенному потоку на весь срок жизни, и что этот срок жизни ограничен сроком жизни данного фрейма стека. Такие объекты можно безопасно размещать в стеке вместо кучи. Даже лучше, в случае мелких объектов, JVM может полностью избавиться от выделения памяти и просто размещать поля объектов в регистрах.
Листинг 2 показывает пример применения escape-анализа. Метод Component.getLocation() создает защитную копию поля location, так что вызывающая сторона не может случайно изменить текущее расположение компонента. Вызов getDistanceFrom() сперва получает местоположение другого компонента (что включает создание объекта), а затем использует поля x и y объекта, возвращаемого getLocation(), для вычисления расстояния между двумя экземплярами компонентов.
Листинг 2.

public class Point 
{
  private int x, y;

  public Point(int x, int y) 
  {
    this.x = x; this.y = y;
  }

  public Point(Point p) { this(p.x, p.y); }
  public int getX() { return x; }
  public int getY() { return y; }
  public void setX(int newValue) { x = newValue; }
  public void setY(int newValue) { y = newValue; }
}

public class Component 
{
  private Point location;

  public Point getLocation() { return new Point(location); }

  public double getDistanceFrom(Component other) 
  {
    Point otherLocation = other.getLocation();
    int deltaX = otherLocation.getX() - location.getX();
    int deltaY = otherLocation.getY() - location.getY();

    return Math.sqrt(deltaX*deltaX + deltaY*deltaY);
  }
}


Метод getLocation() не знает, что вызывающая сторона собирается делать с возвращаемым им Point; она может удерживать ссылку на него, например, поместив ее в коллекцию, так что getLocation() написан в безопасной манере. Но в этом примере getDistanceFrom() не собирается модифицировать получаемые объекты или запоминать ссылки на них. Он просто собирается ненадолго задействовать Point, а затем избавиться от него, что в данном случае выглядит как разбазаривание ресурсов.
Хорошая JVM может разобраться в происходящем и избавиться от размещения защитной копии. Сначала вместо вызова getLocation() будет подставлено (inlined) тело этого метода, то же будет сделано с вызовами getX() и getY(), в результате чего getDistanceFrom() станет выглядеть так, как показано в листинге 3.
Листинг 3.
public double getDistanceFrom(Component other) 
{
  Point otherLocation = new Point(other.x, other.y);
  int deltaX = otherLocation.x - location.x;
  int deltaY = otherLocation.y - location.y;

  return Math.sqrt(deltaX*deltaX + deltaY*deltaY);
}

Здесь escape-анализ может показать, что объект, создаваемый в первой строке, никогда не покинет своего базового блока и что getDistanceFrom() никогда не изменяет состояние компонента other (под "покинет" имеется в виду то, что ссылка на него не хранится в куче и не передается неизвестному коду, который может удерживать копию). При том, что Point действительно используется только в одном потоке и его время жизни, как известно, ограничено базовым блоком, в котором он размещен, он может быть либо размещен в стеке, либо полностью соптимизирован, как показано в листинге 4.
Листинг 4.
public double getDistanceFrom(Component other) 
{
  int tempX = other.x, tempY = other.y;
  int deltaX = tempX - location.x;
  int deltaY = tempY - location.y;
  return Math.sqrt(deltaX*deltaX + deltaY*deltaY);
}

В результате мы получаем точно такую же производительность, как если бы все поля были публичными, но с сохранением безопасности, предоставляемой инкапсуляцией и защитным копированием (и остальными техниками безопасного программирования).
Escape-анализ в Mustang
Escape-анализ – это оптимизация, о которой говорилось уже давно, и вот, наконец, она здесь – текущие версии Mustang (Java SE 6) могут выполнять escape-анализ и конвертировать выделение памяти в куче в выделение памяти в стеке (или просто избавляться от выделения памяти) там, где это возможно. Использование escape-анализа для устранения некоторых распределений памяти приводит к еще большему ускорению работы с памятью, снижению расхода памяти и уменьшению количества промахов мимо кеша. Кроме того, это снижает нагрузку на сборщик мусора и позволяет реже производить сборку.
Заключение
JVM удивительно хорошо делают вещи, которые раньше были полностью отданы на откуп разработчику. Позволяя JVM выбирать между выделением памяти в куче и в стеке, можно получить преимущества производительности выделения памяти в стеке, не заставляя программиста мучаться с выбором.

Brian Goetz

... << RSDN@Home 1.2.0 alpha rev. 618>>
Есть логика намерений и логика обстоятельств, последняя всегда сильнее.
 
Подождите ...
Wait...
Пока на собственное сообщение не было ответов, его можно удалить.