একটি নির্দিষ্ট-আকারের হ্যাশম্যাপের অনুকূল ক্ষমতা এবং লোড ফ্যাক্টরটি কী?


86

আমি একটি নির্দিষ্ট কেসের জন্য অনুকূল ক্ষমতা এবং লোড ফ্যাক্টরটি বের করার চেষ্টা করছি। আমি মনে করি আমি এটির সংক্ষেপণ পেয়েছি, তবে আমার চেয়ে আরও জ্ঞাত জ্ঞানযোগ্য কারও কাছ থেকে নিশ্চিত হওয়ার জন্য আমি কৃতজ্ঞ। :)

যদি আমি জানি যে আমার হ্যাশম্যাপটি 100 টি অবজেক্ট ধারণ করে, বলে, এবং 100 টি অবজেক্ট রাখার বেশিরভাগ সময় ব্যয় করবে তবে আমি অনুমান করছি যে সর্বোত্তম মানগুলি প্রাথমিক ক্ষমতা 100 এবং লোড ফ্যাক্টর 1? বা আমার কি 101 এর সক্ষমতা প্রয়োজন, বা অন্য কোনও গেটচ আছে?

সম্পাদনা: ঠিক আছে, আমি কয়েক ঘন্টা রেখেছি এবং কিছু পরীক্ষা করেছি did ফলাফল এখানে:

  • কৌতূহলীভাবে, ক্ষমতা, ক্ষমতা +1, ক্ষমতা + 2, ক্ষমতা -1 এবং এমনকি ক্ষমতা -10 সমস্ত ঠিক একই ফলাফল দেয়। আমি কমপক্ষে ক্ষমতা -1 এবং ক্ষমতা -10 খারাপ ফলাফল দেওয়ার আশা করব expect
  • প্রাথমিক ক্ষমতা (16 টির ডিফল্ট মান ব্যবহারের বিপরীতে) ব্যবহার করা লক্ষণীয়ভাবে পুট () উন্নতি দেয় - 30% পর্যন্ত দ্রুত।
  • 1 এর লোড ফ্যাক্টরটি ব্যবহার করা অল্প সংখ্যক অবজেক্টের জন্য সমান কর্মক্ষমতা এবং বৃহত সংখ্যক অবজেক্টের (> 100000) এর জন্য আরও ভাল পারফরম্যান্স দেয়। তবে এটি বস্তুর সংখ্যার সাথে আনুপাতিকভাবে উন্নতি করে না; আমি সন্দেহ করি যে ফলাফলগুলিকে প্রভাবিত করে এমন অতিরিক্ত উপাদান রয়েছে।
  • get () পারফরম্যান্স বিভিন্ন সংখ্যক অবজেক্ট / ক্ষমতার জন্য কিছুটা আলাদা, তবে যদিও এটি কেস থেকে কেসে কিছুটা আলাদা হতে পারে, সাধারণত এটি প্রাথমিক ক্ষমতা বা লোড ফ্যাক্টরের দ্বারা প্রভাবিত হয় না।

সম্পাদনা 2: পাশাপাশি আমার অংশে কিছু চার্ট যুক্ত করা হচ্ছে। লোড ফ্যাক্টর 0.75 এবং 1 এর মধ্যে একটি চিত্রিত পার্থক্য এখানে, যেখানে আমি হ্যাশম্যাপ শুরু করি এবং এটি সম্পূর্ণ ক্ষমতা পর্যন্ত পূরণ করি। ওয়াই স্কেলে এমএসে সময় হয় (কম ভাল) এবং এক্স স্কেলটি আকার (অবজেক্টের সংখ্যা)। যেহেতু আকারটি রৈখিকভাবে পরিবর্তিত হয়, তাই প্রয়োজনীয় সময়ও রৈখিকভাবে বৃদ্ধি পায়।

সুতরাং, আসুন দেখি আমি কী পেয়েছি। নিম্নলিখিত দুটি চার্ট লোডের কারণগুলির মধ্যে পার্থক্য দেখায়। প্রথম চার্টটি দেখায় যে হ্যাশম্যাপ যখন ক্ষমতায় পূর্ণ হয় তখন কী হয়; লোড ফ্যাক্টর 0.75 পুনরায় আকার দেওয়ার কারণে আরও খারাপ সম্পাদন করে। যাইহোক, এটি ধারাবাহিকভাবে খারাপ নয়, এবং বিভিন্ন ধাক্কা এবং হপস রয়েছে - আমি অনুমান করি যে এতে জিসির একটি বড় ভূমিকা আছে। লোড ফ্যাক্টর 1.25 1 এর মতোই সম্পাদন করে, তাই এটি চার্টে অন্তর্ভুক্ত নয়।

সম্পূর্ণরূপে ভরা

এই চার্টটি প্রমাণ করে যে আকার পরিবর্তনের কারণে 0.75 আরও খারাপ ছিল; যদি আমরা হ্যাশম্যাপটি অর্ধেক ক্ষমতাতে পূরণ করি তবে 0.75 এর চেয়ে খারাপ নয়, কেবল ... ভিন্ন (এবং এটির কম স্মৃতি ব্যবহার করা উচিত এবং অজানা আরও ভাল পুনরাবৃত্তির পারফরম্যান্স থাকতে হবে)।

সম্পূর্ণ অর্ধেক

আরও একটি জিনিস আমি দেখাতে চাই। এটি তিনটি লোড ফ্যাক্টর এবং বিভিন্ন হ্যাশম্যাপ আকারের জন্য কার্যকারিতা পান। সামান্য পরিবর্তনের সাথে ধারাবাহিকভাবে ধ্রুবক, লোড ফ্যাক্টর 1 এর একটি স্পাইক ব্যতীত আমি সত্যিই এটি জানতে চাই যা (সম্ভবত জিসি, তবে কে জানে)।

