निश्चित आकार के HashMap के लिए इष्टतम क्षमता और भार कारक क्या है?


85

मैं एक विशिष्ट मामले के लिए इष्टतम क्षमता और लोड कारक का पता लगाने की कोशिश कर रहा हूं। मुझे लगता है कि मुझे इसके बारे में जानकारी मिली है, लेकिन मैं अभी भी मेरे से अधिक जानकार किसी व्यक्ति की पुष्टि के लिए आभारी रहूंगा। :)

अगर मुझे पता है कि मेरा हैशपॉप 100 वस्तुओं को समाहित करने, कहने, और 100 वस्तुओं को रखने में अधिकांश समय व्यतीत करेगा, तो मैं अनुमान लगा रहा हूं कि इष्टतम मूल्य प्रारंभिक क्षमता 100 और लोड फैक्टर 1 हैं? या मुझे 101 की क्षमता की आवश्यकता है, या कोई अन्य गोचर्स हैं?

संपादित करें: ठीक है, मैंने कुछ घंटे अलग रखे और कुछ परीक्षण किया। यहाँ परिणाम हैं:

  • उत्सुकता से, क्षमता, क्षमता + 1, क्षमता + 2, क्षमता -1 और यहां तक ​​कि क्षमता -10 सभी बिल्कुल समान परिणाम देते हैं। मुझे उम्मीद है कि कम से कम क्षमता -1 और क्षमता -10 खराब परिणाम देंगे।
  • प्रारंभिक क्षमता का उपयोग करना (16 के डिफ़ॉल्ट मान के विपरीत) ध्यान देने योग्य पुट () सुधार - 30% तक तेज देता है।
  • 1 के लोड फैक्टर का उपयोग छोटी संख्या में वस्तुओं के लिए समान प्रदर्शन देता है, और बड़ी संख्या में वस्तुओं के लिए बेहतर प्रदर्शन (> 100000)। हालाँकि, यह वस्तुओं की संख्या के अनुपात में सुधार नहीं करता है; मुझे संदेह है कि अतिरिक्त कारक है जो परिणामों को प्रभावित करता है।
  • get () प्रदर्शन विभिन्न प्रकार की वस्तुओं / क्षमता के लिए थोड़ा अलग होता है, लेकिन हालाँकि यह केस से मामले में थोड़ा भिन्न हो सकता है, आमतौर पर यह प्रारंभिक क्षमता या लोड फैक्टर से प्रभावित नहीं होता है।

EDIT2: मेरी ओर से भी कुछ चार्ट जोड़ना। यहां उन मामलों में लोड फैक्टर 0.75 और 1 के बीच एक स्पष्ट अंतर है, जहां मैं हैशपैप को इनिशियलाइज़ करता हूं और इसे पूरी क्षमता तक भरता हूं। Y पैमाने पर ms में समय कम है (कम बेहतर है), और x स्केल आकार (ऑब्जेक्ट्स की संख्या) है। चूंकि आकार रैखिक रूप से बदलता है, इसलिए आवश्यक समय रैखिक रूप से भी बढ़ता है।

तो, आइए देखें कि मुझे क्या मिला। निम्नलिखित दो चार्ट लोड कारकों में अंतर दिखाते हैं। पहला चार्ट दिखाता है कि जब हैशपॉप क्षमता से भरा होता है तो क्या होता है; लोड फैक्टर 0.75 आकार बदलने की वजह से खराब प्रदर्शन करता है। हालांकि, यह लगातार बदतर नहीं है, और सभी प्रकार के धक्कों और हॉप्स हैं - मुझे लगता है कि जीसी का इसमें एक प्रमुख खेल है। लोड फैक्टर 1.25 1 के समान प्रदर्शन करता है, इसलिए यह चार्ट में शामिल नहीं है।

पूरी तरह से भरा हुआ

यह चार्ट साबित करता है कि आकार बदलने के कारण 0.75 बदतर था; अगर हम हाशप को आधी क्षमता तक भरते हैं, तो 0.75 भी बदतर नहीं है, बस ... अलग (और इसे कम मेमोरी का उपयोग करना चाहिए और इसमें अनपेक्षित रूप से बेहतर पुनरावृत्ति प्रदर्शन होना चाहिए)।

आधा भरा

एक और बात मैं दिखाना चाहता हूं। यह सभी तीन लोड कारकों और अलग-अलग हैशपॉप आकारों के लिए प्रदर्शन प्राप्त करता है। लोड फैक्टर के लिए एक स्पाइक को छोड़कर लगातार थोड़े बदलाव के साथ लगातार, मैं वास्तव में जानना चाहता हूं कि वह क्या है (शायद जीसी, लेकिन कौन जानता है)।

स्पाइक जाओ

और यहाँ रुचि रखने वालों के लिए कोड है:

import java.util.HashMap;
import java.util.Map;

public class HashMapTest {

  // capacity - numbers high as 10000000 require -mx1536m -ms1536m JVM parameters
  public static final int CAPACITY = 10000000;
  public static final int ITERATIONS = 10000;

  // set to false to print put performance, or to true to print get performance
  boolean doIterations = false;

  private Map<Integer, String> cache;

  public void fillCache(int capacity) {
    long t = System.currentTimeMillis();
    for (int i = 0; i <= capacity; i++)
      cache.put(i, "Value number " + i);

    if (!doIterations) {
      System.out.print(System.currentTimeMillis() - t);
      System.out.print("\t");
    }
  }

  public void iterate(int capacity) {
    long t = System.currentTimeMillis();

    for (int i = 0; i <= ITERATIONS; i++) {
      long x = Math.round(Math.random() * capacity);
      String result = cache.get((int) x);
    }

    if (doIterations) {
      System.out.print(System.currentTimeMillis() - t);
      System.out.print("\t");
    }
  }

  public void test(float loadFactor, int divider) {
    for (int i = 10000; i <= CAPACITY; i+= 10000) {
      cache = new HashMap<Integer, String>(i, loadFactor);
      fillCache(i / divider);
      if (doIterations)
        iterate(i / divider);
    }
    System.out.println();
  }

