2015-10-08 10 views
1

Из интереса: Недавно я столкнулся с ситуацией в одном из моих проектов Java, где я мог хранить некоторые данные либо в двумерном массиве, либо создавать для него выделенный класс, чей экземпляров я бы поместил в одномерный массив. Поэтому я задаюсь вопросом, существуют ли канонические советы по дизайну в этой области с точки зрения производительности (время выполнения, потребление памяти)?Производительность/потребление памяти Java: класс против массива

Без учета шаблонов проектирования (предельно упрощена ситуация), скажем, я мог бы хранить данные как

class MyContainer { 
    public double a; 
    public double b; 
    ... 
} 

, а затем

MyContainer[] myArray = new MyContainer[10000]; 
for(int i = myArray.length; (--i) >= 0;) { 
    myArray[i] = new MyContainer(); 
} 
... 

против

double[][] myData = new double[10000][2]; 
... 

я как-то думаю, что подход на основе массива должен быть более компактный (память) и быстрее (доступ). Опять же, возможно, это не так, массивы тоже являются объектами, а доступ к массиву должен проверять индексы, в то время как доступ к объектным членам не имеет. (?) Распределение массива объектов, вероятно, (?) Займет больше времени, так как мне нужно итеративно создать экземпляров, и мой код будет больше из-за дополнительного класса.

Таким образом, мне интересно, обеспечивают ли схемы общих JVM преимущества для одного подхода по сравнению с другим, с точки зрения скорости доступа и потребления памяти?

Большое спасибо.

+0

Одно пояснение, массивы Java ** являются ** 'Object' (s). Даже массивы примитивов. –

+0

@ElliotFrisch: Да, я это знаю (см. * ... массивы тоже объекты ... *). Тем не менее: возможно, JVM/JIT обрабатывает массивы особым образом, что может ускорить доступ к ним тем или иным способом. Также могут быть элементы кэширования/макета памяти и т. Д. Таким образом, я также спросил о * ... проектах общих JVM ... *. –

+0

Возможно, вы можете использовать 'enum'. Они обрабатываются специально в том смысле, что они являются конструкциями времени компиляции. –

ответ

2

Опять же, может быть, это не так, массивы являются объектами слишком

Это верно. Поэтому я думаю, что такой подход не купит вам ничего.

Если вы хотите спуститься по этому маршруту, вы можете разложить его на одномерный массив (каждый из ваших «объектов» затем занимает два слота). Это даст вам немедленный доступ ко всем полям во всех объектах, без необходимости следовать указателям, и все это всего лишь одно большое выделение памяти: поскольку ваш тип компонента примитивен, есть только один объект в отношении распределения памяти (сам контейнер).

Это одна из мотиваций для людей wanting to have structs and value types in Java, и подобные соображения приводят к созданию специализированных высокопроизводительных библиотек структуры данных (которые избавляются от ненужных оберток объектов).

Я бы не стал беспокоиться об этом, пока у вас действительно есть огромная структура данных. Только тогда накладные расходы объектно-ориентированного характера.

+0

Действительно, это был бы самый удобный для памяти метод. Скорее, будет делать что-то вроде 'myarray [(i << 1) + j]', где в нашем случае 'i' будет индексом, а' j' будет либо '0', либо' 1', быстрее чем делать 'myarray [i] [j]'? (Я вроде думаю «да», но опять же, я не уверен, какие оптимизации сделаны в обычных JVM/JIT.) –

+1

Да, это то, что я имел в виду под «необходимостью следовать указателям». Каждый из объектов компонента представляет собой отдельный объект в другом месте памяти. Плоский массив намного быстрее. – Thilo

1

я почему-то кажется, что подход на основе массива должен быть более компактным (память) и быстрее (доступ)

Это не будет. Вы можете легко убедиться в этом с помощью интерфейсов управления Java:

com.sun.management.ThreadMXBean b = (com.sun.management.ThreadMXBean) ManagementFactory.getThreadMXBean(); 
long selfId = Thread.currentThread().getId(); 
long memoryBefore = b.getThreadAllocatedBytes(selfId); 

// <-- Put measured code here 

long memoryAfter = b.getThreadAllocatedBytes(selfId); 
System.out.println(memoryAfter - memoryBefore); 

Под измеренным кодом поставил new double[0] и new Object(), и вы увидите, что эти ассигнования будут требовать точно такой же объем памяти.

Возможно, JVM/JIT обрабатывает массивы особым образом, что может ускорить доступ к ним тем или иным способом.

JIT сделать vectorization of an array operations if for-loops. Но это скорее скорость арифметических операций, чем скорость доступа. Кроме того, не могу думать ни о чем.

+0

Спасибо за разъяснение этого. Поэтому мы можем ожидать, что общее потребление памяти объектами и 2d-массивом должно быть одинаковым. Знать это хорошо. Еще одна вещь, о которой я мог думать в терминах «компактности»: если я выделил, скажем «новый двойной [1000] [2]», я бы ожидал, что это должно стать одним непрерывным куском памяти в куче. Однако, если я делаю 'for (int i = 0; i <1000; i ++) {data [i] = new MyObject(); } ', Я не уверен, что некоторые объекты могут приземляться в разных местах кучи целиком (?). Так что это может быть штраф за идею Object-array (?) –

+1

Что касается макета памяти, то также не должно быть разницы. JVM выделяет память из локального буфера потока, называемого TLAB (https://blogs.oracle.com/jonthecollector/entry/the_real_thing). Объекты будут смежными в памяти независимо от их типа, насколько они полностью соответствуют TLAB. –

2

Канонический совет, который я видел в этой ситуации, заключается в том, что преждевременная оптимизация является корнем всего зла. После этого вы должны придерживаться кода, который проще всего написать/сохранить/выйти из режима качества кода, а затем посмотреть оптимизацию, если у вас есть измеримая проблема с производительностью.

В ваших примерах потребление памяти аналогично, поскольку в объектном случае у вас есть 10 000 ссылок плюс два удвоения на одну ссылку, а в случае с 2D-массивом у вас есть 10 000 ссылок (первое измерение) на маленькие массивы, содержащие по два удвоения. Таким образом, оба являются одной базовой базой плюс 10000 ссылок плюс 20 000 удвоений.

Более эффективным представлением будет два массива, где у вас будет две базовые ссылки плюс 20 000 удвоений.

double[] a = new double[10000]; 
double[] b = new double[10000]; 
+0

Это очень простое в обслуживании, легко понять, и ему не нужно много памяти. Тем не менее, это может привести к проблемам с производительностью, поскольку они «разрывают» кортежи данных/пары друг от друга. Если у меня есть 'double [10000] [2]', тогда 2 элемента каждого подматрица будут расположены рядом друг с другом в памяти, что хорошо для кэшей. В двух одиночных массивах (если я правильно понимаю ваше предложение), каждый элемент будет располагаться в другом массиве. Два элемента кортежа будут '10000' удваиваются друг от друга в памяти. Это, вероятно, приведет к большему количеству промахов в кеше и замедлит скорость обработки. –

+1

Хороший подход. Также см. Http://stackoverflow.com/a/15585817/14955 – Thilo

 Смежные вопросы

  • Нет связанных вопросов^_^