Определение набора основных правил для высокопроизводительных структур данных (java)

Обычно я использую векторы /arraylists, hashmaps/treemaps и другие java-коллекции взаимозаменяемо, за исключением того факта, что иногда существуют функциональные требования API (например, может потребоваться сортированный набор данных в определенных случаях).

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

Есть ли набор рекомендаций для высокопроизводительных структур данных, которые я могу использовать в качестве основных правил для моего кодирования?

Я ищу общие правила, но в этом контексте ответы на следующие вопросы также могут быть очень полезными:

1) Когда следует использовать многомерные массивы вместо вложенных Коллекции?

2) Векторы против ArrayLists - действительно ли разница в производительности?

3) Создайте API-интерфейс коллекции, например коллекцию Google, java-трюки (например, отражение и литье), а также другие распространенные идиомы разработчика Java замедлить работу JVM, когда он находится под большой нагрузкой?

4) Делают ли примитивы против обычных объектов (т.е. двойные или двойные) JVM при выполнении большого количества вычислений?

5) Существуют ли другие важные рекомендации по коллекций в java-программах, которые должны быть высокопроизводительными?

  • Примечание: на данный момент я не выполняю многопоточность... Я понимаю, что есть другие ограничения, которые могут применяться после начала распараллеливания.

Ответ 1

Все проблемы производительности должны решаться сначала профилированием (как для использования времени, так и для памяти/объекта). Не оптимизируйте вещи, которые не являются фактором эффективности вашего кода. С этим предостережением существуют некоторые общие правила (все они должны быть проверены профилированием!)

1) Когда следует использовать многомерные массивы вместо вложенных коллекций?

Если вам не нужен динамический размер коллекций и вам не нужно кормить свои данные ничем, требующим Collection, то многомерные массивы (массивы массивов, на самом деле) могут быть быстрее.

2) Векторы против ArrayLists - действительно ли разница в производительности?

Да. Многие методы в Vector синхронизированы, что дорого. Если вы не многопоточны, а затем избегайте Vector. Даже если вы, то зернистость синхронизации обычно неправильна, и вам лучше обеспечить безопасность потоков.

3) Утилиты сбора данных, такие как сборники Google, java-трюки (например, отражение и литье) и другие распространенные идиомы разработчиков Java, как правило, замедляют JVM, когда они находятся под большой нагрузкой?

Отражение происходит медленно; сбор мусора медленный. Все, что вы можете сделать, чтобы избежать этого, ускорит процесс.

4) Применяют ли примитивы против обычных объектов (т.е. Double vs double) замедлять JVM при выполнении большого количества вычислений?

Да. Autoboxing/unboxing может очень быстро создавать огромное количество мусора. Все это нужно собрать, что также замедлит вашу программу.

5) Существуют ли другие важные рекомендации для работы с большими наборами в Java-программах, которые должны быть высокопроизводительными?

Предпочитать локальные переменные метода для доступа к полям. Вы можете найти множество других рекомендаций, выполнив поиск в Интернете. Главное, однако, в профиле.

Изменить: там хорошая коллекция подсказок производительности здесь.

Ответ 2

Чтобы ответить на ваш вопрос 4) Да, Double vs double определенно меняет характеристики

Когда у вас есть коллекции, состоящие из примитивов, вы, безусловно, можете использовать коллекции, поддерживаемые примитивами, например, очень хороший API-интерфейс Trove. Если вы избегаете постоянного примитивного объекта и наоборот, вы сохраняете как память, так и драгоценное время.

Кроме того, класс Vector уже давно ушел в прошлое.

Ответ 3

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

2) Векторы и Hashtables следует рассматривать почти так, как если бы они устарели, на мой взгляд. Они являются "потокобезопасными", но для большинства сценариев реального мира просто отсутствие структуры данных в потоковом режиме недостаточно; обычно ваша логика приложения также должна быть частью этой синхронизации. ArrayList, HashMap будет работать лучше, поскольку у них нет синхронизированных блоков, которые в 99,9% случаев не принесут вам ничего полезного.