  public static void main(String[] args) {
    HashMapTest test = new HashMapTest();

    // fill to capacity
    test.test(0.75f, 1);
    test.test(1, 1);
    test.test(1.25f, 1);

    // fill to half capacity
    test.test(0.75f, 2);
    test.test(1, 2);
    test.test(1.25f, 2);
  }

}

1
इष्टतम इस अर्थ में कि चूक को बदलना इस मामले के लिए बेहतर प्रदर्शन (तेज पुट () निष्पादन) देता है।
डोमची

2
@Peter जीसी = कचरा संग्रह।
डोमची

2
वे चार्ट साफ-सुथरे हैं ... आप उन्हें उत्पन्न / प्रस्तुत करने के लिए क्या उपयोग करेंगे?
G_H

1
@G_H कुछ भी नहीं फैंसी - उपरोक्त कार्यक्रम और एक्सेल का उत्पादन। :)
Domchi

2
अगली बार, लाइनों के बजाय बिंदुओं का उपयोग करें। यह तुलना को आसान बना देगा।
पॉल ड्रेपर

जवाबों:


74

ठीक है, इस चीज़ को आराम करने के लिए, मैंने कुछ परिदृश्यों को चलाने के लिए एक परीक्षण ऐप बनाया है और परिणामों के कुछ दृश्य प्राप्त किए हैं। यहाँ परीक्षण कैसे किए जाते हैं:

  • विभिन्न संग्रह आकारों की एक संख्या की कोशिश की गई है: एक सौ, एक हजार और एक सौ हजार प्रविष्टियां।
  • उपयोग की जाने वाली चाबियाँ एक वर्ग के उदाहरण हैं जो एक आईडी द्वारा विशिष्ट रूप से पहचानी जाती हैं। प्रत्येक परीक्षण अद्वितीय कुंजियों का उपयोग करता है, आईडी के रूप में पूर्णांक बढ़ाता है। equalsविधि केवल, आईडी का उपयोग करता है ताकि कोई कुंजी मानचित्रण एक दूसरे से अधिलेखित कर देता है।
  • कुंजी में एक हैश कोड होता है जिसमें कुछ पूर्व निर्धारित संख्या के मुकाबले उनकी आईडी के शेष मॉड्यूल होते हैं। हम उस नंबर को हैश लिमिट कहेंगे । इसने मुझे हैश टक्करों की संख्या को नियंत्रित करने की अनुमति दी जो अपेक्षित होगी। उदाहरण के लिए, यदि हमारे संग्रह का आकार 100 है, तो हमारे पास 0 से 99 तक की आईडी वाली चाबियां होंगी। यदि हैश सीमा 100 है, तो प्रत्येक कुंजी में एक अद्वितीय हैश कोड होगा। यदि हैश की सीमा 50 है, तो कुंजी 0 में कुंजी 50 के समान हैश कोड होगा, 1 में समान हैश कोड होगा जैसे 51 आदि। दूसरे शब्दों में, प्रति कुंजी हैश टक्कर की अपेक्षित संख्या हैश द्वारा विभाजित संग्रह आकार है। सीमा।
  • संग्रह आकार और हैश सीमा के प्रत्येक संयोजन के लिए, मैंने अलग-अलग सेटिंग्स के साथ आरम्भ किए गए हैश मानचित्रों का उपयोग करके परीक्षण चलाया है। ये सेटिंग्स लोड फैक्टर हैं, और एक प्रारंभिक क्षमता जिसे संग्रह सेटिंग के कारक के रूप में व्यक्त किया गया है। उदाहरण के लिए, 100 के संग्रह के आकार के साथ एक परीक्षण और 1.25 की प्रारंभिक क्षमता कारक 125 की प्रारंभिक क्षमता के साथ एक हैश मानचित्र को प्रारंभ करेगा।
  • प्रत्येक कुंजी का मान केवल एक नया है Object
  • प्रत्येक परीक्षा परिणाम एक परिणाम वर्ग के उदाहरण में समझाया गया है। सभी परीक्षणों के अंत में, परिणामों को सबसे खराब समग्र प्रदर्शन से सर्वश्रेष्ठ करने का आदेश दिया जाता है।
  • पुट्स और हो जाता है के लिए औसत समय की गणना 10 पुट / मिलती है।
  • जेआईटी संकलन प्रभाव को खत्म करने के लिए सभी परीक्षण संयोजन एक बार चलाए जाते हैं। उसके बाद, परीक्षण वास्तविक परिणामों के लिए चलाए जाते हैं।

यहाँ वर्ग है:

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 {

        //First entry of each array is the sample collection size, subsequent entries
        //are the hash limits
        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};

        //Doing a warmup run to eliminate JIT influence
        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();

        //Now for the real thing...
        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();
        }

