{"id":5047,"date":"2016-11-25T20:31:31","date_gmt":"2016-11-25T20:31:31","guid":{"rendered":"https:\/\/www.modernescpp.com\/index.php\/associative-containers-a-simple-performance-comparison\/"},"modified":"2023-06-26T12:35:07","modified_gmt":"2023-06-26T12:35:07","slug":"associative-containers-a-simple-performance-comparison","status":"publish","type":"post","link":"https:\/\/www.modernescpp.com\/index.php\/associative-containers-a-simple-performance-comparison\/","title":{"rendered":"Associative Containers &#8211; A simple Performance Comparison"},"content":{"rendered":"<p>Before I take a deeper look insight the interface of the hash tables &#8211; officially called unordered associative containers &#8211; I will first compare the performance of the associative containers. The best candidates are<span style=\"font-family: courier new,courier;\"> std::unordered_map<\/span> and the ordered pendant <span style=\"font-family: courier new,courier;\">std::map<\/span> because both are used most frequently.<\/p>\n<p><!--more--><\/p>\n<p>&nbsp;<\/p>\n<p>Admittedly, the eight variations of associative containers are a little bit confusing. In addition, the name component unordered the new ones is not easy to read and write. The reason for the special names is easily explained. On the one hand, the unordered associative containers were too late for the C++98 standard. On the other hand, they were missed so severely that most architectures implemented them by themself. Therefore, simple names have already been used, and the C++ standardization committee has to use more elaborated names. But it is very nice that the names of the unordered associative containers follow a simple pattern. I presented the pattern in the post <a href=\"https:\/\/www.modernescpp.com\/index.php\/hash-tables\">Hash tables<\/a>.<\/p>\n<\/p>\n<h2>std::map versus std::unordered_map<\/h2>\n<p>For my simple performance comparison, I put a lot of arbitrary values into a <span style=\"font-family: courier new,courier;\">std::map<\/span> and <span style=\"font-family: courier new,courier;\">std::unorderd_map <\/span>and measured and compa<span>red their performance<\/span><span style=\"font-family: courier new,courier;\"><span>.<\/span><\/span><span> Precisely this is done in my program.<\/span><\/p>\n<p>&nbsp;<\/p>\n<p><!-- HTML generated using hilite.me --><\/p>\n<div style=\"background: #ffffff; overflow: auto; width: auto; gray;border-width: .1em .1em .1em .8em;\">\n<table>\n<tbody>\n<tr>\n<td>\n<pre style=\"margin: 0; line-height: 125%;\"> 1\r\n 2\r\n 3\r\n 4\r\n 5\r\n 6\r\n 7\r\n 8\r\n 9\r\n10\r\n11\r\n12\r\n13\r\n14\r\n15\r\n16\r\n17\r\n18\r\n19\r\n20\r\n21\r\n22\r\n23\r\n24\r\n25\r\n26\r\n27\r\n28\r\n29\r\n30\r\n31\r\n32\r\n33\r\n34\r\n35\r\n36\r\n37\r\n38\r\n39\r\n40\r\n41\r\n42\r\n43\r\n44\r\n45\r\n46\r\n47\r\n48\r\n49<\/pre>\n<\/td>\n<td>\n<pre style=\"margin: 0; line-height: 125%;\"><span style=\"color: #008000;\">\/\/ associativeCompare.cpp<\/span>\r\n\r\n<span style=\"color: #0000ff;\">#include &lt;chrono&gt;<\/span>\r\n<span style=\"color: #0000ff;\">#include &lt;iostream&gt;<\/span>\r\n<span style=\"color: #0000ff;\">#include &lt;map&gt;<\/span>\r\n<span style=\"color: #0000ff;\">#include &lt;random&gt;<\/span>\r\n<span style=\"color: #0000ff;\">#include &lt;unordered_map&gt;<\/span>\r\n\r\n<span style=\"color: #0000ff;\">static<\/span> <span style=\"color: #0000ff;\">const<\/span> <span style=\"color: #2b91af;\">long<\/span> <span style=\"color: #2b91af;\">long<\/span> mapSize= 10000000;\r\n<span style=\"color: #0000ff;\">static<\/span> <span style=\"color: #0000ff;\">const<\/span> <span style=\"color: #2b91af;\">long<\/span> <span style=\"color: #2b91af;\">long<\/span> accSize= 1000000;\r\n\r\n<span style=\"color: #2b91af;\">int<\/span> main(){\r\n\r\n  std::cout &lt;&lt; std::endl;\r\n\r\n  std::map&lt;<span style=\"color: #2b91af;\">int<\/span>,<span style=\"color: #2b91af;\">int<\/span>&gt; myMap;\r\n  std::unordered_map&lt;<span style=\"color: #2b91af;\">int<\/span>,<span style=\"color: #2b91af;\">int<\/span>&gt; myHash;\r\n\r\n  <span style=\"color: #0000ff;\">for<\/span> ( <span style=\"color: #2b91af;\">long<\/span> <span style=\"color: #2b91af;\">long<\/span> i=0; i &lt; mapSize; ++i ){\r\n    myMap[i]=i;\r\n    myHash[i]= i;\r\n  }\r\n\r\n  std::vector&lt;<span style=\"color: #2b91af;\">int<\/span>&gt; randValues;\r\n  randValues.reserve(accSize);\r\n\r\n  <span style=\"color: #008000;\">\/\/ random values<\/span>\r\n  std::random_device seed;\r\n  std::mt19937 engine(seed());\r\n  std::uniform_int_distribution&lt;&gt; uniformDist(0,mapSize);\r\n  <span style=\"color: #0000ff;\">for<\/span> ( <span style=\"color: #2b91af;\">long<\/span> <span style=\"color: #2b91af;\">long<\/span> i=0 ; i&lt; accSize ; ++i) randValues.push_back(uniformDist(engine));\r\n\r\n  <span style=\"color: #0000ff;\">auto<\/span> start = std::chrono::system_clock::now();\r\n  <span style=\"color: #0000ff;\">for<\/span> ( <span style=\"color: #2b91af;\">long<\/span> <span style=\"color: #2b91af;\">long<\/span> i=0; i &lt; accSize; ++i){\r\n    myMap[randValues[i]];\r\n  }\r\n  std::chrono::duration&lt;<span style=\"color: #2b91af;\">double<\/span>&gt; dur= std::chrono::system_clock::now() - start;\r\n  std::cout &lt;&lt; <span style=\"color: #a31515;\">\"time for std::map: \"<\/span> &lt;&lt; dur.count() &lt;&lt; <span style=\"color: #a31515;\">\" seconds\"<\/span> &lt;&lt; std::endl;\r\n\r\n  <span style=\"color: #0000ff;\">auto<\/span> start2 = std::chrono::system_clock::now();\r\n  <span style=\"color: #0000ff;\">for<\/span> ( <span style=\"color: #2b91af;\">long<\/span> <span style=\"color: #2b91af;\">long<\/span> i=0; i &lt; accSize; ++i){\r\n    myHash[randValues[i]];\r\n  }\r\n  std::chrono::duration&lt;<span style=\"color: #2b91af;\">double<\/span>&gt; dur2= std::chrono::system_clock::now() - start2;\r\n  std::cout &lt;&lt; <span style=\"color: #a31515;\">\"time for std::unordered_map: \"<\/span> &lt;&lt; dur2.count() &lt;&lt; <span style=\"color: #a31515;\">\" seconds\"<\/span> &lt;&lt; std::endl;\r\n\r\n  std::cout &lt;&lt; std::endl;\r\n\r\n}\r\n<\/pre>\n<\/td>\n<\/tr>\n<\/tbody>\n<\/table>\n<\/div>\n<p>&nbsp;<\/p>\n<p>I fill in lines 19 -22 a <span style=\"font-family: courier new,courier;\">std::map<\/span> (line 20) and a<span style=\"font-family: courier new,courier;\"> std::unordered_map<\/span> (line 21) with <span style=\"font-family: courier new,courier;\">mapSize<\/span> key\/values pairs. <span style=\"font-family: courier new,courier;\">mapSize<\/span> is ten million. Then I create a <span style=\"font-family: courier new,courier;\">std::vector<\/span> with one million arbitrary elements between 0 and <span style=\"font-family: courier new,courier;\">mapSize<\/span>. The random number generator <span style=\"font-family: courier new,courier;\">engine(seed())<\/span> (line 29) initialized by the seed creates the values. I use in line 30 the random number generator <span style=\"font-family: courier new,courier;\">engine<\/span> in combination with the random number distribution<span style=\"font-family: courier new,courier;\"> <\/span><span style=\"font-family: courier new,courier;\">unformDist<\/span>: <span style=\"font-family: courier new,courier;\">uniformDist(engine). <\/span>Of course,<span style=\"font-family: courier new,courier;\"> uniformDist <\/span>guarantees the uniform distribution of the values. Ultimately, <span style=\"font-family: courier new,courier;\">randValues<\/span> has one million arbitrarily created elements between 0 and 10 million that are uniformly distributed. These one million elements are the keys for which I&#8217;m interested in the values from <span style=\"font-family: courier new,courier;\">std::map<\/span> and the <span style=\"font-family: courier new,courier;\">std::unordered_map<\/span>.<\/p>\n<p>How do they compare to each other?<\/p>\n<h3>Without optimization<\/h3>\n<p>At first, I compile the program without optimization. I use the current Microsoft Visual 15 C++ compiler and GCC 4.8. Here are the numbers.<\/p>\n<h4>Microsoft Visual C++ Compiler<\/h4>\n<h3><img loading=\"lazy\" decoding=\"async\" class=\" size-full wp-image-5042\" src=\"https:\/\/www.modernescpp.com\/wp-content\/uploads\/2016\/11\/windows.PNG\" alt=\"windows\" width=\"805\" height=\"171\" srcset=\"https:\/\/www.modernescpp.com\/wp-content\/uploads\/2016\/11\/windows.PNG 805w, https:\/\/www.modernescpp.com\/wp-content\/uploads\/2016\/11\/windows-300x64.png 300w, https:\/\/www.modernescpp.com\/wp-content\/uploads\/2016\/11\/windows-768x163.png 768w\" sizes=\"auto, (max-width: 805px) 100vw, 805px\" \/><\/h3>\n<h4>GCC Compiler<\/h4>\n<h3><img loading=\"lazy\" decoding=\"async\" class=\" size-full wp-image-5043\" src=\"https:\/\/www.modernescpp.com\/wp-content\/uploads\/2016\/11\/linux.png\" alt=\"linux\" width=\"549\" height=\"193\" srcset=\"https:\/\/www.modernescpp.com\/wp-content\/uploads\/2016\/11\/linux.png 549w, https:\/\/www.modernescpp.com\/wp-content\/uploads\/2016\/11\/linux-300x105.png 300w\" sizes=\"auto, (max-width: 549px) 100vw, 549px\" \/><\/h3>\n<p>The performance differences are significant. The&nbsp; <span style=\"font-family: courier new,courier;\">std::unordered_map<\/span> is about three times faster on Windows and about five times faster on Linux. I stop here with my reasoning because the executables run on different PC.<\/p>\n<p>Now I&#8217;m curious about the optimized versions.<\/p>\n<h3>Maximum optimization<\/h3>\n<p>To compile the program with maximum optimization, I use the cl.exe compiler, the flag <a href=\"https:\/\/msdn.microsoft.com\/en-us\/library\/59a3b321.aspx\"><span style=\"font-family: courier new,courier;\">Ox<\/span><\/a><span style=\"font-family: courier new,courier;\">;<\/span><span style=\"font-family: courier new,courier;\"> <\/span>in the case of the <span style=\"font-family: courier new,courier;\">g++<\/span> compiler, the flag <span style=\"font-family: courier new,courier;\"><a href=\"https:\/\/gcc.gnu.org\/onlinedocs\/gcc\/Optimize-Options.html\">O3<\/a><\/span>. The performance differences from the non-optimized versions are very impressive.<\/p>\n<h4>Microsoft Visual C++<\/h4>\n<p>By compiling with full optimization, the access time to the<span style=\"font-family: courier new,courier;\"> std::map<\/span> becomes about two times faster, and the access time to the<span style=\"font-family: courier new,courier;\"> std::unorderd_map<\/span> becomes about six times faster. Therefore, the<span style=\"font-family: courier new,courier;\"> std::unorderd_map<\/span> is 11 times faster than the <span style=\"font-family: courier new,courier;\">std::map.<\/span><\/p>\n<p><img loading=\"lazy\" decoding=\"async\" class=\" size-full wp-image-5044\" src=\"https:\/\/www.modernescpp.com\/wp-content\/uploads\/2016\/11\/windowsOptimizePNG.PNG\" alt=\"windowsOptimizePNG\" width=\"793\" height=\"187\" srcset=\"https:\/\/www.modernescpp.com\/wp-content\/uploads\/2016\/11\/windowsOptimizePNG.PNG 793w, https:\/\/www.modernescpp.com\/wp-content\/uploads\/2016\/11\/windowsOptimizePNG-300x71.png 300w, https:\/\/www.modernescpp.com\/wp-content\/uploads\/2016\/11\/windowsOptimizePNG-768x181.png 768w\" sizes=\"auto, (max-width: 793px) 100vw, 793px\" \/><\/p>\n<h4>GCC Compiler<\/h4>\n<p>The performance difference is not so dramatic in the case of the GCC compiler. Therefore, the optimized access with <span style=\"font-family: courier new,courier;\">std::map<\/span> is about 20% faster, but the access time of <span style=\"font-family: courier new,courier;\">std::unordered_map<\/span> is about six times faster.<\/p>\n<p>&nbsp;<\/p>\n<p><img loading=\"lazy\" decoding=\"async\" class=\" size-full wp-image-5045\" src=\"https:\/\/www.modernescpp.com\/wp-content\/uploads\/2016\/11\/linuxOptimzed.png\" alt=\"linuxOptimzed\" width=\"549\" height=\"193\" srcset=\"https:\/\/www.modernescpp.com\/wp-content\/uploads\/2016\/11\/linuxOptimzed.png 549w, https:\/\/www.modernescpp.com\/wp-content\/uploads\/2016\/11\/linuxOptimzed-300x105.png 300w\" sizes=\"auto, (max-width: 549px) 100vw, 549px\" \/><\/p>\n<p>I present them once more in a simple table to whom &#8211; like me &#8211; is lost with so many numbers.<\/p>\n<h3>The raw numbers<\/h3>\n<p>For simplicity reasons, I rounded the value to 3 decimal places. The last two columns show the maximum optimized programs with (<span style=\"font-family: courier new,courier;\">cl.exe \/Ox<\/span>) on Windows and (<span style=\"font-family: courier new,courier;\">g++ -O3<\/span>) on Linux.<\/p>\n<p><img loading=\"lazy\" decoding=\"async\" class=\" size-full wp-image-5046\" src=\"https:\/\/www.modernescpp.com\/wp-content\/uploads\/2016\/11\/comparisonTableEng.png\" alt=\"comparisonTableEng\" width=\"700\" height=\"112\" style=\"margin: 15px;\" srcset=\"https:\/\/www.modernescpp.com\/wp-content\/uploads\/2016\/11\/comparisonTableEng.png 812w, https:\/\/www.modernescpp.com\/wp-content\/uploads\/2016\/11\/comparisonTableEng-300x48.png 300w, https:\/\/www.modernescpp.com\/wp-content\/uploads\/2016\/11\/comparisonTableEng-768x123.png 768w\" sizes=\"auto, (max-width: 700px) 100vw, 700px\" \/><\/p>\n<h2>What&#8217;s next?<\/h2>\n<p>In the post <a href=\"https:\/\/www.modernescpp.com\/index.php\/hash-tables\">Hash tables<\/a>, I said quite imprecise that the unordered associative containers have a similar interface as the ordered associative containers. That is not true. The unordered associative containers have a more powerful interface than the ordered pendants. E.g., you can&nbsp;adjust the hash function or the distribution of the hash values to their buckets. As ever, the details will follow in the <a href=\"https:\/\/www.modernescpp.com\/index.php\/hash-functions\">next post. <\/a><span id=\"transmark\"><\/span><\/p>\n<p>&nbsp;<\/p>\n<p>&nbsp;<\/p>\n<p>&nbsp;<\/p>\n","protected":false},"excerpt":{"rendered":"<p>Before I take a deeper look insight the interface of the hash tables &#8211; officially called unordered associative containers &#8211; I will first compare the performance of the associative containers. The best candidates are std::unordered_map and the ordered pendant std::map because both are used most frequently.<\/p>\n","protected":false},"author":21,"featured_media":5042,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[364],"tags":[472],"class_list":["post-5047","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-embedded","tag-associative-containers"],"_links":{"self":[{"href":"https:\/\/www.modernescpp.com\/index.php\/wp-json\/wp\/v2\/posts\/5047","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/www.modernescpp.com\/index.php\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/www.modernescpp.com\/index.php\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/www.modernescpp.com\/index.php\/wp-json\/wp\/v2\/users\/21"}],"replies":[{"embeddable":true,"href":"https:\/\/www.modernescpp.com\/index.php\/wp-json\/wp\/v2\/comments?post=5047"}],"version-history":[{"count":1,"href":"https:\/\/www.modernescpp.com\/index.php\/wp-json\/wp\/v2\/posts\/5047\/revisions"}],"predecessor-version":[{"id":6922,"href":"https:\/\/www.modernescpp.com\/index.php\/wp-json\/wp\/v2\/posts\/5047\/revisions\/6922"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/www.modernescpp.com\/index.php\/wp-json\/wp\/v2\/media\/5042"}],"wp:attachment":[{"href":"https:\/\/www.modernescpp.com\/index.php\/wp-json\/wp\/v2\/media?parent=5047"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/www.modernescpp.com\/index.php\/wp-json\/wp\/v2\/categories?post=5047"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/www.modernescpp.com\/index.php\/wp-json\/wp\/v2\/tags?post=5047"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}