স্পাইক যেতে

আগ্রহীদের জন্য কোডটি এখানে:

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);
  }

}

4
এক অর্থে অনুকূল যে ডিফল্ট পরিবর্তন করা এই ক্ষেত্রে ভাল পারফরম্যান্স দেয় (দ্রুত করা () কার্যকর))
ডোমিচি

4
@ পিটার জিসি = আবর্জনা সংগ্রহ।
ডোমচি

4
এই চার্টগুলি ঝরঝরে ... আপনি এগুলি উত্পন্ন / রেন্ডার করতে কী ব্যবহার করবেন?
জিএইচ

4
@ জিএইচএইচ তেমন কিছুই অভিনব নয় - উপরের প্রোগ্রাম এবং এক্সেলের ফলাফল। :)
ডোমচি

4
পরের বার, লাইনের পরিবর্তে পয়েন্টগুলি ব্যবহার করুন। এটি দৃষ্টিভঙ্গি তুলনা আরও সহজ করে তুলবে।
পল ড্রাগার

উত্তর:


74

ঠিক আছে, এই জিনিসটি বিশ্রামে রাখার জন্য, আমি বেশ কয়েকটি পরিস্থিতি চালানোর জন্য এবং ফলাফলের কিছু দৃশ্যধারণের জন্য একটি পরীক্ষা অ্যাপ তৈরি করেছি। পরীক্ষাগুলি কীভাবে করা হয় তা এখানে:

  • বিভিন্ন সংগ্রহের আকারের কয়েকটি চেষ্টা করা হয়েছে: একশ, এক হাজার এবং এক লক্ষ হাজার এন্ট্রি।
  • ব্যবহৃত কীগুলি কোনও শ্রেণীর উদাহরণ যা আইডি দ্বারা স্বতন্ত্রভাবে চিহ্নিত করা হয়। প্রতিটি পরীক্ষায় আইডি হিসাবে বর্ধমান পূর্ণসংখ্যার সাথে অনন্য কী ব্যবহার করা হয়। equalsপদ্ধতি শুধুমাত্র আইডি ব্যবহার তাই কোনও কী ম্যাপিং অন্য এক মুছে ফেলা হয়।
  • কীগুলি একটি হ্যাশ কোড পায় যা কিছু প্রিসেট সংখ্যার বিপরীতে তাদের আইডি মডিউল বাকি থাকে। আমরা সেই নম্বরটিকে হ্যাশ সীমাতে কল করব । এটি আমাকে প্রত্যাশিত হ্যাশ সংঘর্ষের সংখ্যা নিয়ন্ত্রণ করতে দেয়। উদাহরণস্বরূপ, যদি আমাদের সংগ্রহের আকার 100 হয়, তবে আমাদের 0 থেকে 99 টি আইডি সহ কীগুলি থাকবে the হ্যাশের সীমা যদি 50 হয় তবে কী 0-তে একই 50 টির মতো হ্যাশ কোড থাকবে, 1 টিতে 51 এর সমান হ্যাশ কোড থাকবে other সীমা
  • সংগ্রহের আকার এবং হ্যাশ সীমাটির প্রতিটি সংমিশ্রণের জন্য, আমি হ্যাশ মানচিত্রগুলি বিভিন্ন সেটিংসের সাথে ইনিশিয়াল করে ব্যবহার করে পরীক্ষা চালিয়েছি। এই সেটিংসগুলি লোড ফ্যাক্টর এবং একটি প্রাথমিক ক্ষমতা যা সংগ্রহের সেটিংয়ের ফ্যাক্টর হিসাবে প্রকাশ করা হয়। উদাহরণস্বরূপ, 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.");

        }

    }

}

এটি চালাতে কিছু সময় লাগতে পারে। ফলাফলগুলি স্ট্যান্ডার্ড আউটতে মুদ্রিত হয়। আপনি খেয়াল করতে পারেন আমি একটি লাইন মন্তব্য করেছি। এই লাইনটি একটি ভিজ্যুয়ালাইজারকে কল করে যা ফলাফলগুলির ভিজ্যুয়াল উপস্থাপনাগুলিকে পিএনজি ফাইলগুলিতে আউটপুট করে। এর জন্য ক্লাসটি নীচে দেওয়া হল। আপনি যদি এটি চালাতে চান তবে উপরের কোডে যথাযথ লাইনটি আপত্তি করুন সতর্কতা অবলম্বন করুন: ভিজ্যুয়ালাইজার শ্রেণি ধরে নেয় যে আপনি উইন্ডোজটিতে চলছে এবং সি: \ টেম্পে ফোল্ডার এবং ফাইলগুলি তৈরি করবে। অন্য প্ল্যাটফর্মে চলার সময় এটিকে সামঞ্জস্য করুন।

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 অক্ষের উপর লোড ফ্যাক্টরটি দেখায়।

আরও অগ্রগতি ছাড়াই, আসুন ফলাফলগুলি একবার দেখুন। আমি puts জন্য ফলাফল দিয়ে শুরু করব।

ফলাফল রাখুন


সংগ্রহের আকার: 100. হ্যাশ সীমা: 50. এর অর্থ প্রতিটি হ্যাশ কোডটি দু'বার হওয়া উচিত এবং প্রতিটি অন্যান্য কী হ্যাশ মানচিত্রে সংঘর্ষিত হয়।

আকার_100_hlimit_50_puts

ঠিক আছে, এটি খুব ভাল শুরু হয় না। আমরা দেখতে পাই যে সংগ্রহের আকারের 25% উপরে 1% এর লোড ফ্যাক্টর সহ প্রাথমিক ক্ষমতার জন্য একটি বড় হটস্পট রয়েছে The নীচের বাম কোণটি খুব ভাল সম্পাদন করে না।