3) API-интерфейсы Google - отличные, никаких реальных проблем с производительностью. Отражение определенно медленное и не должно быть во внутренних циклах.

4) В идеале вы хотели бы избежать бокса/распаковки примитивов во внутренних циклах. Вы можете найти коллекции, которые специально настроены на примитивы (т.е. Коллекции Troave http://trove.starlight-systems.com/).

5) Это зависит от конкретного использования, я бы не сказал, что существуют общие рекомендации. Просто убедитесь, что вы понимаете, что вы делаете при преобразовании коллекций и т.д. Например, убедитесь, что он не клонирует всю вашу коллекцию при преобразовании списка в набор или что-то в этом роде.

Ответ 4

  • Я считаю, что единственный раз, когда вы должны использовать Vector, нужно, чтобы он был синхронизирован, но вы можете использовать специальную Syncronized thingy в ArrayList, поэтому я бы сказал, что Vector не нужен. Всегда используйте ArrayList вместо LinkedList. Он отходит от здравого смысла, поэтому он должен быть реализацией java, но ArrayList намного быстрее. Раньше я верил в LinkedList, поэтому я создал следующий тест:

    импортировать java.util.ArrayList; import java.util.GregorianCalendar; import java.util.LinkedList; import java.util.List; import java.util.Random;

/**  *  */

/**  * @author thom  *  */ public class ListTest {

private ArrayList<Integer>      arrayList = new ArrayList<Integer>();
private LinkedList<Integer>     linkedList = new LinkedList<Integer>();

/**
 * 
 */
public void test(){
    LinkedList<Integer> arrayTimes = new LinkedList<Integer>();
    LinkedList<Integer> linkedTimes = new LinkedList<Integer>();

    for(int ix = 0; ix < 100; ix ++){
        arrayList.clear();
        long start = new GregorianCalendar().getTimeInMillis();
        fillList(arrayList);
        long stop = new GregorianCalendar().getTimeInMillis();
        int elapsed = (int) (stop - start);
        arrayTimes.add(elapsed);
    }

    for(int ix = 0; ix < 100; ix ++){
        linkedList.clear();
        long start = new GregorianCalendar().getTimeInMillis();
        fillList(linkedList);
        long stop = new GregorianCalendar().getTimeInMillis();
        int elapsed = (int) (stop - start);
        linkedTimes.add(elapsed);
    }

    double arrayAvg = avg(arrayTimes);
    double linkedAvg = avg(linkedTimes);

    System.err.println("Adding 100,000 entries 100 times to linked list.");
    System.err.println("ArrayList elapsed time (ms.):" + arrayAvg);
    System.err.println("LinkedList elapsed time (ms.):" + linkedAvg);

    arrayTimes.clear();
    linkedTimes.clear();

    long start = new GregorianCalendar().getTimeInMillis();
    insertMiddle(arrayList);
    long stop = new GregorianCalendar().getTimeInMillis();
    int elapsed = (int) (stop - start);

    System.err.println();
    System.err.println("Inserting 1,000 entries to the middle of the list.");
    System.err.println("ArrayList elapsed time (ms.):" + elapsed);

    start = new GregorianCalendar().getTimeInMillis();
    insertMiddle(linkedList);
    stop = new GregorianCalendar().getTimeInMillis();
    elapsed = (int) (stop - start);
    System.err.println("LinkedList elapsed time (ms.):" + elapsed);

    start = new GregorianCalendar().getTimeInMillis();
    for(int ix = 0; ix < 100; ++ix){
        for(int jx = 0; jx < 100000; ++jx){
            arrayList.get(jx);
        }
    }
    stop = new GregorianCalendar().getTimeInMillis();
    elapsed = (int) (stop - start);

    System.err.println();
    System.err.println("Sequentially reading the list 100 times");
    System.err.println("ArrayList elapsed time (ms.):" + elapsed);

    start = new GregorianCalendar().getTimeInMillis();
    for(int ix = 0; ix < 100; ++ix){
        for(int jx = 0; jx < 100000; ++jx){
            linkedList.get(jx);
        }
    }
    stop = new GregorianCalendar().getTimeInMillis();
    elapsed = (int) (stop - start);
    System.err.println("LinkedList elapsed time (ms.):" + elapsed);

    Random rnd = new Random();
    start = new GregorianCalendar().getTimeInMillis();
    for(int ix = 0; ix < 100; ++ix){
        for(int jx = 0; jx < 100000; ++jx){
            int index = rnd.nextInt(100000);
            arrayList.get(index);
        }
    }
    stop = new GregorianCalendar().getTimeInMillis();
    elapsed = (int) (stop - start);

    System.err.println();
    System.err.println("Randomly reading the list 100 times");
    System.err.println("ArrayList elapsed time (ms.):" + elapsed);

    start = new GregorianCalendar().getTimeInMillis();
    for(int ix = 0; ix < 100; ++ix){
        for(int jx = 0; jx < 100000; ++jx){
            int index = rnd.nextInt(100000);
            linkedList.get(index);
        }
    }
    stop = new GregorianCalendar().getTimeInMillis();
    elapsed = (int) (stop - start);
    System.err.println("LinkedList elapsed time (ms.):" + elapsed);
}

/**
 * @param values
 */
protected double avg(List<Integer> values){
    double sum = 0;
    for(int ix:values){
        sum += ix;
    }

    double result = sum / values.size();
    return result;
}

/**
 * @param list
 */
protected void fillList(List<Integer> list){
    for(int ix = 0; ix < 100000; ix++){
        list.add(ix);
    }
}

/**
 * @param list
 */
protected void insertMiddle(List<Integer> list){
    for(int ix = 0; ix < 1000; ix++){
        list.add(50000, ix);
    }
}

/**
 * @param args
 */
public static void main(String[] args) {
    ListTest listTest = new ListTest();
    listTest.test();
}

}