//      ResultVisualizer.visualizeResults(results);

    }

    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 + "%");

        //Generating our sample key collection.
        final List<Key> keys = generateSamples(hashLimit, sampleSize);

        //Generating our value collection
        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);

        //Haha, what kind of noob explicitly calls for garbage collection?
        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) {

            //Equals implies same hashCode if limit is the same
            //Same hashCode doesn't necessarily implies equals

            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 करने के लिए परिणामों के दृश्य प्रतिनिधित्व को आउटपुट करता है। इसके लिए वर्ग नीचे दिया गया है। यदि आप इसे चलाना चाहते हैं, तो ऊपर दिए गए कोड में उपयुक्त लाइन को अनइंस्टॉल करें। सावधान रहें: विज़ुअलाइज़र क्लास मानती है कि आप विंडोज पर चल रहे हैं और 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) {

        //[x][y] => x is mapped to initial capacity, y is mapped to load factor
        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. इसका मतलब है कि प्रत्येक हैश कोड दो बार और हर दूसरे को हैश मानचित्र में टकरा जाना चाहिए।

size_100_hlimit_50_puts

खैर, यह बहुत अच्छा शुरू नहीं करता है। हम देखते हैं कि संग्रह आकार के ऊपर 25% की प्रारंभिक क्षमता के लिए एक बड़ा हॉटस्पॉट है, जिसमें 1 का लोड फैक्टर है। निचले बाएं कोने का प्रदर्शन बहुत अच्छा नहीं है।


संग्रह का आकार: 100. हैश सीमा: 90. दस कुंजियों में से एक में डुप्लिकेट हैश कोड होता है।

size_100_hlimit_90_puts

यह एक और अधिक यथार्थवादी परिदृश्य है, जिसमें एक पूर्ण हैश फ़ंक्शन नहीं है लेकिन फिर भी 10% अधिभार है। हॉटस्पॉट चला गया है, लेकिन कम लोड फैक्टर के साथ कम प्रारंभिक क्षमता का संयोजन स्पष्ट रूप से काम नहीं करता है।


संग्रह का आकार: 100. हैश सीमा: 100. प्रत्येक कुंजी का अपना विशिष्ट हैश कोड होता है। यदि पर्याप्त बाल्टियाँ हैं तो किसी भी टक्कर की उम्मीद नहीं है।

size_100_hlimit_100_puts

1 के लोड फैक्टर के साथ 100 की प्रारंभिक क्षमता ठीक लगती है। हैरानी की बात है, कम लोड कारक के साथ एक उच्च प्रारंभिक क्षमता जरूरी अच्छा नहीं है।


संग्रह का आकार: 1000. हैश सीमा: 500. यह 1000 से अधिक प्रविष्टियों के साथ यहां और अधिक गंभीर हो रहा है। पहले टेस्ट की तरह ही, इसमें 2 से 1 का हैश ओवरलोड है।

size_1000_hlimit_500_puts

निचले बाएं कोने में अभी भी अच्छा नहीं चल रहा है। लेकिन कम प्रारंभिक गिनती / उच्च लोड कारक और उच्च प्रारंभिक गणना / कम लोड कारक के कॉम्बो के बीच एक समरूपता प्रतीत होती है।


संग्रह का आकार: 1000. हैश सीमा: 900। इसका मतलब है कि दस हैश कोड में एक दो बार होगा। टकराव के संबंध में उचित परिदृश्य।

size_1000_hlimit_900_puts

वहाँ एक बहुत ही अजीब बात है एक प्रारंभिक क्षमता की संभावना कॉम्बो के साथ चल रहा है कि 1 से ऊपर एक लोड फैक्टर के साथ बहुत कम है, जो प्रति-सहज है। अन्यथा, अभी भी काफी सममित है।


संग्रह का आकार: 1000. हैश सीमा: 990. कुछ टकराव, लेकिन केवल कुछ। इस संबंध में काफी यथार्थवादी।

size_1000_hlimit_990_puts

हमें यहां एक अच्छी समरूपता मिली है। निचले बाएं कोने अभी भी उप-इष्टतम हैं, लेकिन कॉम्बो 1000 इनिट क्षमता / 1.0 लोड कारक बनाम 1250 इनिट क्षमता / 0.75 लोड फैक्टर समान स्तर पर हैं।


संग्रह का आकार: 1000। हैश सीमा: 1000। कोई डुप्लिकेट हैश कोड नहीं है, लेकिन अब 1000 का नमूना आकार है।

size_1000_hlimit_1000_puts

यहां ज्यादा कुछ नहीं कहा जा सकता है। 0.75 के लोड फैक्टर के साथ उच्च प्रारंभिक क्षमता का संयोजन 1 के लोड फैक्टर के साथ 1000 प्रारंभिक क्षमता के संयोजन को थोड़ा बेहतर बनाता है।


संग्रह का आकार: 100_000। हैश लिमिट: 10_000। ठीक है, यह अब गंभीर हो रहा है, प्रति सैकड़ा एक हजार और 100 हैश कोड के सैंपल साइज के साथ।

size_100000_hlimit_10000_puts

ओह! मुझे लगता है कि हमने अपना निचला स्पेक्ट्रम पाया। 1 के लोड फैक्टर के साथ वास्तव में संग्रह आकार की एक init क्षमता वास्तव में यहाँ अच्छी तरह से कर रही है, लेकिन इसके अलावा यह पूरी दुकान पर है।


संग्रह का आकार: 100_000। हैश लिमिट: 90_000। पिछले परीक्षण की तुलना में थोड़ा अधिक यथार्थवादी, यहाँ हमें हैश कोड में 10% अधिभार मिला है।

size_100000_hlimit_90000_puts

निचले बाएँ कोने अभी भी अवांछनीय है। उच्च प्रारंभिक क्षमता सबसे अच्छा काम करती है।


संग्रह का आकार: 100_000। हैश लिमिट: 99_000। अच्छा परिदृश्य, यह। 1% हैश कोड अधिभार के साथ एक बड़ा संग्रह।

size_100000_hlimit_99000_puts

1 संग्रह के भार कारक के साथ init क्षमता के रूप में सटीक संग्रह आकार का उपयोग करके यहाँ से जीतता है! हालांकि थोड़ा बड़ा इनिट कैपेसिटी काफी अच्छा काम करता है।


संग्रह का आकार: 100_000। हैश लिमिट: 100_000। बड़ा वाला। एक सही हैश फ़ंक्शन के साथ सबसे बड़ा संग्रह।

size_100000_hlimit_100000_puts

कुछ आश्चर्यजनक सामान यहाँ। 1 जीत के लोड कारक पर 50% अतिरिक्त कमरे के साथ एक प्रारंभिक क्षमता।


ठीक है, यह पुट के लिए है। अब, हम मिल की जाँच करेंगे। याद रखें, नीचे दिए गए नक्शे सभी सबसे अच्छे / सबसे खराब समय के सापेक्ष हैं, पुट टाइम को ध्यान में नहीं रखा जाता है।

परिणाम प्राप्त करें


संग्रह का आकार: 100. हैश सीमा: 50. इसका मतलब है कि प्रत्येक हैश कोड दो बार होना चाहिए और हर दूसरे कुंजी को हैश मानचित्र में टकराने की उम्मीद थी।

size_100_hlimit_50_gets

एह ... क्या?


संग्रह का आकार: 100. हैश सीमा: 90. दस कुंजियों में से एक में डुप्लिकेट हैश कोड होता है।

size_100_hlimit_90_gets

वाह नेली! यह पूछने वाले के प्रश्न के साथ सहसंबंधित करने के लिए सबसे अधिक संभावना परिदृश्य है, और जाहिर तौर पर 1 के लोड फैक्टर के साथ 100 की प्रारंभिक क्षमता यहां सबसे खराब चीजों में से एक है! मैं कसम खाता हूँ कि मैं यह नकली नहीं था।


संग्रह का आकार: 100. हैश सीमा: 100. प्रत्येक कुंजी का अपना विशिष्ट हैश कोड होता है। कोई टक्कर की उम्मीद नहीं है।

size_100_hlimit_100_gets

यह थोड़ा और शांतिपूर्ण लगता है। ज्यादातर बोर्ड भर में एक ही परिणाम।


संग्रह का आकार: 1000. हैश सीमा: 500। पहले टेस्ट की तरह, इसमें 2 से 1 का हैश ओवरलोड है, लेकिन अब बहुत अधिक प्रविष्टियों के साथ।

size_1000_hlimit_500_gets

ऐसा लगता है कि किसी भी सेटिंग से यहां अच्छा परिणाम मिलेगा।


संग्रह का आकार: 1000. हैश सीमा: 900। इसका मतलब है कि दस हैश कोड में एक दो बार होगा। टकराव के संबंध में उचित परिदृश्य।

size_1000_hlimit_900_gets

और बस इस सेटअप के लिए पुट्स के साथ, हमें एक अजीब जगह में एक विसंगति मिलती है।


संग्रह का आकार: 1000. हैश सीमा: 990. कुछ टकराव, लेकिन केवल कुछ। इस संबंध में काफी यथार्थवादी।

size_1000_hlimit_990_gets

हर जगह निर्णय लेना, कम भार कारक के साथ उच्च प्रारंभिक क्षमता के संयोजन के लिए बचत करना। मुझे उम्मीद है कि पुट के लिए, दो हैश मानचित्र के आकार की उम्मीद की जा सकती है। पर क्यों मिलता है?


संग्रह का आकार: 1000। हैश सीमा: 1000। कोई डुप्लिकेट हैश कोड नहीं है, लेकिन अब 1000 का नमूना आकार है।

size_1000_hlimit_1000_gets

एक पूर्ण अलौकिक दृश्य। यह काम करने लगता है चाहे कुछ भी हो।


संग्रह का आकार: 100_000। हैश लिमिट: 10_000। फिर से 100K में जा रहा है, पूरे हैश कोड ओवरलैप के साथ।

size_100000_hlimit_10000_gets

यह सुंदर नहीं दिखता है, हालांकि खराब स्थान बहुत स्थानीय हैं। यहाँ प्रदर्शन सेटिंग्स के बीच एक निश्चित तालमेल पर काफी हद तक निर्भर करता है।


संग्रह का आकार: 100_000। हैश लिमिट: 90_000। पिछले परीक्षण की तुलना में थोड़ा अधिक यथार्थवादी, यहाँ हमें हैश कोड में 10% अधिभार मिला है।

size_100000_hlimit_90000_gets

बहुत अधिक विचरण, हालांकि यदि आप स्क्विंट करते हैं तो आप ऊपरी दाहिने कोने की ओर इशारा करते हुए एक तीर देख सकते हैं।


संग्रह का आकार: 100_000। हैश लिमिट: 99_000। अच्छा परिदृश्य, यह। 1% हैश कोड अधिभार के साथ एक बड़ा संग्रह।

size_100000_hlimit_99000_gets

बहुत अराजक। यहां बहुत संरचना खोजना मुश्किल है।


संग्रह का आकार: 100_000। हैश लिमिट: 100_000। बड़ा वाला। एक सही हैश फ़ंक्शन के साथ सबसे बड़ा संग्रह।

size_100000_hlimit_100000_gets

किसी और को लगता है कि यह अटारी ग्राफिक्स जैसा दिखने लगा है? यह वास्तव में संग्रह आकार, -25% या + 50% की प्रारंभिक क्षमता का पक्ष लेता है।


ठीक है, अब निष्कर्ष के लिए समय है ...

  • पुट टाइम के बारे में: आप प्रारंभिक क्षमताओं से बचना चाहते हैं जो मानचित्र प्रविष्टियों की अपेक्षित संख्या से कम हों। यदि एक सटीक संख्या पहले से ज्ञात है, तो वह संख्या या थोड़ा ऊपर यह सबसे अच्छा काम करता है। उच्च लोड कारक पहले के हैश मानचित्र के आकार के कारण कम प्रारंभिक क्षमताओं को ऑफसेट कर सकते हैं। उच्च प्रारंभिक क्षमताओं के लिए, वे उतना मायने नहीं रखते हैं।
  • समय के बारे में: परिणाम यहाँ थोड़ा अव्यवस्थित हैं। निष्कर्ष निकालने के लिए बहुत कुछ नहीं है। यह हैश कोड ओवरलैप, प्रारंभिक क्षमता और लोड फैक्टर के बीच सूक्ष्म अनुपात पर बहुत अधिक भरोसा करता है, कुछ माना जाता है कि खराब सेटअप अच्छा प्रदर्शन कर रहे हैं और अच्छे सेटअप शानदार प्रदर्शन कर रहे हैं।
  • मैं जाहिरा तौर पर बकवास से भरा हुआ हूं जब यह जावा प्रदर्शन के बारे में मान्यताओं की बात आती है। सच्चाई यह है कि जब तक आप अपनी सेटिंग्स को पूरी तरह से लागू करने के लिए तैयार नहीं होते हैं, तब तक HashMapपरिणाम सभी जगह होने वाले हैं। अगर इसमें से एक चीज़ निकालनी है, तो यह है कि 16 का डिफ़ॉल्ट प्रारंभिक आकार किसी भी चीज़ के लिए थोड़ा गूंगा है, लेकिन सबसे छोटे नक्शे हैं, इसलिए एक निर्माता का उपयोग करें जो प्रारंभिक आकार सेट करता है यदि आपके पास आकार के क्रम के बारे में किसी प्रकार का विचार है यह होने वाला है।
  • हम यहाँ नैनोसेकंड में माप रहे हैं। प्रति 10 मिनट में सबसे अच्छा औसत समय 1179 एनएस था और मेरी मशीन पर सबसे खराब 5105 एनएस। प्रति 10 में सबसे अच्छा औसत समय 547 एनएस और सबसे खराब 3484 एनएस था। यह एक कारक 6 अंतर हो सकता है, लेकिन हम एक मिलीसेकंड से कम बात कर रहे हैं। उन संग्रहों पर जो मूल पोस्टर की तुलना में बहुत बड़े हैं, जो उनके दिमाग में थे।

हां इसी तरह। मुझे उम्मीद है कि मेरे कोड में कुछ भयावह ओवरसाइट नहीं हैं जो मेरे द्वारा पोस्ट की गई हर चीज को अमान्य करते हैं। यह मज़ेदार रहा है, और मैंने सीखा है कि अंत में आप छोटे कामों से बहुत अंतर की अपेक्षा जावा पर अपना काम करने के लिए भरोसा कर सकते हैं। यह कहना नहीं है कि कुछ सामान से बचा नहीं जाना चाहिए, लेकिन फिर हम ज्यादातर लूप्स के लिए लंबे स्ट्रिंग्स का निर्माण करने की बात कर रहे हैं, गलत डेटास्ट्रक्चर का उपयोग कर रहे हैं और ओ (एन ^ 3) एल्गोरिदम बना रहे हैं।


1
प्रयास के लिए धन्यवाद, बहुत अच्छा लग रहा है! आलसी होने के लिए नहीं, मैंने अपने परिणामों के साथ कुछ सुंदर रेखांकन भी जोड़े। मेरे परीक्षण आपकी तुलना में थोड़ा अधिक क्रूर बल हैं, लेकिन मैंने पाया कि बड़े मानचित्रों का उपयोग करते समय अंतर अधिक ध्यान देने योग्य होते हैं। छोटे नक्शे के साथ, आप जो कुछ भी करते हैं, आप याद नहीं कर सकते हैं। JVM ऑप्टिमाइज़ेशन और GC के कारण प्रदर्शन अव्यवस्थित हो जाता है, और मेरा एक सिद्धांत है कि आपके कुछ छोटे डेटासेट के लिए किसी भी मजबूत निष्कर्ष को उस अराजकता द्वारा खाया गया।
डोमची

प्रदर्शन पाने के लिए एक और टिप्पणी। यह अराजक लगता है, लेकिन मैंने पाया कि यह एक बहुत ही संकीर्ण सीमा में बहुत भिन्न होता है, लेकिन कुल मिलाकर, यह निरंतर और नरक के रूप में उबाऊ है। मुझे कभी-कभी अजीब तरह के स्पाइक्स मिलते थे जैसे कि आपने 100/90 पर किया था। मैं इसकी व्याख्या नहीं कर सकता, लेकिन व्यवहार में यह संभव नहीं है।
डोमची

G_H, कृपया मेरे उत्तर पर एक नज़र डालें, मुझे पता है कि यह एक बहुत पुराना धागा है, लेकिन संभवतः आपके परीक्षणों को इस बात को ध्यान में रखकर फिर से तैयार किया जाना चाहिए।
Durron597

अरे आपको एक सम्मेलन पत्र के रूप में एसीएम को यह पोस्ट करना चाहिए :) क्या प्रयास है!
येरिलबिल्बिन