সংগ্রহের আকার: 100. হ্যাশ সীমা: 90. দশটি কীতে একটিতে সদৃশ হ্যাশ কোড রয়েছে।

আকার_00_হ্লিমিট_৯০_পুট

এটি একটি সামান্য আরও বাস্তব চিত্র, সঠিক হ্যাশ ফাংশন না থাকলেও 10% ওভারলোড but হটস্পট চলে গেছে, তবে কম লোড ফ্যাক্টরের সাথে স্বল্প প্রাথমিক ক্ষমতার সংমিশ্রণটি সম্ভবত কাজ করে না।


সংগ্রহের আকার: 100. হ্যাশ সীমা: 100. প্রতিটি কী এর নিজস্ব অনন্য হ্যাশ কোড হিসাবে। পর্যাপ্ত বালতি থাকলে কোনও সংঘর্ষের প্রত্যাশা নেই।

আকার_100_hlimit_100_puts

1 এর লোড ফ্যাক্টর সহ 100 এর প্রাথমিক ক্ষমতাটি সূক্ষ্ম বলে মনে হচ্ছে। আশ্চর্যজনকভাবে, লোড ফ্যাক্টর সহ উচ্চতর প্রাথমিক ক্ষমতা অগত্যা ভাল নয়।


সংগ্রহের আকার: 1000. হ্যাশ সীমা: 500. এটি 1000 টি এন্ট্রি সহ এখানে আরও গুরুতর হয়ে উঠছে। ঠিক প্রথম পরীক্ষার মতোই এখানে 2 থেকে 1 এর একটি হ্যাশ ওভারলোড রয়েছে।

আকার_1000_hlimit_500_puts

নীচের বাম কোণটি এখনও ভাল করছে না। তবে নিম্ন প্রাথমিক গণনা / উচ্চ লোড ফ্যাক্টর এবং উচ্চতর প্রাথমিক গণনা / লো লোড ফ্যাক্টরের কম্বোর মধ্যে একটি প্রতিসাম্য বলে মনে হচ্ছে।


সংগ্রহের আকার: 1000. হ্যাশ সীমা: 900. এর অর্থ দশটি হ্যাশ কোডের মধ্যে একটি হ'ল দু'বার হবে। সংঘর্ষের বিষয়ে যুক্তিসঙ্গত দৃশ্য।

আকার_1000_hlimit_900_puts

প্রাথমিক ক্ষমতার সম্ভাব্য কম্বোতে খুব মজার কিছু চলছে যা 1 এর উপরে লোড ফ্যাক্টরের সাথে খুব কম, যা পাল্টা স্বজ্ঞাত int অন্যথায়, এখনও যথেষ্ট প্রতিসম।


সংগ্রহের আকার: 1000. হ্যাশ সীমা: 990. কিছু সংঘর্ষ, তবে কয়েকটি। এই ক্ষেত্রে যথেষ্ট বাস্তববাদী।

আকার_000_হ্লিমিট_990_পুট

আমরা এখানে একটি দুর্দান্ত প্রতিসাম্য পেয়েছি। নীচের বাম কোণটি এখনও সাব-অনুকূল, তবে কম্বোস 1000 ডিআইডি ক্ষমতা / 1.0 লোড ফ্যাক্টর বনাম 1250 আরডি ক্ষমতা / 0.75 লোড ফ্যাক্টর একই স্তরে রয়েছে।


সংগ্রহের আকার: 1000. হ্যাশ সীমা: 1000. কোনও সদৃশ হ্যাশ কোড নেই, তবে এখন 1000 এর নমুনা আকারের।

আকার_1000_hlimit_1000_puts

এখানে খুব বেশি কিছু বলা যায় না। 0.75 লোড ফ্যাক্টরের সাথে উচ্চতর প্রাথমিক ক্ষমতার সংমিশ্রণটি 1 এর লোড ফ্যাক্টরের সাথে 1000 প্রাথমিক ক্ষমতার সংমিশ্রণটিকে সামান্য ছাড়িয়ে গেছে বলে মনে হচ্ছে।


সংগ্রহের আকার: 100_000। হ্যাশ সীমা: 10_000। ঠিক আছে, এখন প্রতি সিরিয় একশো হাজার এবং হ্যাশ কোডের নকলের নমুনা আকারের সাথে এটি গুরুতর হয়ে উঠছে।

আকার_100000_hlimit_10000_puts

হায়! আমি মনে করি আমরা আমাদের নিম্ন বর্ণালীটি পেয়েছি। 1 এর লোড ফ্যাক্টর সহ সঠিকভাবে সংগ্রহের আকারের একটি ইআরসি ক্ষমতা এখানে খুব ভাল করছে তবে এটি সমস্ত দোকান জুড়েই রয়েছে।


সংগ্রহের আকার: 100_000। হ্যাশ সীমা: 90_000। পূর্ববর্তী পরীক্ষার চেয়ে কিছুটা বাস্তবসম্মত, এখানে আমরা হ্যাশ কোডগুলিতে একটি 10% ওভারলোড পেয়েছি।

আকার_100000_hlimit_90000_puts

নীচের বাম কোণটি এখনও অনাকাঙ্ক্ষিত। উচ্চতর প্রাথমিক ক্ষমতা সর্বোত্তম কাজ করে।


সংগ্রহের আকার: 100_000। হ্যাশ সীমা: 99_000। ভাল দৃশ্য, এটি। 1% হ্যাশ কোড ওভারলোড সহ একটি বৃহত সংগ্রহ।

আকার_100000_hlimit_99000_puts

1 টির লোড ফ্যাক্টর সহ ডিআইডি ক্ষমতা হিসাবে সঠিক সংগ্রহের আকারটি এখানে জিততে পারে! যদিও সামান্য বৃহত্তর init সক্ষমতা বেশ ভালভাবে কাজ করে work


