Tôi đang thêm hàng triệu mục nhập của một đối tượng tùy chỉnh vào List
. Điều này hóa ra rất chậm và giao diện người dùng đồ họa giữ lạnh một cách ngẫu nhiên (không phản hồi trên Windows) mặc dù thao tác thêm được bọc trong một SwingWorker
do đó nó không ảnh hưởng đến EDT. Hơn nữa, việc sử dụng CPU
tăng lên khoảng 70% - 90%.Java List Horrible Adding Performance
Tôi đang khởi tạo danh sách như sau:
List<SearchResult> updatedSearchResults = new ArrayList<>();
Sau đó, tôi đang liên tục gọi
SearchResult searchResult = new SearchResult(...);
updatedSearchResults.add(searchResult);
để thêm các yếu tố.
Tôi biết rằng ArrayList
sử dụng nội bộ một mảng được thay đổi kích thước khi đầy và tất cả các phần tử được sao chép. Tuy nhiên, việc sử dụng LinkedList
đã cho thấy chậm hơn so với ArrayList
và không có triển khai Danh sách khác nào tồn tại.
Theo câu trả lời this, thêm vào ArrayList
có hiệu suất là O(1)
. Câu hỏi đặt ra là tại sao cách tiếp cận của tôi trong việc thu thập tất cả các kết quả vẫn rất hiệu quả. SearchResult
là một đối tượng dữ liệu đơn giản đóng gói các trường và khởi tạo nó là giá rẻ vì chỉ có các trường được thiết lập.
Khi so sánh, việc thêm vào danh sách chậm hơn đáng kể so với việc gửi cùng một lượng dữ liệu qua ổ cắm mạng. Điều này không có ý nghĩa gì vì các hoạt động cục bộ phải luôn chạy nhanh hơn các hoạt động liên quan đến mạng.
1 million
yếu tố kết thúc trong khoảng 0.2 seconds
nhưng 67 million
thậm chí sẽ không hoàn thành trong một khoảng thời gian hợp lý. Nó sẽ kết thúc như một vài giây ở mức tối đa.
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");
}
}
Vấn đề lớn ở đây là gì?
Điều rõ ràng đầu tiên cần làm sẽ là để xác định kích thước phù hợp với Danh mục ngay từ đầu, nhưng tôi nghi ngờ vấn đề là thực sự ở đâu đó: JVM của bạn có đủ bộ nhớ cho hàng triệu thành phần đó không? Chúng chứa gì? Nhà xây dựng làm gì. Đăng một ví dụ hoàn chỉnh tối thiểu tái tạo vấn đề. –
Mất bao lâu để thêm cùng một mục vào danh sách 67M lần? Đó là mất bao lâu để làm * thêm *. Tất cả thời gian còn lại trong mã hiện tại của bạn là các công cụ khác, như tạo các yếu tố để đưa vào danh sách. –
Sử dụng profiler như visualvm trong jdk để xác định nút cổ chai. Bạn có thể ngạc nhiên –