12

यह एक बहुत अच्छा धागा है, इसके अलावा एक महत्वपूर्ण चीज है जो आप याद कर रहे हैं। तुमने कहा था:

उत्सुकता से, क्षमता, क्षमता + 1, क्षमता + 2, क्षमता -1 और यहां तक ​​कि क्षमता -10 सभी बिल्कुल समान परिणाम देते हैं। मुझे उम्मीद है कि कम से कम क्षमता -1 और क्षमता -10 खराब परिणाम देंगे।

स्रोत कोड प्रारंभिक क्षमता को अगले उच्चतम पावर ऑफ़-टू-टू में आंतरिक रूप से जोड़ता है। इसका मतलब है कि, उदाहरण के लिए, 513, 600, 700, 800, 800, 900, 1000, और 1024 की प्रारंभिक क्षमता सभी एक ही प्रारंभिक क्षमता (1024) का उपयोग करेंगे। यह @G_H द्वारा किए गए परीक्षण को अमान्य नहीं करता है, हालांकि, किसी को यह एहसास होना चाहिए कि यह उसके परिणामों का विश्लेषण करने से पहले किया जा रहा है। और यह कुछ परीक्षणों के अजीब व्यवहार की व्याख्या करता है।

यह JDK स्रोत के लिए निर्माता का अधिकार है:

/**
 * Constructs an empty <tt>HashMap</tt> with the specified initial
 * capacity and load factor.
 *
 * @param  initialCapacity the initial capacity
 * @param  loadFactor      the load factor
 * @throws IllegalArgumentException if the initial capacity is negative
 *         or the load factor is nonpositive
 */
public HashMap(int initialCapacity, float loadFactor) {
    if (initialCapacity < 0)
        throw new IllegalArgumentException("Illegal initial capacity: " +
                                           initialCapacity);
    if (initialCapacity > MAXIMUM_CAPACITY)
        initialCapacity = MAXIMUM_CAPACITY;
    if (loadFactor <= 0 || Float.isNaN(loadFactor))
        throw new IllegalArgumentException("Illegal load factor: " +
                                           loadFactor);

    // Find a power of 2 >= initialCapacity
    int capacity = 1;
    while (capacity < initialCapacity)
        capacity <<= 1;

    this.loadFactor = loadFactor;
    threshold = (int)(capacity * loadFactor);
    table = new Entry[capacity];
    init();
}

यह ताकतवर दिलचस्प है! मुझे इसका कोई अंदाजा नहीं था। वास्तव में समझाता है कि मैंने परीक्षणों में क्या देखा। और, फिर से, यह पुष्टि करता है कि समय से पहले अनुकूलन अक्सर उपयोगी होता है क्योंकि आप वास्तव में नहीं जानते (या वास्तव में जानने की आवश्यकता होनी चाहिए) आपके पीठ के पीछे कंपाइलर या कोड क्या कर सकता है। और फिर निश्चित रूप से यह प्रति संस्करण / कार्यान्वयन में भिन्न हो सकता है। इसे साफ़ करने के लिए धन्यवाद!
G_H