সংগ্রহের আকার: 100_000। হ্যাশ সীমা: 100_000। বড় এক. একটি নিখুঁত হ্যাশ ফাংশন সহ বৃহত্তম সংগ্রহ।

আকার_100000_hlimit_100000_puts

কিছু বিস্ময়কর জিনিস এখানে। 1 জয়ের লোড ফ্যাক্টরে 50% অতিরিক্ত কক্ষ সহ প্রাথমিক ক্ষমতা।


ঠিক আছে, এটা পুটদের জন্য। এখন, আমরা কীগুলি পরীক্ষা করব। মনে রাখবেন, নীচের মানচিত্রগুলি সর্বোত্তম / খারাপ সময়গুলির সাথে সম্পর্কিত, পুট সময়গুলিকে আর বিবেচনায় নেওয়া হয় না।

ফলাফল পান


সংগ্রহের আকার: 100. হ্যাশ সীমা: 50. এর অর্থ প্রতিটি হ্যাশ কোডটি দু'বার হওয়া উচিত এবং প্রতিটি অন্যান্য কী হ্যাশ মানচিত্রে সংঘর্ষে প্রত্যাশিত।

আকার_100_hlimit_50_gets

এহ ... কি?


সংগ্রহের আকার: 100. হ্যাশ সীমা: 90. দশটি কীতে একটিতে সদৃশ হ্যাশ কোড রয়েছে।

আকার_100_hlimit_90_gets

ওহে নেলি! এটিই সম্ভবত প্রশ্নকের প্রশ্নের সাথে সম্পর্কিত হতে পারে এবং দৃশ্যত 1 এর লোড ফ্যাক্টরের সাথে 100 এর প্রাথমিক ক্ষমতা এখানে সবচেয়ে খারাপ জিনিসগুলির মধ্যে একটি! আমি শপথ করছি আমি এই জাল না।


সংগ্রহের আকার: 100. হ্যাশ সীমা: 100. প্রতিটি কী এর নিজস্ব অনন্য হ্যাশ কোড হিসাবে। কোনও সংঘর্ষ আশা করা যায় না।

আকার_100_hlimit_100_gets

এটিকে কিছুটা বেশি শান্ত লাগছে। বোর্ড জুড়ে প্রায় একই ফলাফল।


সংগ্রহের আকার: 1000. হ্যাশ সীমা: 500. ঠিক প্রথম পরীক্ষার মতোই 2 থেকে 1 এর হ্যাশ ওভারলোড রয়েছে তবে এখন আরও অনেক এন্ট্রি রয়েছে।

আকার_1000_hlimit_500_gets

দেখে মনে হচ্ছে যে কোনও সেটিংস এখানে একটি ভাল ফলাফল দেবে।


সংগ্রহের আকার: 1000. হ্যাশ সীমা: 900. এর অর্থ দশটি হ্যাশ কোডের মধ্যে একটি হ'ল দু'বার হবে। সংঘর্ষের বিষয়ে যুক্তিসঙ্গত দৃশ্য।

আকার_1000_hlimit_900_gets

এবং ঠিক এই সেটআপটির জন্য রাখার মতো, আমরা একটি অদ্ভুত স্পটে একটি অসাধারণতা পেয়েছি।


সংগ্রহের আকার: 1000. হ্যাশ সীমা: 990. কিছু সংঘর্ষ, তবে কয়েকটি। এই ক্ষেত্রে যথেষ্ট বাস্তববাদী।

আকার_1000_hlimit_990_gets

সর্বত্র শালীন কর্মক্ষমতা, কম লোড ফ্যাক্টরের সাথে উচ্চ প্রাথমিক ক্ষমতার সংমিশ্রনের জন্য সংরক্ষণ করুন। আমি পুটের জন্য এটি আশা করবো, যেহেতু দুটি হ্যাশ মানচিত্রের আকারের প্রত্যাশা করা যেতে পারে। তবে কেন পেল?


সংগ্রহের আকার: 1000. হ্যাশ সীমা: 1000. কোনও সদৃশ হ্যাশ কোড নেই, তবে এখন 1000 এর নমুনা আকারের।

আকার_1000_hlimit_1000_gets

একটি সম্পূর্ণ অনিচ্ছাকৃত দৃশ্য। এটি যাই হোক না কেন কাজ করে বলে মনে হচ্ছে।


সংগ্রহের আকার: 100_000। হ্যাশ সীমা: 10_000। পুরো 100 টি হ্যাশ কোডের ওভারল্যাপের সাথে আবার 100 কে।

আকার_100000_hlimit_10000_gets

এটি দেখতে সুন্দর দেখাচ্ছে না, যদিও খারাপ দাগগুলি খুব স্থানীয় রয়েছে। এখানে পারফরম্যান্স বেশিরভাগ ক্ষেত্রে সেটিংসের মধ্যে একটি নির্দিষ্ট সিনেরির উপর নির্ভর করে।


সংগ্রহের আকার: 100_000। হ্যাশ সীমা: 90_000। পূর্ববর্তী পরীক্ষার চেয়ে কিছুটা বাস্তবসম্মত, এখানে আমরা হ্যাশ কোডগুলিতে একটি 10% ওভারলোড পেয়েছি।

আকার_100000_hlimit_90000_gets

অনেকগুলি বৈকল্পিকতা, যদিও আপনি স্ক্রিন্ট করলে আপনি উপরের ডানদিকে কোণায় নির্দেশিত একটি তীর দেখতে পাবেন।


সংগ্রহের আকার: 100_000। হ্যাশ সীমা: 99_000। ভাল দৃশ্য, এটি। 1% হ্যাশ কোড ওভারলোড সহ একটি বৃহত সংগ্রহ।

