เอาล่ะเพื่อหยุดสิ่งนี้ฉันได้สร้างแอปทดสอบเพื่อเรียกใช้สถานการณ์สองสามสถานการณ์และรับภาพผลลัพธ์บางอย่าง นี่คือวิธีการทดสอบ:
- มีการลองคอลเลกชันขนาดต่างๆจำนวนหนึ่ง: หนึ่งร้อยหนึ่งพันและหนึ่งแสนรายการ
- คีย์ที่ใช้คืออินสแตนซ์ของคลาสที่ ID ระบุโดยไม่ซ้ำกัน การทดสอบแต่ละครั้งใช้คีย์ที่ไม่ซ้ำกันโดยเพิ่มจำนวนเต็มเป็น ID
equals
วิธีเดียวที่จะใช้รหัสจึงไม่มีการทำแผนที่ที่สำคัญอีกคนหนึ่งเขียนทับ
- คีย์จะได้รับรหัสแฮชที่ประกอบด้วยส่วนที่เหลือของโมดูลของ ID เทียบกับหมายเลขที่ตั้งไว้ล่วงหน้า เราจะโทรไปยังหมายเลขที่วงเงินกัญชา สิ่งนี้ทำให้ฉันสามารถควบคุมจำนวนการชนกันของแฮชที่คาดว่าจะเกิดขึ้นได้ ตัวอย่างเช่นหากขนาดคอลเลกชันของเราคือ 100 เราจะมีคีย์ที่มี ID ตั้งแต่ 0 ถึง 99 หากแฮช จำกัด คือ 100 คีย์ทุกคีย์จะมีรหัสแฮชที่ไม่ซ้ำกัน หากขีด จำกัด แฮชคือ 50 คีย์ 0 จะมีรหัสแฮชเช่นเดียวกับคีย์ 50 โดย 1 จะมีรหัสแฮชเหมือนกับ 51 เป็นต้นกล่าวอีกนัยหนึ่งจำนวนแฮชที่คาดหวังต่อคีย์คือขนาดคอลเลกชันหารด้วยแฮช ขีด จำกัด
- สำหรับการรวมกันของขนาดคอลเลกชันและขีด จำกัด แฮชฉันได้ทำการทดสอบโดยใช้แผนที่แฮชที่เริ่มต้นด้วยการตั้งค่าที่แตกต่างกัน การตั้งค่าเหล่านี้เป็นปัจจัยการโหลดและความจุเริ่มต้นที่แสดงเป็นปัจจัยของการตั้งค่าการรวบรวม ตัวอย่างเช่นการทดสอบที่มีขนาดคอลเลกชัน 100 และปัจจัยความจุเริ่มต้นที่ 1.25 จะเริ่มต้นแผนที่แฮชที่มีความจุเริ่มต้น 125
Object
คุ้มค่าสำหรับแต่ละคีย์เป็นเพียงใหม่
- ผลการทดสอบแต่ละรายการจะถูกห่อหุ้มในอินสแตนซ์ของคลาส Result ในตอนท้ายของการทดสอบทั้งหมดผลลัพธ์จะเรียงลำดับจากประสิทธิภาพโดยรวมที่แย่ที่สุดไปจนถึงดีที่สุด
- เวลาเฉลี่ยสำหรับการวางและรับจะคำนวณต่อ 10 ครั้ง / รับ
- ชุดทดสอบทั้งหมดจะรันครั้งเดียวเพื่อกำจัดอิทธิพลการรวบรวม JIT หลังจากนั้นการทดสอบจะดำเนินการเพื่อผลลัพธ์ที่แท้จริง
นี่คือชั้นเรียน:
package hashmaptest;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
public class HashMapTest {
private static final List<Result> results = new ArrayList<Result>();
public static void main(String[] args) throws IOException {
final int[][] sampleSizesAndHashLimits = new int[][] {
{100, 50, 90, 100},
{1000, 500, 900, 990, 1000},
{100000, 10000, 90000, 99000, 100000}
};
final double[] initialCapacityFactors = new double[] {0.5, 0.75, 1.0, 1.25, 1.5, 2.0};
final float[] loadFactors = new float[] {0.5f, 0.75f, 1.0f, 1.25f};
for(int[] sizeAndLimits : sampleSizesAndHashLimits) {
int size = sizeAndLimits[0];
for(int i = 1; i < sizeAndLimits.length; ++i) {
int limit = sizeAndLimits[i];
for(double initCapacityFactor : initialCapacityFactors) {
for(float loadFactor : loadFactors) {
runTest(limit, size, initCapacityFactor, loadFactor);
}
}
}
}
results.clear();
for(int[] sizeAndLimits : sampleSizesAndHashLimits) {
int size = sizeAndLimits[0];
for(int i = 1; i < sizeAndLimits.length; ++i) {
int limit = sizeAndLimits[i];
for(double initCapacityFactor : initialCapacityFactors) {
for(float loadFactor : loadFactors) {
runTest(limit, size, initCapacityFactor, loadFactor);
}
}
}
}
Collections.sort(results);
for(final Result result : results) {
result.printSummary();
}
}
private static void runTest(final int hashLimit, final int sampleSize,
final double initCapacityFactor, final float loadFactor) {
final int initialCapacity = (int)(sampleSize * initCapacityFactor);
System.out.println("Running test for a sample collection of size " + sampleSize
+ ", an initial capacity of " + initialCapacity + ", a load factor of "
+ loadFactor + " and keys with a hash code limited to " + hashLimit);
System.out.println("====================");
double hashOverload = (((double)sampleSize/hashLimit) - 1.0) * 100.0;
System.out.println("Hash code overload: " + hashOverload + "%");
final List<Key> keys = generateSamples(hashLimit, sampleSize);
final List<Object> values = generateValues(sampleSize);
final HashMap<Key, Object> map = new HashMap<Key, Object>(initialCapacity, loadFactor);
final long startPut = System.nanoTime();
for(int i = 0; i < sampleSize; ++i) {
map.put(keys.get(i), values.get(i));
}
final long endPut = System.nanoTime();
final long putTime = endPut - startPut;
final long averagePutTime = putTime/(sampleSize/10);
System.out.println("Time to map all keys to their values: " + putTime + " ns");
System.out.println("Average put time per 10 entries: " + averagePutTime + " ns");
final long startGet = System.nanoTime();
for(int i = 0; i < sampleSize; ++i) {
map.get(keys.get(i));
}
final long endGet = System.nanoTime();
final long getTime = endGet - startGet;
final long averageGetTime = getTime/(sampleSize/10);
System.out.println("Time to get the value for every key: " + getTime + " ns");
System.out.println("Average get time per 10 entries: " + averageGetTime + " ns");
System.out.println("");
final Result result =
new Result(sampleSize, initialCapacity, loadFactor, hashOverload, averagePutTime, averageGetTime, hashLimit);
results.add(result);
System.gc();
try {
Thread.sleep(200);
} catch(final InterruptedException e) {}
}
private static List<Key> generateSamples(final int hashLimit, final int sampleSize) {
final ArrayList<Key> result = new ArrayList<Key>(sampleSize);
for(int i = 0; i < sampleSize; ++i) {
result.add(new Key(i, hashLimit));
}
return result;
}
private static List<Object> generateValues(final int sampleSize) {
final ArrayList<Object> result = new ArrayList<Object>(sampleSize);
for(int i = 0; i < sampleSize; ++i) {
result.add(new Object());
}
return result;
}
private static class Key {
private final int hashCode;
private final int id;
Key(final int id, final int hashLimit) {
this.id = id;
this.hashCode = id % hashLimit;
}
@Override
public int hashCode() {
return hashCode;
}
@Override
public boolean equals(final Object o) {
return ((Key)o).id == this.id;
}
}
static class Result implements Comparable<Result> {
final int sampleSize;
final int initialCapacity;
final float loadFactor;
final double hashOverloadPercentage;
final long averagePutTime;
final long averageGetTime;
final int hashLimit;
Result(final int sampleSize, final int initialCapacity, final float loadFactor,
final double hashOverloadPercentage, final long averagePutTime,
final long averageGetTime, final int hashLimit) {
this.sampleSize = sampleSize;
this.initialCapacity = initialCapacity;
this.loadFactor = loadFactor;
this.hashOverloadPercentage = hashOverloadPercentage;
this.averagePutTime = averagePutTime;
this.averageGetTime = averageGetTime;
this.hashLimit = hashLimit;
}
@Override
public int compareTo(final Result o) {
final long putDiff = o.averagePutTime - this.averagePutTime;
final long getDiff = o.averageGetTime - this.averageGetTime;
return (int)(putDiff + getDiff);
}
void printSummary() {
System.out.println("" + averagePutTime + " ns per 10 puts, "
+ averageGetTime + " ns per 10 gets, for a load factor of "
+ loadFactor + ", initial capacity of " + initialCapacity
+ " for " + sampleSize + " mappings and " + hashOverloadPercentage
+ "% hash code overload.");
}
}
}
การดำเนินการนี้อาจใช้เวลาสักครู่ ผลลัพธ์จะถูกพิมพ์ออกมาตามมาตรฐาน คุณอาจสังเกตเห็นว่าฉันได้แสดงความคิดเห็นไว้ในบรรทัด บรรทัดนั้นเรียกวิชวลไลเซอร์ที่แสดงผลการแสดงผลไปยังไฟล์ png ชั้นเรียนสำหรับสิ่งนี้ได้รับด้านล่าง หากคุณต้องการเรียกใช้ให้ยกเลิกการใส่เครื่องหมายบรรทัดที่เหมาะสมในโค้ดด้านบน ขอเตือน: คลาส Visualizer จะถือว่าคุณใช้งาน Windows และจะสร้างโฟลเดอร์และไฟล์ใน C: \ temp เมื่อทำงานบนแพลตฟอร์มอื่นให้ปรับสิ่งนี้
package hashmaptest;
import hashmaptest.HashMapTest.Result;
import java.awt.Color;
import java.awt.Graphics2D;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;
import java.text.DecimalFormat;
import java.text.NumberFormat;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import javax.imageio.ImageIO;
public class ResultVisualizer {
private static final Map<Integer, Map<Integer, Set<Result>>> sampleSizeToHashLimit =
new HashMap<Integer, Map<Integer, Set<Result>>>();
private static final DecimalFormat df = new DecimalFormat("0.00");
static void visualizeResults(final List<Result> results) throws IOException {
final File tempFolder = new File("C:\\temp");
final File baseFolder = makeFolder(tempFolder, "hashmap_tests");
long bestPutTime = -1L;
long worstPutTime = 0L;
long bestGetTime = -1L;
long worstGetTime = 0L;
for(final Result result : results) {
final Integer sampleSize = result.sampleSize;
final Integer hashLimit = result.hashLimit;
final long putTime = result.averagePutTime;
final long getTime = result.averageGetTime;
if(bestPutTime == -1L || putTime < bestPutTime)
bestPutTime = putTime;
if(bestGetTime <= -1.0f || getTime < bestGetTime)
bestGetTime = getTime;
if(putTime > worstPutTime)
worstPutTime = putTime;
if(getTime > worstGetTime)
worstGetTime = getTime;
Map<Integer, Set<Result>> hashLimitToResults =
sampleSizeToHashLimit.get(sampleSize);
if(hashLimitToResults == null) {
hashLimitToResults = new HashMap<Integer, Set<Result>>();
sampleSizeToHashLimit.put(sampleSize, hashLimitToResults);
}
Set<Result> resultSet = hashLimitToResults.get(hashLimit);
if(resultSet == null) {
resultSet = new HashSet<Result>();
hashLimitToResults.put(hashLimit, resultSet);
}
resultSet.add(result);
}
System.out.println("Best average put time: " + bestPutTime + " ns");
System.out.println("Best average get time: " + bestGetTime + " ns");
System.out.println("Worst average put time: " + worstPutTime + " ns");
System.out.println("Worst average get time: " + worstGetTime + " ns");
for(final Integer sampleSize : sampleSizeToHashLimit.keySet()) {
final File sizeFolder = makeFolder(baseFolder, "sample_size_" + sampleSize);
final Map<Integer, Set<Result>> hashLimitToResults =
sampleSizeToHashLimit.get(sampleSize);
for(final Integer hashLimit : hashLimitToResults.keySet()) {
final File limitFolder = makeFolder(sizeFolder, "hash_limit_" + hashLimit);
final Set<Result> resultSet = hashLimitToResults.get(hashLimit);
final Set<Float> loadFactorSet = new HashSet<Float>();
final Set<Integer> initialCapacitySet = new HashSet<Integer>();
for(final Result result : resultSet) {
loadFactorSet.add(result.loadFactor);
initialCapacitySet.add(result.initialCapacity);
}
final List<Float> loadFactors = new ArrayList<Float>(loadFactorSet);
final List<Integer> initialCapacities = new ArrayList<Integer>(initialCapacitySet);
Collections.sort(loadFactors);
Collections.sort(initialCapacities);
final BufferedImage putImage =
renderMap(resultSet, loadFactors, initialCapacities, worstPutTime, bestPutTime, false);
final BufferedImage getImage =
renderMap(resultSet, loadFactors, initialCapacities, worstGetTime, bestGetTime, true);
final String putFileName = "size_" + sampleSize + "_hlimit_" + hashLimit + "_puts.png";
final String getFileName = "size_" + sampleSize + "_hlimit_" + hashLimit + "_gets.png";
writeImage(putImage, limitFolder, putFileName);
writeImage(getImage, limitFolder, getFileName);
}
}
}
private static File makeFolder(final File parent, final String folder) throws IOException {
final File child = new File(parent, folder);
if(!child.exists())
child.mkdir();
return child;
}
private static BufferedImage renderMap(final Set<Result> results, final List<Float> loadFactors,
final List<Integer> initialCapacities, final float worst, final float best,
final boolean get) {
final Color[][] map = new Color[initialCapacities.size()][loadFactors.size()];
for(final Result result : results) {
final int x = initialCapacities.indexOf(result.initialCapacity);
final int y = loadFactors.indexOf(result.loadFactor);
final float time = get ? result.averageGetTime : result.averagePutTime;
final float score = (time - best)/(worst - best);
final Color c = new Color(score, 1.0f - score, 0.0f);
map[x][y] = c;
}
final int imageWidth = initialCapacities.size() * 40 + 50;
final int imageHeight = loadFactors.size() * 40 + 50;
final BufferedImage image =
new BufferedImage(imageWidth, imageHeight, BufferedImage.TYPE_3BYTE_BGR);
final Graphics2D g = image.createGraphics();
g.setColor(Color.WHITE);
g.fillRect(0, 0, imageWidth, imageHeight);
for(int x = 0; x < map.length; ++x) {
for(int y = 0; y < map[x].length; ++y) {
g.setColor(map[x][y]);
g.fillRect(50 + x*40, imageHeight - 50 - (y+1)*40, 40, 40);
g.setColor(Color.BLACK);
g.drawLine(25, imageHeight - 50 - (y+1)*40, 50, imageHeight - 50 - (y+1)*40);
final Float loadFactor = loadFactors.get(y);
g.drawString(df.format(loadFactor), 10, imageHeight - 65 - (y)*40);
}
g.setColor(Color.BLACK);
g.drawLine(50 + (x+1)*40, imageHeight - 50, 50 + (x+1)*40, imageHeight - 15);
final int initialCapacity = initialCapacities.get(x);
g.drawString(((initialCapacity%1000 == 0) ? "" + (initialCapacity/1000) + "K" : "" + initialCapacity), 15 + (x+1)*40, imageHeight - 25);
}
g.drawLine(25, imageHeight - 50, imageWidth, imageHeight - 50);
g.drawLine(50, 0, 50, imageHeight - 25);
g.dispose();
return image;
}
private static void writeImage(final BufferedImage image, final File folder,
final String filename) throws IOException {
final File imageFile = new File(folder, filename);
ImageIO.write(image, "png", imageFile);
}
}
ผลลัพธ์ที่มองเห็นได้มีดังนี้:
- การทดสอบจะแบ่งตามขนาดคอลเลกชันก่อนจากนั้นตามขีด จำกัด แฮช
- สำหรับการทดสอบแต่ละครั้งจะมีภาพที่แสดงเกี่ยวกับเวลาในการใส่เฉลี่ย (ต่อ 10 ครั้ง) และเวลารับเฉลี่ย (ต่อ 10 ครั้ง) ภาพเหล่านี้เป็น "แผนที่ความร้อน" สองมิติที่แสดงสีต่อการรวมกันของความจุเริ่มต้นและปัจจัยการรับน้ำหนัก
- สีในภาพจะขึ้นอยู่กับเวลาเฉลี่ยในระดับมาตรฐานจากผลที่ดีที่สุดไปจนถึงแย่ที่สุดตั้งแต่สีเขียวอิ่มตัวไปจนถึงสีแดงอิ่มตัว กล่าวอีกนัยหนึ่งเวลาที่ดีที่สุดจะเป็นสีเขียวเต็มที่ในขณะที่เวลาที่เลวร้ายที่สุดจะเป็นสีแดงเต็มที่ การวัดเวลาที่ต่างกันสองค่าไม่ควรมีสีเดียวกัน
- แผนที่สีจะคำนวณแยกกันสำหรับการใส่และการรับ แต่ครอบคลุมการทดสอบทั้งหมดสำหรับหมวดหมู่ที่เกี่ยวข้อง
- การแสดงภาพแสดงความจุเริ่มต้นบนแกน x และโหลดแฟกเตอร์บนแกน y
โดยไม่ต้องกังวลใจอีกต่อไปมาดูผลลัพธ์กันดีกว่า ฉันจะเริ่มต้นด้วยผลลัพธ์สำหรับการวาง
ใส่ผลลัพธ์
ขนาดคอลเลกชัน: 100 ขีด จำกัด แฮช: 50 ซึ่งหมายความว่าแต่ละรหัสแฮชควรเกิดขึ้นสองครั้งและคีย์อื่น ๆ ทั้งหมดจะชนกันในแผนที่แฮช
ดีที่เริ่มต้นได้ไม่ดีนัก เราเห็นว่ามีฮอตสปอตขนาดใหญ่สำหรับความจุเริ่มต้น 25% เหนือขนาดคอลเลกชันโดยมีปัจจัยการโหลด 1 ที่มุมล่างซ้ายทำงานได้ไม่ดีนัก
ขนาดคอลเลคชัน: 100 ขีด จำกัด แฮช: 90 คีย์ 1 ใน 10 มีรหัสแฮชซ้ำกัน
นี่เป็นสถานการณ์ที่เหมือนจริงกว่าเล็กน้อยไม่มีฟังก์ชันแฮชที่สมบูรณ์แบบ แต่ยังเกิน 10% ฮอตสปอตหายไป แต่เห็นได้ชัดว่าการรวมกันของความจุเริ่มต้นต่ำกับปัจจัยการโหลดต่ำไม่ทำงาน
ขนาดคอลเลกชัน: 100 ขีด จำกัด แฮช: 100 แต่ละคีย์เป็นรหัสแฮชเฉพาะของตัวเอง คาดว่าจะไม่มีการชนกันหากมีที่เก็บข้อมูลเพียงพอ
ความจุเริ่มต้นที่ 100 พร้อมตัวประกอบการโหลด 1 ดูเหมือนจะดี น่าแปลกที่ความจุเริ่มต้นที่สูงขึ้นพร้อมกับปัจจัยการรับน้ำหนักที่ต่ำกว่านั้นไม่จำเป็นต้องดี
ขนาดคอลเลกชัน: 1,000 ขีด จำกัด แฮช: 500 รายการเริ่มจริงจังมากขึ้นที่นี่โดยมี 1,000 รายการ เช่นเดียวกับในการทดสอบครั้งแรกแฮชเกินกำลัง 2 ต่อ 1
มุมล่างซ้ายยังทำได้ไม่ดี แต่ดูเหมือนว่าจะมีความสมมาตรระหว่างคำสั่งผสมของจำนวนเริ่มต้นที่ต่ำกว่า / ปัจจัยการรับน้ำหนักสูงและจำนวนเริ่มต้นที่สูงขึ้น / ปัจจัยการโหลดต่ำ
ขนาดคอลเล็กชัน: 1,000 ขีด จำกัด แฮช: 900 ซึ่งหมายความว่ารหัสแฮช 1 ใน 10 จะเกิดขึ้นสองครั้ง สถานการณ์ที่เหมาะสมเกี่ยวกับการชน
มีบางอย่างที่ตลกมากที่เกิดขึ้นกับคอมโบที่ไม่น่าจะเป็นไปได้ของความจุเริ่มต้นที่ต่ำเกินไปโดยมีปัจจัยการโหลดสูงกว่า 1 ซึ่งค่อนข้างตอบโต้ได้ง่าย มิฉะนั้นจะยังค่อนข้างสมมาตร
ขนาดคอลเลกชัน: 1,000 ขีด จำกัด แฮช: 990 การชนกันบางครั้ง แต่มีเพียงไม่กี่ชิ้น ค่อนข้างสมจริงในแง่นี้
เรามีความสมมาตรที่ดีตรงนี้ มุมล่างด้านซ้ายยังคงไม่เหมาะสม แต่คอมโบ 1,000 init capacity / 1.0 load factor เทียบกับ 1250 init capacity / 0.75 load factor อยู่ในระดับเดียวกัน
ขนาดคอลเลกชัน: 1,000 ขีด จำกัด แฮช: 1,000 ไม่มีรหัสแฮชซ้ำ แต่ตอนนี้มีขนาดตัวอย่าง 1,000
ไม่มากที่จะกล่าวที่นี่ การรวมกันของกำลังการผลิตเริ่มต้นที่สูงขึ้นกับปัจจัยการโหลด 0.75 ดูเหมือนว่าจะมีประสิทธิภาพดีกว่าการรวมกำลังการผลิตเริ่มต้น 1,000 ครั้งโดยมีค่าโหลด 1 เท่า
ขนาดคอลเลกชัน: 100_000. ขีด จำกัด แฮช: 10_000 เอาล่ะตอนนี้เริ่มจริงจังแล้วโดยมีขนาดตัวอย่างรหัสแฮช 1 แสนและ 100 รายการต่อคีย์
อือ! ฉันคิดว่าเราพบสเปกตรัมที่ต่ำกว่า ความจุในการเริ่มต้นของขนาดคอลเลกชันที่มีค่าน้ำหนักเท่ากับ 1 นั้นทำได้ดีมากที่นี่ แต่นอกเหนือจากนั้นก็มีอยู่ทั่วร้าน
ขนาดคอลเลกชัน: 100_000. ขีด จำกัด แฮช: 90_000 มีความเป็นจริงมากกว่าการทดสอบก่อนหน้าเล็กน้อยที่นี่เรามีแฮชโค้ดเกินพิกัด 10%
มุมล่างซ้ายยังไม่เป็นที่ต้องการ ความจุเริ่มต้นที่สูงขึ้นจะทำงานได้ดีที่สุด
ขนาดคอลเลกชัน: 100_000. ขีด จำกัด แฮช: 99_000 สถานการณ์ที่ดีนี้ คอลเลกชันขนาดใหญ่ที่มีโค้ดแฮชเกิน 1%
การใช้ขนาดคอลเลกชันที่แน่นอนเป็นความจุเริ่มต้นด้วยปัจจัยการโหลด 1 ชนะที่นี่ ความสามารถในการเริ่มต้นที่ใหญ่กว่าเล็กน้อยทำงานได้ค่อนข้างดี
ขนาดคอลเลกชัน: 100_000. ขีด จำกัด แฮช: 100_000 อันใหญ่. คอลเลกชันที่ใหญ่ที่สุดพร้อมฟังก์ชันแฮชที่สมบูรณ์แบบ
บางสิ่งที่น่าแปลกใจที่นี่ ความจุเริ่มต้นพร้อมห้องเพิ่มเติม 50% ที่ปัจจัยการบรรทุก 1 ครั้ง
เอาล่ะเท่านี้ก็เรียบร้อย ตอนนี้เราจะตรวจสอบการได้รับ โปรดจำไว้ว่าแผนที่ด้านล่างทั้งหมดสัมพันธ์กับเวลาที่ได้รับดีที่สุด / แย่ที่สุดเวลาวางจะไม่ถูกนำมาพิจารณาอีกต่อไป
รับผลลัพธ์
ขนาดคอลเล็กชัน: 100 ขีด จำกัด แฮช: 50 ซึ่งหมายความว่าแต่ละรหัสแฮชควรเกิดขึ้นสองครั้งและคาดว่าคีย์อื่น ๆ จะชนกันในแผนที่แฮช
เอ๊ะ ... อะไรนะ?
ขนาดคอลเลคชัน: 100 ขีด จำกัด แฮช: 90 คีย์ 1 ใน 10 มีรหัสแฮชซ้ำกัน
โอ้โฮเนลลี่! นี่เป็นสถานการณ์ที่น่าจะมีความสัมพันธ์กับคำถามของผู้ถามมากที่สุดและเห็นได้ชัดว่าความจุเริ่มต้นที่ 100 โดยมีปัจจัยการรับน้ำหนักเป็น 1 เป็นหนึ่งในสิ่งที่แย่ที่สุดที่นี่! ฉันสาบานว่าฉันไม่ได้แกล้งทำสิ่งนี้
ขนาดคอลเลกชัน: 100 ขีด จำกัด แฮช: 100 แต่ละคีย์เป็นรหัสแฮชเฉพาะของตัวเอง ไม่คาดว่าจะมีการชนกัน
นี้ดูสงบขึ้นเล็กน้อย ส่วนใหญ่ผลลัพธ์เดียวกันทั่วกระดาน
ขนาดคอลเลกชัน: 1,000 ขีด จำกัด แฮช: 500 เช่นเดียวกับในการทดสอบครั้งแรกมีแฮชเกิน 2 ต่อ 1 แต่ตอนนี้มีจำนวนรายการมากขึ้น
ดูเหมือนว่าการตั้งค่าใด ๆ จะให้ผลลัพธ์ที่ดีที่นี่
ขนาดคอลเล็กชัน: 1,000 ขีด จำกัด แฮช: 900 ซึ่งหมายความว่ารหัสแฮช 1 ใน 10 จะเกิดขึ้นสองครั้ง สถานการณ์ที่เหมาะสมเกี่ยวกับการชน
และเช่นเดียวกับที่วางไว้สำหรับการตั้งค่านี้เราได้รับความผิดปกติในจุดแปลก ๆ
ขนาดคอลเลกชัน: 1,000 ขีด จำกัด แฮช: 990 การชนกันบางครั้ง แต่มีเพียงไม่กี่ชิ้น ค่อนข้างสมจริงในแง่นี้
ประสิทธิภาพที่ดีทุกที่ประหยัดสำหรับการรวมกันของความจุเริ่มต้นที่สูงและปัจจัยการโหลดต่ำ ฉันคาดหวังสิ่งนี้ไว้สำหรับการวางเนื่องจากอาจคาดว่าจะมีการปรับขนาดแผนที่แฮชสองรายการ แต่ทำไมถึงได้รับ?
ขนาดคอลเลกชัน: 1,000 ขีด จำกัด แฮช: 1,000 ไม่มีรหัสแฮชซ้ำ แต่ตอนนี้มีขนาดตัวอย่าง 1,000
การแสดงภาพที่ไม่ชัดเจนทั้งหมด ดูเหมือนว่าจะได้ผลไม่ว่าจะเกิดอะไรขึ้น
ขนาดคอลเลกชัน: 100_000. ขีด จำกัด แฮช: 10_000 เข้าสู่ 100K อีกครั้งโดยมีรหัสแฮชทับซ้อนกันมากมาย
มันดูไม่สวยแม้ว่าจุดที่ไม่ดีจะมีการแปลเป็นภาษาท้องถิ่นมากก็ตาม ประสิทธิภาพที่นี่ดูเหมือนจะขึ้นอยู่กับการทำงานร่วมกันระหว่างการตั้งค่าเป็นส่วนใหญ่
ขนาดคอลเลกชัน: 100_000. ขีด จำกัด แฮช: 90_000 มีความเป็นจริงมากกว่าการทดสอบก่อนหน้าเล็กน้อยที่นี่เรามีแฮชโค้ดเกินพิกัด 10%
ความแปรปรวนมากแม้ว่าคุณจะเหล่คุณจะเห็นลูกศรชี้ไปที่มุมขวาบน
ขนาดคอลเลกชัน: 100_000. ขีด จำกัด แฮช: 99_000 สถานการณ์ที่ดีนี้ คอลเลกชันขนาดใหญ่ที่มีโค้ดแฮชเกิน 1%
วุ่นวายมาก. มันยากที่จะพบโครงสร้างมากมายที่นี่
ขนาดคอลเลกชัน: 100_000. ขีด จำกัด แฮช: 100_000 อันใหญ่. คอลเลกชันที่ใหญ่ที่สุดพร้อมฟังก์ชันแฮชที่สมบูรณ์แบบ
มีใครคิดว่าสิ่งนี้เริ่มดูเหมือนกราฟิก Atari หรือไม่? ดูเหมือนว่าจะรองรับความจุเริ่มต้นของขนาดคอลเลกชันที่แน่นอนคือ -25% หรือ + 50%
เอาล่ะได้เวลาสรุปแล้ว ...
- เกี่ยวกับเวลาใส่: คุณจะต้องหลีกเลี่ยงความจุเริ่มต้นที่ต่ำกว่าจำนวนรายการแผนที่ที่คาดไว้ หากทราบตัวเลขที่แน่นอนไว้ก่อนตัวเลขนั้นหรือสูงกว่าเล็กน้อยดูเหมือนจะทำงานได้ดีที่สุด ปัจจัยที่มีภาระงานสูงสามารถชดเชยขีดความสามารถเริ่มต้นที่ลดลงเนื่องจากการปรับขนาดแผนที่แฮชก่อนหน้านี้ สำหรับความจุเริ่มต้นที่สูงขึ้นดูเหมือนว่าจะไม่สำคัญมากนัก
- เกี่ยวกับเวลารับ: ผลลัพธ์จะวุ่นวายเล็กน้อยที่นี่ ไม่มีอะไรมากที่จะสรุป ดูเหมือนว่าจะต้องพึ่งพาอัตราส่วนที่ละเอียดอ่อนระหว่างการทับซ้อนของรหัสแฮชความจุเริ่มต้นและปัจจัยการโหลดโดยที่การตั้งค่าที่ไม่ดีบางอย่างจะทำงานได้ดีและการตั้งค่าที่ดีมีประสิทธิภาพอย่างมาก
- เห็นได้ชัดว่าฉันเต็มไปด้วยเรื่องไร้สาระเมื่อพูดถึงสมมติฐานเกี่ยวกับประสิทธิภาพของ Java ความจริงก็คือเว้นแต่คุณจะปรับแต่งการตั้งค่าของคุณให้เข้ากับการใช้งานได้อย่างสมบูรณ์แบบ
HashMap
ผลลัพธ์ก็จะเป็นไปได้ หากมีสิ่งหนึ่งที่ต้องหลีกเลี่ยงจากสิ่งนี้ก็คือขนาดเริ่มต้นเริ่มต้นที่ 16 นั้นค่อนข้างโง่สำหรับทุกอย่างยกเว้นแผนที่ที่เล็กที่สุดดังนั้นให้ใช้ตัวสร้างที่กำหนดขนาดเริ่มต้นหากคุณมีความคิดเกี่ยวกับลำดับของขนาด มันจะเป็น
- เรากำลังวัดเป็นนาโนวินาทีที่นี่ เวลาเฉลี่ยที่ดีที่สุดต่อ 10 ครั้งคือ 1179 ns และ 5105 ns ที่แย่ที่สุดในเครื่องของฉัน เวลาเฉลี่ยที่ดีที่สุดต่อ 10 ครั้งคือ 547 ns และ 3484 ns ที่แย่ที่สุด นั่นอาจเป็นความแตกต่างของปัจจัยที่ 6 แต่เรากำลังพูดถึงไม่ถึงมิลลิวินาที ในคอลเลกชันที่มีขนาดใหญ่กว่าที่ผู้โพสต์คิดไว้อย่างมาก
ก็แค่นั้นแหละ ฉันหวังว่ารหัสของฉันจะไม่มีการกำกับดูแลที่น่ากลัวซึ่งทำให้ทุกสิ่งที่ฉันโพสต์ที่นี่เป็นโมฆะ นี่เป็นเรื่องสนุกและฉันได้เรียนรู้ว่าในท้ายที่สุดคุณก็อาจพึ่งพา Java ในการทำงานได้ดีกว่าที่จะคาดหวังความแตกต่างอย่างมากจากการเพิ่มประสิทธิภาพเพียงเล็กน้อย นั่นไม่ได้หมายความว่าไม่ควรหลีกเลี่ยงบางสิ่ง แต่ส่วนใหญ่เรากำลังพูดถึงการสร้างสตริงที่ยาวสำหรับลูปโดยใช้โครงสร้างข้อมูลที่ไม่ถูกต้องและสร้างอัลกอริทึม O (n ^ 3)