@G_H मैं आपके परीक्षणों को फिर से देखना पसंद करूंगा, इस जानकारी को देखते हुए संख्याओं को चुनना अधिक उपयुक्त होगा। उदाहरण के लिए, यदि मेरे पास 1200 तत्व हैं, तो क्या मुझे 1024 मानचित्र, 2048 मानचित्र या 4096 मानचित्र का उपयोग करना चाहिए? मुझे मूल प्रश्न का उत्तर नहीं पता है, इसीलिए मैंने इस सूत्र को शुरू किया। हालांकि, मुझे पता है कि अमरूद पलता अपने expectedSizeद्वारा 1.33जब आप करते हैंMaps.newHashMap(int expectedSize)
durron597

यदि हाशपैप पावर-टू-वैल्यू के लिए राउंड अप नहीं करेगा capacity, तो कुछ बकेट का उपयोग कभी नहीं किया जाएगा। मैप डेटा को कहाँ रखा जाए, इसके लिए बकेट इंडेक्स द्वारा निर्धारित किया जाता है bucketIndex = hashCode(key) & (capacity-1)। इसलिए यदि capacityदो की शक्ति के अलावा कुछ और था, तो द्विआधारी प्रतिनिधित्व में (capacity-1)कुछ शून्य होंगे, जिसका अर्थ है कि &(बाइनरी और ऑपरेशन) हैशकोड के कुछ निचले बिट्स को हमेशा शून्य करेंगे। उदाहरण: (capacity-1)है 111110के बजाय (62) 111111(63)। इस मामले में केवल सूचकांकों वाली बाल्टियों का भी उपयोग किया जा सकता है।
माइकल जिएर

2

बस साथ चलते हैं 101। मुझे वास्तव में यकीन नहीं है कि इसकी आवश्यकता है, लेकिन यह संभवतः कभी भी सुनिश्चित करने के प्रयास के लायक नहीं हो सकता है।

... बस जोड़ दो 1


संपादित करें: मेरे जवाब के लिए कुछ औचित्य।

पहले, मैं यह मान रहा हूं कि आपका HashMapइससे आगे नहीं बढ़ेगा 100; यदि ऐसा होता है, तो आपको लोड-फैक्टर को छोड़ देना चाहिए। इसी तरह, यदि आपकी चिंता प्रदर्शन है, तो लोड-फैक्टर को छोड़ दें । यदि आपकी चिंता स्मृति है, तो आप स्थैतिक आकार सेट करके कुछ बचा सकते हैं। यदि आप स्मृति में बहुत सारा सामान समेट रहे हैं तो यह करने योग्य हो सकता है; यानी, कई नक्शे संग्रहीत कर रहे हैं, या ढेर-अंतरिक्ष-तनाव-आकार के नक्शे बना रहे हैं।

दूसरा, मैं मूल्य चुनता हूं 101क्योंकि यह बेहतर पठनीयता प्रदान करता है ... अगर मैं आपके कोड को बाद में देख रहा हूं और देख रहा हूं कि आपने प्रारंभिक क्षमता निर्धारित कर ली है 100और आप इसे 100तत्वों के साथ लोड कर रहे हैं, तो मुझे यह करना होगा Javadoc के माध्यम से यह सुनिश्चित करने के लिए पढ़ें कि यह ठीक होने पर इसे आकार नहीं देगा 100। बेशक, मुझे वहाँ जवाब नहीं मिलेगा, इसलिए मुझे स्रोत को देखना होगा। यह इसके लायक नहीं है ... बस इसे छोड़ दो 101और हर कोई खुश है और कोई नहीं देख रहा है, हालांकि स्रोत-कोड java.util.HashMap। Hoorah।

तीसरा, का दावा है कि स्थापित करने HashMapके लिए क्या आप में से एक लोड फैक्टर के साथ उम्मीद की सही क्षमता को 1 " अपने देखने और प्रविष्टि के प्रदर्शन मार डालेगा " भले ही यह बोल्ड में बना है सिर्फ सच नहीं है,।

... यदि आपके पास nबाल्टियाँ हैं, और आप बेतरतीब ढंग nसे nबाल्टियों में आइटम असाइन करते हैं , तो हाँ, आप एक ही बाल्टी में वस्तुओं के साथ समाप्त करने जा रहे हैं, निश्चित रूप से ... लेकिन यह दुनिया का अंत नहीं है ... व्यवहार में, यह सिर्फ एक जोड़े की तुलना के बराबर है। वास्तव में, वहाँ esp है। थोड़ा अंतर जब आप समझते हैं कि विकल्प बाल्टी nमें आइटम असाइन कर रहा है n/0.75

इसके लिए मेरा शब्द लेने की जरूरत नहीं ...


त्वरित परीक्षण कोड:

static Random r = new Random();

public static void main(String[] args){
    int[] tests = {100, 1000, 10000};
    int runs = 5000;

    float lf_sta = 1f;
    float lf_dyn = 0.75f;

    for(int t:tests){
        System.err.println("=======Test Put "+t+"");
        HashMap<Integer,Integer> map = new HashMap<Integer,Integer>();
        long norm_put = testInserts(map, t, runs);
        System.err.print("Norm put:"+norm_put+" ms. ");

        int cap_sta = t;
        map = new HashMap<Integer,Integer>(cap_sta, lf_sta);
        long sta_put = testInserts(map, t, runs);
        System.err.print("Static put:"+sta_put+" ms. ");

        int cap_dyn = (int)Math.ceil((float)t/lf_dyn);
        map = new HashMap<Integer,Integer>(cap_dyn, lf_dyn);
        long dyn_put = testInserts(map, t, runs);
        System.err.println("Dynamic put:"+dyn_put+" ms. ");
    }

    for(int t:tests){
        System.err.println("=======Test Get (hits) "+t+"");
        HashMap<Integer,Integer> map = new HashMap<Integer,Integer>();
        fill(map, t);
        long norm_get_hits = testGetHits(map, t, runs);
        System.err.print("Norm get (hits):"+norm_get_hits+" ms. ");

        int cap_sta = t;
        map = new HashMap<Integer,Integer>(cap_sta, lf_sta);
        fill(map, t);
        long sta_get_hits = testGetHits(map, t, runs);
        System.err.print("Static get (hits):"+sta_get_hits+" ms. ");

        int cap_dyn = (int)Math.ceil((float)t/lf_dyn);
        map = new HashMap<Integer,Integer>(cap_dyn, lf_dyn);
        fill(map, t);
        long dyn_get_hits = testGetHits(map, t, runs);
        System.err.println("Dynamic get (hits):"+dyn_get_hits+" ms. ");
    }

    for(int t:tests){
        System.err.println("=======Test Get (Rand) "+t+"");
        HashMap<Integer,Integer> map = new HashMap<Integer,Integer>();
        fill(map, t);
        long norm_get_rand = testGetRand(map, t, runs);
        System.err.print("Norm get (rand):"+norm_get_rand+" ms. ");

        int cap_sta = t;
        map = new HashMap<Integer,Integer>(cap_sta, lf_sta);
        fill(map, t);
        long sta_get_rand = testGetRand(map, t, runs);
        System.err.print("Static get (rand):"+sta_get_rand+" ms. ");

        int cap_dyn = (int)Math.ceil((float)t/lf_dyn);
        map = new HashMap<Integer,Integer>(cap_dyn, lf_dyn);
        fill(map, t);
        long dyn_get_rand = testGetRand(map, t, runs);
        System.err.println("Dynamic get (rand):"+dyn_get_rand+" ms. ");
    }
}