আকার_100000_hlimit_99000_gets

খুব বিশৃঙ্খল। এখানে অনেক কাঠামো খুঁজে পাওয়া শক্ত।


সংগ্রহের আকার: 100_000। হ্যাশ সীমা: 100_000। বড় এক. একটি নিখুঁত হ্যাশ ফাংশন সহ বৃহত্তম সংগ্রহ।

আকার_100000_hlimit_100000_gets

অন্য কেউ ভাবেন যে এটি আটারি গ্রাফিক্সের মতো দেখতে শুরু হচ্ছে? এটি সংগ্রহের সঠিক আকারের -25% বা + 50% এর প্রাথমিক ক্ষমতার পক্ষে বলে মনে হচ্ছে।


ঠিক আছে, এখনই সিদ্ধান্তের সময় ...

  • পুট টাইম সম্পর্কিত: আপনি প্রাথমিক সক্ষমতা এড়াতে চান যা মানচিত্রের প্রবেশের প্রত্যাশিত সংখ্যার চেয়ে কম। যদি আগে থেকে কোনও সঠিক সংখ্যা জানা থাকে তবে number সংখ্যা বা কিছুটা উপরে এটি সেরা কাজ করে বলে মনে হচ্ছে। পূর্ববর্তী হ্যাশ মানচিত্রের আকারের কারণে উচ্চ লোড ফ্যাক্টরগুলি কম প্রাথমিক সামর্থ্যগুলি অফসেট করতে পারে। উচ্চতর প্রাথমিক ক্ষমতার জন্য, তাদের পক্ষে এতো কিছু মনে হয় না।
  • প্রাপ্ত সময় সম্পর্কে: ফলাফলগুলি এখানে কিছুটা বিশৃঙ্খলাযুক্ত। উপসংহার করার মতো অনেক কিছুই নেই। মনে হচ্ছে কিছু হ্যাশ কোড ওভারল্যাপ, প্রাথমিক ক্ষমতা এবং লোড ফ্যাক্টরের মধ্যে সূক্ষ্ম অনুপাতের উপর খুব বেশি নির্ভর করে বলে মনে হচ্ছে কিছু খারাপ সেটআপ ভাল অভিনয় করছে এবং ভাল সেটআপগুলি দুর্দান্তভাবে পারফর্ম করছে।
  • জাভা পারফরম্যান্স সম্পর্কে অনুমানের বিষয়টি যখন আসে তখন আমি স্পষ্টতই বাজে থাকি। সত্যটি হ'ল, যদি না আপনি নিজের সেটিংস প্রয়োগের ক্ষেত্রে পুরোপুরি টিউন করেনHashMap ফলাফলগুলি পুরো জায়গাতেই হবে। যদি এ থেকে দূরে নেওয়ার মতো কোনও জিনিস থাকে তবে তা হল যে 16 এর ডিফল্ট প্রাথমিক আকারটি ছোট ম্যাপ ছাড়া অন্য কোনও কিছুর জন্য কিছুটা বোবা, সুতরাং এমন কোন কনস্ট্রাক্টর ব্যবহার করুন যা আপনার আকারের ক্রম সম্পর্কে কোনও ধরণের ধারণা থাকলে প্রাথমিক আকার নির্ধারণ করে এটা হতে যাচ্ছে.
  • আমরা এখানে ন্যানোসেকেন্ডে পরিমাপ করছি। 10 পুট প্রতি সেরা গড় সময় ছিল 1179 এনএস এবং আমার মেশিনে সবচেয়ে খারাপ 5105 এনএস। প্রতি 10 প্রতি গড় গড় গড় গড় ছিল 547 এনএস এবং সবচেয়ে খারাপ 3484 এনএস। এটি একটি ফ্যাক্টর 6 পার্থক্য হতে পারে, তবে আমরা একটি মিলিসেকেন্ডের চেয়ে কম কথা বলছি। মূল পোস্টারটি যা মনে রেখেছিল তার চেয়ে অনেক বড় আকারের সংগ্রহগুলিতে।

ঠিক আছে, এটা। আমি আশা করি যে আমার কোডটিতে এমন কিছু ভয়ঙ্কর নজরদারি নেই যা আমি এখানে পোস্ট করা সমস্ত কিছুকে অকার্যকর করে। এটি মজাদার হয়েছে, এবং আমি শিখেছি যে শেষ পর্যন্ত আপনি ক্ষুদ্রতর অপ্টিমাইজেশান থেকে অনেক পার্থক্য প্রত্যাশার চেয়ে তার কাজটি করার জন্য জাভার উপর নির্ভর করতে পারেন। এর অর্থ এই নয় যে কিছু জিনিস এড়ানো উচিত নয়, তবে আমরা প্রায়শই লুপগুলির জন্য দীর্ঘতর স্ট্রিংগুলি নির্মাণের কথা বলছি, ভুল ডেটাস্ট্রাকচার ব্যবহার করে ও (এন ^ 3) অ্যালগরিদম তৈরির বিষয়ে।


4
প্রচেষ্টার জন্য ধন্যবাদ, দুর্দান্ত দেখাচ্ছে! অলস হওয়ার দরকার নেই, আমি আমার ফলাফলগুলিতে কিছু চমত্কার গ্রাফও যুক্ত করেছি। আমার পরীক্ষাগুলি আপনার চেয়ে কিছুটা নিষ্ঠুর শক্তি, তবে আমি দেখতে পেয়েছি যে বড় মানচিত্র ব্যবহার করার সময় পার্থক্যগুলি আরও লক্ষণীয়। ছোট মানচিত্র সহ, আপনি যা কিছু করুন না কেন, মিস করতে পারবেন না। পারফরম্যান্স বিশৃঙ্খল হতে থাকে, জেভিএম অপ্টিমাইজেশান এবং জিসির কারণে, এবং আমার কাছে একটি তত্ত্ব আছে যে আপনার কয়েকটি ছোট ডেটাসেটের জন্য কোনও শক্তিশালী সিদ্ধান্ত সেই বিশৃঙ্খলা দ্বারা খেয়েছে।
ডোমচি

