Я добавляю миллионы записей пользовательского объекта в List
. Это оказывается очень медленным, и графический пользовательский интерфейс также сохраняет зависание случайно (не реагируя на Windows), даже если операция добавления завернута в SwingWorker
, поэтому она не должна влиять на EDT. Кроме того, использование CPU
составляет около 70% - 90%.Список Java Horrible Добавление производительности
Я инициализирует список выглядит следующим образом:
List<SearchResult> updatedSearchResults = new ArrayList<>();
Тогда я неоднократно называя
SearchResult searchResult = new SearchResult(...);
updatedSearchResults.add(searchResult);
для добавления элементов.
Я знаю, что ArrayList
внутренне использует массив, размер которого изменяется, когда он заполнен, и все элементы скопированы. Однако использование LinkedList
показало, что оно медленнее, чем ArrayList
, хотя и никакой другой реализации List не существует.
Согласно this ответ, добавление ArrayList
имеет производительность O(1)
. Возникает вопрос, почему мой подход к сбору всех результатов по-прежнему так ужасен по производительности. SearchResult
- это простой объект, инкапсулирующий поля и инициализирующий его дешево, поскольку установлены только поля.
В сравнении, добавление к списку заметно медленнее, чем отправка одного и того же объема данных по сетевому сокету. Это не имеет никакого смысла, поскольку локальные операции всегда должны быть быстрее, чем операции, связанные с сетью.
1 million
Элементы отделки около 0.2 seconds
, но 67 million
не смогут закончить в разумные сроки. Он должен заканчиваться как максимум на несколько секунд.
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.math.BigInteger;
import java.util.ArrayList;
import java.util.List;
public class ListPerformanceTester
{
public enum ValueSize
{
EIGHT_BIT("8-Bit"),
SIXTEEN_BIT("16-Bit"),
THIRTY_TWO_BIT("32-Bit"),
SIXTY_FOUR_BIT("64-Bit"),
NINETY_SIX_BIT("96-Bit");
private String name;
ValueSize(String name)
{
this.name = name;
}
public int getBytesCount()
{
switch (this)
{
case EIGHT_BIT:
return 1;
case SIXTEEN_BIT:
return 2;
case THIRTY_TWO_BIT:
return 4;
case SIXTY_FOUR_BIT:
return 8;
case NINETY_SIX_BIT:
return 12;
}
throw new IllegalStateException("Bytes count undefined for " + this);
}
@Override
public String toString()
{
return name;
}
public static ValueSize parse(String name)
{
ValueSize[] valueSizes = values();
for (ValueSize valueSize : valueSizes)
{
String currentName = valueSize.toString();
if (currentName.equals(name))
{
return valueSize;
}
}
throw new IllegalArgumentException("No value size associated");
}
}
public static class SearchResult implements Cloneable, Comparable
{
private int address;
private ValueSize valueSize;
private BigInteger previousValue;
private BigInteger currentValue;
private BigInteger valueDifference;
public SearchResult(int address, BigInteger previousValue,
BigInteger currentValue, ValueSize valueSize)
{
this.address = address;
this.valueSize = valueSize;
this.previousValue = previousValue;
this.currentValue = currentValue;
setValueDifference();
}
private void setValueDifference()
{
BigInteger subtractionResult = previousValue.subtract(currentValue);
valueDifference = subtractionResult.abs();
}
private byte[] getBytes(BigInteger bigInteger) throws IOException
{
byte[] retrieved = bigInteger.toByteArray();
ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
int bytesCount = getValueSize().getBytesCount();
int paddingBytes = bytesCount - retrieved.length;
if (paddingBytes >= 0)
{
byteArrayOutputStream.write(new byte[paddingBytes]);
byteArrayOutputStream.write(retrieved);
} else
{
writeWithoutLeadingNullBytes(byteArrayOutputStream, retrieved);
}
return byteArrayOutputStream.toByteArray();
}
private void writeWithoutLeadingNullBytes(ByteArrayOutputStream byteArrayOutputStream, byte[] bytes)
{
int index = 0;
boolean nonNullByteFound = false;
while (index < bytes.length)
{
byte value = bytes[index];
if (value != 0 || nonNullByteFound)
{
nonNullByteFound = true;
byteArrayOutputStream.write(value);
}
index++;
}
}
public int getAddress()
{
return address;
}
@Override
public boolean equals(Object object)
{
if (!(object instanceof SearchResult))
{
return false;
}
SearchResult searchResult = (SearchResult) object;
return searchResult.getAddress() == getAddress();
}
@Override
public int hashCode()
{
return Integer.hashCode(address);
}
public ValueSize getValueSize()
{
return valueSize;
}
@Override
public SearchResult clone()
{
return new SearchResult(address, previousValue, currentValue, valueSize);
}
@Override
public int compareTo(Object object)
{
return new Integer(address).compareTo(((SearchResult) object).getAddress());
}
}
public static void main(String[] arguments)
{
long milliseconds = System.currentTimeMillis();
int elementsCount = 2000000;
/*List<Integer> list = new ArrayList<>();
for (int elementIndex = 0; elementIndex < elementsCount; elementIndex++)
{
list.add(0);
}*/
List<SearchResult> searchResults = new ArrayList<>();
for (int elementIndex = 0; elementIndex < elementsCount; elementIndex++)
{
SearchResult searchResult = new SearchResult(0x12345678, new BigInteger("3"), new BigInteger("1"), ValueSize.EIGHT_BIT);
searchResults.add(searchResult);
}
System.out.println((System.currentTimeMillis() - milliseconds)/(double) 1000 + " seconds");
}
}
Что это такое?
Первая очевидная вещь, чтобы сделать было бы указать правильный размер для списка с самого начала, но я подозреваю, что проблема на самом деле в другом месте: у вашей JVM достаточно памяти для этих миллионов элементов? Что они содержат? Что делает конструктор. Отправьте полный минимальный пример, воспроизводящий проблему. –
Сколько нужно времени, чтобы добавить один и тот же элемент в список 67M раз? Вот как долго нужно делать * добавление *. Все остальное время в вашем текущем коде - это другой материал, например, создание элементов для добавления в список. –
Используйте профилировщик как visualvm в jdk, чтобы идентифицировать узкое место. Вы можете быть удивлены –