public static long testInserts(HashMap<Integer,Integer> map, int test, int runs){
    long b4 = System.currentTimeMillis();

    for(int i=0; i<runs; i++){
        fill(map, test);
        map.clear();
    }
    return System.currentTimeMillis()-b4;
}

public static void fill(HashMap<Integer,Integer> map, int test){
    for(int j=0; j<test; j++){
        if(map.put(r.nextInt(), j)!=null){
            j--;
        }
    }
}

public static long testGetHits(HashMap<Integer,Integer> map, int test, int runs){
    long b4 = System.currentTimeMillis();

    ArrayList<Integer> keys = new ArrayList<Integer>();
    keys.addAll(map.keySet());

    for(int i=0; i<runs; i++){
        for(int j=0; j<test; j++){
            keys.get(r.nextInt(keys.size()));
        }
    }
    return System.currentTimeMillis()-b4;
}

public static long testGetRand(HashMap<Integer,Integer> map, int test, int runs){
    long b4 = System.currentTimeMillis();

    for(int i=0; i<runs; i++){
        for(int j=0; j<test; j++){
            map.get(r.nextInt());
        }
    }
    return System.currentTimeMillis()-b4;
}

परीक्षण के परिणाम:

=======Test Put 100
Norm put:78 ms. Static put:78 ms. Dynamic put:62 ms. 
=======Test Put 1000
Norm put:764 ms. Static put:763 ms. Dynamic put:748 ms. 
=======Test Put 10000
Norm put:12921 ms. Static put:12889 ms. Dynamic put:12873 ms. 
=======Test Get (hits) 100
Norm get (hits):47 ms. Static get (hits):31 ms. Dynamic get (hits):32 ms. 
=======Test Get (hits) 1000
Norm get (hits):327 ms. Static get (hits):328 ms. Dynamic get (hits):343 ms. 
=======Test Get (hits) 10000
Norm get (hits):3304 ms. Static get (hits):3366 ms. Dynamic get (hits):3413 ms. 
=======Test Get (Rand) 100
Norm get (rand):63 ms. Static get (rand):46 ms. Dynamic get (rand):47 ms. 
=======Test Get (Rand) 1000
Norm get (rand):483 ms. Static get (rand):499 ms. Dynamic get (rand):483 ms. 
=======Test Get (Rand) 10000
Norm get (rand):5190 ms. Static get (rand):5362 ms. Dynamic get (rand):5236 ms. 

पुन: ← - इस बारे में हैविभिन्न सेटिंग्स के बीच बहुत अंतर है


मेरे मूल उत्तर (पहली क्षैतिज रेखा से थोड़ा ऊपर) के संबंध में, यह जानबूझकर ग्लिब था क्योंकि ज्यादातर मामलों में , इस प्रकार का सूक्ष्म-अनुकूलन अच्छा नहीं है


@ ईजेपी, मेरा अनुमान गलत नहीं है। ऊपर संपादन देखें। आपका अनुमान गलत है कि किसका अनुमान सही है और किसका अनुमान गलत है।
बैडिट्रिक