И он произвел следующие результаты:

Adding 100,000 entries 100 times to linked list.
ArrayList elapsed time (ms.):2.78
LinkedList elapsed time (ms.):12.24

Inserting 1,000 entries to the middle of the list.
ArrayList elapsed time (ms.):35
LinkedList elapsed time (ms.):154

Sequentially reading the list 100 times
ArrayList elapsed time (ms.):94
LinkedList elapsed time (ms.):748271

Randomly reading the list 100 times
ArrayList elapsed time (ms.):404
LinkedList elapsed time (ms.):1158273

Кто-то, пожалуйста, проверьте мой код, чтобы убедиться, что я не сделал что-то глупое, но он показывает, что ArrayList EXTREMELY быстрее, чем LinkedList для всего.

  • Отражение явно медленное.

  • Примитивы быстрее для вычислений. Будьте осторожны с авто-боксом, так как это поражает производительность. Это приятно, просто убедитесь, что вы понимаете затраты.

Ответ 5

1) Когда вы знаете максимальный размер, используйте массивы.

2) Векторы имеют синхронизированные методы, поэтому медленнее, чем ArrayLists. Есть разница. В последнее время существует тенденция использовать Collections.synchronizedList вместо векторов.

3) Существует несколько реализаций "быстрых" коллекций, например. http://labs.carrotsearch.com/hppc.html или Trove, other Какая наиболее эффективная библиотека сборников Java?

4) Если вы можете, используйте примитив. Упаковщики приносят дополнительные накладные расходы.

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

Ответ 6

ИМХО первое и самое главное правило - выбрать правильную структуру для вашего использования.

Использование карты для реализации словаря может быть полезно для производительности (времени), поскольку это займет много памяти (пробел), используйте Trie.

Поиск Hash (с использованием HashMap) хорош, но если у вас есть ключ с конечным числовым диапазоном, массив будет лучше.

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

Ответ 7

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

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

Ответ 8

Еще один небольшой трюк:

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