পারফরম্যান্স পেতে আরও একটি মন্তব্য। এটি বিশৃঙ্খলাযুক্ত বলে মনে হচ্ছে তবে আমি দেখতে পেয়েছি যে এটি খুব সংকীর্ণ পরিসরে অনেকটা পরিবর্তিত হয় তবে সামগ্রিকভাবে এটি নরকের মতো ধ্রুবক এবং বিরক্তিকর। 100/90-তে আপনি করেছেন এমন আমি মাঝে মাঝে অদ্ভুত স্পাইক পেয়েছি। আমি এটি ব্যাখ্যা করতে পারি না, তবে অনুশীলনে এটি সম্ভবত অদম্য।
ডোমচি

জিএএইচ, দয়া করে আমার উত্তরটি একবার দেখুন, আমি জানি এটি একটি খুব পুরানো থ্রেড তবে সম্ভবত আপনার পরীক্ষাগুলি এটিকে মনে রেখে পুনরায় করা উচিত।
durron597

আরে আপনার এটি কনফারেন্স পেপার হিসাবে এসিএম এ পোস্ট করা উচিত :) কি চেষ্টা!
yerlilbilgin

12

এটি খুব দুর্দান্ত একটি থ্রেড, যা বাদ দেওয়া একটি গুরুত্বপূর্ণ বিষয় বাদে is তুমি বলেছিলে:

কৌতূহলীভাবে, ক্ষমতা, ক্ষমতা +1, ক্ষমতা + 2, ক্ষমতা -1 এবং এমনকি ক্ষমতা -10 সমস্ত ঠিক একই ফলাফল দেয়। আমি কমপক্ষে ক্ষমতা -1 এবং ক্ষমতা -10 খারাপ ফলাফল দেওয়ার আশা করব expect

উত্স কোড প্রাথমিক ক্ষমতার অভ্যন্তরীণভাবে পরের দু'জনের পাওয়ার সাফ করে। তার অর্থ হল, উদাহরণস্বরূপ, 513, 600, 700, 800, 900, 1000, এবং 1024 এর প্রাথমিক সক্ষমতা সমস্ত একই প্রাথমিক ক্ষমতা (1024) ব্যবহার করবে। এটি @ জিএইচএইচ দিয়ে নেওয়া পরীক্ষাটি অকার্যকর করে না তবে একজনকে বুঝতে হবে যে তার ফলাফলগুলি বিশ্লেষণ করার আগে এটি করা হচ্ছে। এবং এটি কয়েকটি পরীক্ষার বিজোড় আচরণ ব্যাখ্যা করে।

এটি জেডিকে উত্সের জন্য নির্মাতা অধিকার:

/**
 * 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();
}

এটা শক্তিশালী আকর্ষণীয়! আমি এই সম্পর্কে কোন ধারণা ছিল না। আমি পরীক্ষাগুলিতে যা দেখেছি তা সত্যই ব্যাখ্যা করে। এবং আবারও, এটি নিশ্চিত করে যে অকালীন অপ্টিমাইজেশন প্রায়শই দরকারী কারণ আপনি ঠিক জানেন না (বা সত্যই এটি জানা উচিত) আপনার পিছনের পিছনে সংকলক বা কোড কী করছে। এবং তারপরে অবশ্যই এটি প্রতি সংস্করণ / প্রয়োগে পরিবর্তিত হতে পারে। এটি পরিষ্কার করার জন্য ধন্যবাদ!
জিএইচ

@ জিএইচএইচ, আমি এই তথ্যের ভিত্তিতে আরও উপযুক্ত নম্বরগুলি বেছে নিয়ে আপনার পরীক্ষাগুলি আবার চালানো দেখতে চাই love উদাহরণস্বরূপ, আমার যদি 1200 উপাদান থাকে তবে আমি কি একটি 1024 মানচিত্র, একটি 2048 মানচিত্র, বা 4096 মানচিত্র ব্যবহার করব? আমি মূল প্রশ্নের উত্তর জানি না, এজন্যই আমি এই থ্রেডটি শুরু করতে পেলাম। যদিও, আমি জানি যে পেয়ারা যখন আপনি করেন তখন আপনার expectedSizeদ্বারা বহুগুণ বৃদ্ধি করে1.33Maps.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উপাদান দিয়ে লোড করছেন , আমাকে করতে হবে জাভাডোকের মাধ্যমে পড়ুন এটি নিশ্চিত হয়ে নিন যে এটি সঠিকভাবে পৌঁছালে এটির আকার পরিবর্তন হবে না 100। অবশ্যই, আমি সেখানে উত্তরটি খুঁজে পাব না, তাই আমাকে উত্সটি দেখতে হবে। এটি মূল্যবান নয় ... কেবল এটি ছেড়ে দিন 101এবং সবাই খুশি এবং কেউ এর উত্স-কোড সন্ধান করছে না java.util.HashMap। হুরাহ।

তৃতীয়, দাবী যে " আপনার অনুসন্ধান এবং সন্নিবেশনের কার্য সম্পাদন " কেHashMap লোড ফ্যাক্টরের সাথে আপনি যা প্রত্যাশা করছেন তার সঠিক ক্ষমতা নির্ধারণ1 যদিও তা গাঢ় তৈরি হচ্ছে মাত্র সত্য নয়।

... আপনার যদি nবালতি থাকে এবং আপনি এলোমেলোভাবে বালতিগুলিতে nআইটেমগুলি অর্পণ করেন n, হ্যাঁ, আপনি একই বালতিতে আইটেমগুলি দিয়ে শেষ করতে যাচ্ছেন, অবশ্যই ... তবে এটি বিশ্বের শেষ নয় ... অনুশীলনে, এটি তুলনা তুলনায় আরও কয়েক। আসলে, এসএসপি আছে। যখন আপনি বিবেচনা করবেন যে বিকল্পটি বালতিগুলিতে 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. 

Re: ↑ - এই সম্পর্কে → || ← বিভিন্ন সেটিংস মধ্যে অনেক পার্থক্য


আমার মূল উত্তর (প্রথম অনুভূমিক রেখা উপরে বিট) থেকে সম্মান সঙ্গে, এটা ইচ্ছাকৃতভাবে সাবলীল করা হয়েছে কারণ মধ্যে অধিকাংশ ক্ষেত্রে , মাইক্রো-সর্বোচ্চকরন এই ধরনের ভালো নয়


@ এজেপি, আমার অনুমানের কাজটি ভুল নয় । উপরের সম্পাদনাগুলি দেখুন। তোমার আন্দাজ যার আন্দাজ সঠিক এবং যার আন্দাজ ভুল সম্পর্কে ভুল।
বদরোয়েট

(... সম্ভবত আমি কিছুটা ছদ্মবেশী হয়ে উঠছি ... আমি কিছুটা বিরক্ত হলেও: পি)
ছদ্মবেশী হয়ে উঠছি

4
আপনি ইজেপিতে যথাযথভাবে বিরক্ত হতে পারেন, তবে এখন আমার পালা; পি - যদিও আমি একমত যে অকালীন অপটিমাইজেশন অনেকটা অকাল বীর্যপাতের মতো, তবে দয়া করে ধরে নিবেন না যে এমন কিছু যা সাধারণত চেষ্টা করার মতো না হয় তা আমার ক্ষেত্রে চেষ্টা করার মতো নয় is । আমার ক্ষেত্রে এটি যথেষ্ট গুরুত্বপূর্ণ যেটি আমি অনুমান করতে চাই না, তাই আমি এটি সন্ধান করেছি - আমার ক্ষেত্রে +1 প্রয়োজন হয় না (তবে এটি হতে পারে যেখানে আপনার প্রাথমিক / প্রকৃত ক্ষমতা এক নয় এবং লোড ফ্যাক্টরটি 1 নয়, এই কাস্টটিকে হ্যাশম্যাপে প্রান্তে দেখুন: থ্রেশহোল্ড = (ইনট) (ক্ষমতা * লোডফ্যাক্টর)।
ডোমচি

@ বিড্রয়েট আপনি স্পষ্ট করেই বলেছিলেন যে আমি এটির প্রয়োজনীয় যে আসলেই নিশ্চিত নই '। সুতরাং এটি অনুমান কাজ ছিল। এখন আপনি গবেষণাটি করেছেন এবং পোস্ট করেছেন, এটি আর অনুমানের কাজ নয়, এবং আপনি যেমন স্পষ্টভাবে আগে এটি করেননি এটি অনুমান করা ছিল , অন্যথায় আপনি নিশ্চিত হয়ে যেতেন would 'ভুল' হিসাবে, জাভাদোক সুস্পষ্টভাবে 0.75 লোড ফ্যাক্টরকে আদেশ দেয়, যেমন বেশ কয়েক দশক গবেষণা, এবং জিএইচ-এর উত্তর। অবশেষে 'এটি সম্ভবত চেষ্টাটির পক্ষে উপযুক্ত হতে পারে না' হিসাবে ডোমচির মন্তব্যটি এখানে দেখুন। যা সঠিক ছিল তেমনটি ছেড়ে দেয় না, যদিও সাধারণভাবে আমি মাইক্রো-অপ্টিমাইজেশন সম্পর্কে আপনার সাথে একমত হই।
ব্যবহারকারী 207421

আরাম করুন, সবাই। হ্যাঁ, আমার উত্তর অতিরঞ্জিত জিনিস। আপনার যদি 100 টি অবজেক্ট থাকে যার কিছুটা ভারী equalsফাংশন না থাকে তবে আপনি সম্ভবত সেগুলিকে একটি তালিকায় রেখে কেবল `অন্তর্ভুক্ত´ ব্যবহার করে পালিয়ে যাবেন ´ এই জাতীয় একটি ছোট সেট সঙ্গে, কর্মক্ষমতা মধ্যে বড় পার্থক্য কখনও হবে না। গতি বা মেমরির উদ্বেগগুলি অন্য সকলের উপরে চলে গেলে বা সমান এবং হ্যাশ খুব নির্দিষ্ট থাকে তবে এটি সত্যই গুরুত্বপূর্ণ। আমি পরে বড় সংগ্রহ এবং বিভিন্ন লোড ফ্যাক্টর এবং প্রাথমিক সক্ষমতা নিয়ে একটি পরীক্ষা করবো তা দেখার জন্য আমি ক্রেপ পূর্ণ আছি কি না।
জিএইচএইচ


1

HashMapজাভাডক থেকে :

একটি সাধারণ নিয়ম হিসাবে, ডিফল্ট লোড ফ্যাক্টর (.75) সময় এবং স্থান ব্যয়ের মধ্যে একটি ভাল ট্রেড অফ দেয়। উচ্চতর মানগুলি স্পেসের ওভারহেড হ্রাস করে তবে অনুসন্ধানের ব্যয় বাড়ায় (হ্যাশম্যাপ শ্রেণির বেশিরভাগ ক্রিয়াকলাপে প্রতিফলিত হয়, গেট অ্যান্ড পুট সহ)। প্রাথমিক ক্ষমতা নির্ধারণের সময় মানচিত্রে এবং এর লোড ফ্যাক্টরের প্রত্যাশিত সংখ্যাগুলি বিবেচনায় নেওয়া উচিত, যাতে পুনঃস্থাপনের ক্রিয়াকলাপের সংখ্যা হ্রাস করা যায়। যদি প্রাথমিক ক্ষমতা লোড ফ্যাক্টর দ্বারা বিভক্ত সর্বাধিক সংখ্যক এন্ট্রিগুলির চেয়ে বেশি হয়, তবে কোনও পুনঃস্থাপন ক্রিয়াকলাপ ঘটবে না।

সুতরাং আপনি যদি 100 টি এন্ট্রি প্রত্যাশা করে থাকেন তবে সম্ভবত 0.75 এর লোড ফ্যাক্টর এবং সিলিংয়ের প্রাথমিক ক্ষমতা (100 / 0.75) সবচেয়ে ভাল হবে। এটি 134 এ নেমে আসে।

আমাকে স্বীকার করতে হবে, উচ্চতর লোড ফ্যাক্টরের জন্য অনুসন্ধানের ব্যয় কেন বেশি হবে তা আমি নিশ্চিত নই। হ্যাশম্যাপটি আরও বেশি "জনাকীর্ণ" হওয়ার অর্থ এই নয় যে আরও বালু বস্তু একই বালতিতে রাখা হবে, তাই না? এটি কেবল তাদের হ্যাশ কোডের উপর নির্ভর করে, যদি আমার ভুল না হয়। সুতরাং একটি শালীন হ্যাশ কোড স্প্রেড ধরে নিলে, লোড ফ্যাক্টর নির্বিশেষে বেশিরভাগ ক্ষেত্রে ও (1) হওয়া উচিত নয়?

সম্পাদনা: পোস্ট করার আগে আমার আরও পড়তে হবে ... অবশ্যই হ্যাশ কোডটি কোনও কোনও অভ্যন্তরীণ সূচীতে সরাসরি ম্যাপ করতে পারে না। এটি অবশ্যই একটি মানতে হ্রাস করতে হবে যা বর্তমান ক্ষমতা অনুসারে ফিট করে। এর অর্থ আপনার প্রাথমিক ক্ষমতা যত বেশি হবে আপনি হ্যাশের সংঘর্ষের সংখ্যাটি যতটা কম আশা করতে পারেন। আপনার 1 টির লোড ফ্যাক্টর সহ সেট করা আপনার অবজেক্টের আকার (বা +1) হ'ল একটি প্রাথমিক ক্ষমতা বাছাই করা সত্যই নিশ্চিত করবে যে আপনার মানচিত্রটি কখনও পুনরায় আকার দেবে না। যাহোক, এটি আপনার অনুসন্ধান এবং সন্নিবেশ সম্পাদনাকে নষ্ট করবে। একটি আকার পরিবর্তন এখনও তুলনামূলকভাবে দ্রুত এবং কেবল একবারই ঘটতে পারে, যখন মানচিত্রের সাথে কোনও প্রাসঙ্গিক কাজের জন্য লুকআপগুলি সম্পন্ন করা হয়। ফলস্বরূপ, দ্রুত অনুসন্ধানের জন্য অনুকূলিতকরণ হ'ল আপনি এখানে যা চান তা সত্য। আপনি জাভাডক যেমনটি বলে পুনরায় আকার পরিবর্তন না করে এটি একত্রিত করতে পারেন: আপনার প্রয়োজনীয় ক্ষমতা গ্রহণ করুন, একটি সর্বোত্তম লোড ফ্যাক্টর (যেমন 0.75) দ্বারা ভাগ করুন এবং সেই লোড ফ্যাক্টরের সাথে এটি প্রাথমিক ক্ষমতা হিসাবে ব্যবহার করুন। রাউন্ডিংটি আপনাকে পাবেন না তা নিশ্চিত করতে 1 যুক্ত করুন।


4
" এটি আপনার অনুসন্ধান এবং সন্নিবেশ সম্পাদনাকে নষ্ট করবে " এটি অতিরিক্ত-অতিরঞ্জিত / সরল-ভুল।
বদরোয়েট

4
আমার পরীক্ষাগুলি দেখায় যে 1 এর লোড ফ্যাক্টরটি সেট করে দেখার জন্য পারফরম্যান্স প্রভাবিত হয় না Inোকানো কর্মক্ষমতা আসলে উন্নত হয়; যেহেতু কোনও আকার নেই, এটি দ্রুত। সুতরাং, আপনার বক্তব্যটি সাধারণ ক্ষেত্রে (সঠিক সংখ্যার উপাদানগুলির সাথে একটি হ্যাশম্যাপের সন্ধানের জন্য 1 এর তুলনায় 0.75 দিয়ে দ্রুত হবে) সঠিক, তবে আমার নির্দিষ্ট ক্ষেত্রে ভুল যখন হাশম্যাপ সর্বদা তার সর্বোচ্চ ক্ষমতায় পূর্ণ থাকে, যা কখনই পরিবর্তন হয় না। প্রারম্ভিক আকার উচ্চতর সেট করার আপনার পরামর্শটি আমার ক্ষেত্রে আকর্ষণীয় তবে অপ্রাসঙ্গিক যেহেতু আমার টেবিলটি বৃদ্ধি পাবে না, সুতরাং লোড ফ্যাক্টরটি কেবল আকার পরিবর্তনের আলোকেই গুরুত্বপূর্ণ।
ডোমচি
আমাদের সাইট ব্যবহার করে, আপনি স্বীকার করেছেন যে আপনি আমাদের কুকি নীতি এবং গোপনীয়তা নীতিটি পড়েছেন এবং বুঝতে পেরেছেন ।
Licensed under cc by-sa 3.0 with attribution required.