(... हो सकता है कि मैं थोड़ा
नासमझ

3
आप EJP पर सही तरीके से नाराज हो सकते हैं, हालांकि अब मेरी बारी है? पी - जबकि मैं मानता हूं कि शीघ्रपतन शीघ्रपतन की तरह एक बहुत कुछ है, कृपया यह न मानें कि कुछ ऐसा जो आमतौर पर एक प्रयास के लायक नहीं है, मेरे मामले में एक प्रयास के लायक नहीं है। । मेरे मामले में, यह पर्याप्त महत्वपूर्ण है कि मैं अनुमान नहीं करना चाहता, इसलिए मैंने इसे देखा - मेरे मामले में +1 की आवश्यकता नहीं है (लेकिन हो सकता है कि आपकी प्रारंभिक / वास्तविक क्षमता समान नहीं है और लोडफ़ैक्टर 1 नहीं है, इस कलाकार को HashMap में देखने के लिए देखें: दहलीज = (int) (क्षमता * loadFactor)।
डोमची

@badroit आपने स्पष्ट रूप से कहा कि मुझे वास्तव में यकीन नहीं है कि इसकी आवश्यकता है '। इसलिए यह अनुमान था। अब जब आपने शोध कर लिया है और पोस्ट कर दिया है, तो यह अब अनुमान नहीं है, और जैसा कि आपने पहले से स्पष्ट रूप से नहीं किया था , यह स्पष्ट रूप से अनुमान लगाया गया था , अन्यथा आप निश्चित होते। 'गलत' के रूप में, Javadoc ने स्पष्ट रूप से 0.75 के लोड फैक्टर को जनादेश दिया है, जैसा कि कई दशकों के शोध और G_H का जवाब है। अंत में 'के रूप में यह संभवतः प्रयास के लायक नहीं हो सकता है' डोमची की टिप्पणी यहाँ देखें। बहुत कुछ ऐसा नहीं था जो सही था, हालांकि सामान्य रूप से मैं आपके साथ माइक्रो-ऑप्टिमाइज़ेशन के बारे में सहमत हूं।
लोर्ने की अगुवाई

आराम करो, सबको। हां, मेरा जवाब अतिरंजित बातें है। यदि आपके पास ऐसी 100 वस्तुएं हैं जिनके पास कोई जबरदस्त रूप से भारी equalsकार्य नहीं है, तो आप शायद उन्हें एक सूची में डालने और सिर्फ `कंटो 'का उपयोग करने से दूर हो जाएंगे। इस तरह के एक छोटे से सेट के साथ, प्रदर्शन में कभी भी बड़े अंतर नहीं होंगे। यह वास्तव में केवल महत्वपूर्ण है अगर गति या स्मृति चिंताएं अन्य सभी से ऊपर जाती हैं, या बराबर और हैश बहुत विशिष्ट हैं। मैं बाद में बड़े संग्रह और विभिन्न लोड कारकों और प्रारंभिक क्षमताओं के साथ एक परीक्षण करूँगा कि मैं बकवास से भरा हूं या नहीं।
G_H


1

से HashMapJavaDoc:

एक सामान्य नियम के रूप में, डिफ़ॉल्ट लोड कारक (.75) समय और अंतरिक्ष लागत के बीच एक अच्छा व्यापार प्रदान करता है। उच्चतर मान अंतरिक्ष उपरि को कम करते हैं, लेकिन लुकअप लागत को बढ़ाते हैं (हैशपॉप क्लास के अधिकांश परिचालनों में परिलक्षित होता है, जिसमें गेट और पुट शामिल हैं)। मानचित्र में प्रविष्टियों की अपेक्षित संख्या और इसके लोड कारक को इसकी प्रारंभिक क्षमता निर्धारित करते समय ध्यान में रखा जाना चाहिए, ताकि पुन: संचालन की संख्या को कम से कम किया जा सके। यदि प्रारंभिक क्षमता लोड फैक्टर द्वारा विभाजित प्रविष्टियों की अधिकतम संख्या से अधिक है, तो कोई भी पुन: संचालन नहीं होगा।

इसलिए यदि आप 100 प्रविष्टियों की उम्मीद कर रहे हैं, तो शायद 0.75 का लोड फैक्टर और सीलिंग की प्रारंभिक क्षमता (100 / 0.75) सबसे अच्छा होगा। वह 134 पर आ जाता है।

मुझे स्वीकार करना होगा, मैं निश्चित नहीं हूं कि उच्च लोड कारक के लिए लुकअप लागत अधिक क्यों होगी। सिर्फ इसलिए कि हाशपॉफ़ अधिक "भीड़" है इसका मतलब यह नहीं है कि अधिक वस्तुओं को एक ही बाल्टी में रखा जाएगा, है ना? यह केवल उनके हैश कोड पर निर्भर करता है, अगर मैं गलत नहीं हूँ। तो एक सभ्य हैश कोड स्प्रेड मानते हुए, ज्यादातर मामलों में अभी भी ओ (1) लोड कारक की परवाह किए बिना नहीं होना चाहिए?

संपादित करें: मुझे पोस्ट करने से पहले अधिक पढ़ना चाहिए ... बेशक हैश कोड सीधे कुछ आंतरिक सूचकांक पर मैप नहीं कर सकता है। इसे एक मूल्य तक घटाया जाना चाहिए जो वर्तमान क्षमता के अनुकूल हो। मतलब कि आपकी प्रारंभिक क्षमता जितनी अधिक होगी, आप उतने ही छोटे हैश की संख्या की उम्मीद कर सकते हैं। 1 की लोड फैक्टर के साथ सेट की गई आपकी वस्तु के आकार (या +1) को एक प्रारंभिक क्षमता चुनना वास्तव में यह सुनिश्चित करेगा कि आपका नक्शा कभी भी संशोधित नहीं किया गया है। हालाँकि, यह आपके लुकअप और इंसर्शन प्रदर्शन को मार देगा। एक आकार परिवर्तन अभी भी अपेक्षाकृत जल्दी है और केवल एक बार हो सकता है, जबकि लुकअप नक्शे के साथ किसी भी प्रासंगिक कार्य पर किया जाता है। नतीजतन, त्वरित लुकअप के लिए अनुकूलन वही है जो आप वास्तव में यहां चाहते हैं। आप यह जोड़ सकते हैं कि JavaDoc के अनुसार जैसा कि आप कभी भी आकार परिवर्तन नहीं कर सकते हैं: अपनी आवश्यक क्षमता लें, एक इष्टतम लोड फैक्टर (जैसे 0.75) से विभाजित करें और उस लोड फैक्टर के साथ प्रारंभिक क्षमता के रूप में उपयोग करें। सुनिश्चित करने के लिए 1 जोड़ें कि गोलाई आपको न मिले।


1
" यह आपके लुकअप और इंसर्शन प्रदर्शन को मार देगा "। यह अतिशयोक्तिपूर्ण / सादा-गलत है।
बैडिट्रिक

1
मेरे परीक्षण बताते हैं कि लुकअप प्रदर्शन 1 के लोड फैक्टर को सेट करने से प्रभावित नहीं होता है। प्रविष्टि प्रदर्शन वास्तव में सुधार हुआ है; चूंकि कोई आकार नहीं हैं, इसलिए यह तेज है। तो, आपका कथन एक सामान्य मामले के लिए सही है (तत्वों के स्माल संख्या के साथ हाशप के लिए खोज करना 0.75 के साथ 1 के साथ तेज होगा), लेकिन मेरे विशिष्ट मामले के लिए गलत है जब हाशप हमेशा अपनी अधिकतम क्षमता से भरा होता है, जो कभी नहीं बदलता है। प्रारंभिक आकार उच्चतर सेट करने का आपका सुझाव मेरे मामले के लिए दिलचस्प लेकिन अप्रासंगिक है क्योंकि मेरी तालिका नहीं बढ़ती है, इस प्रकार लोड फैक्टर केवल आकार बदलने के प्रकाश में महत्वपूर्ण है।
डोमची
हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.