Skip to content
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
131 changes: 76 additions & 55 deletions pretext/AlgorithmAnalysis/HashTableAnalysis.ptx
Original file line number Diff line number Diff line change
Expand Up @@ -91,70 +91,93 @@
advantage of the hash table is the constant speed of the <c>contains</c>
operation.</p>
<p>For our last performance experiment we will compare the performance of
the contains operation between vectors and hash tables. In the process we
will confirm that the contains operation for vectors is <m>O(n)</m> and
the contains operation for hash tables is <m>O(1)</m>. The experiment
we will use to compare the two is simple. We'll make a vector with a range
of numbers in it. Then we will pick numbers at random and check to see
if the numbers are in the vector. If our performance tables are correct
the bigger the vector the longer it should take to determine if any one
number is contained in the vector.</p>
searching for a value between vectors and hash tables. In C++, vectors do
not have a <c>contains</c> method, so we will use <c>std::find</c> to
search the vector and <c>find</c> (or <c>contains</c>) to search the hash
table. In the process we will confirm that searching a vector is
<m>O(n)</m> and searching a hash table is <m>O(1)</m>. The experiment we
will use to compare the two is simple. We'll fill a vector with a range of
numbers, then search for every one of those numbers in turn and time how
long the search takes. If our performance tables are correct, the bigger
the vector the longer it should take to find a number, even though the
values themselves are already in sorted order.</p>
<p>We will repeat the same experiment for a hash table that contains
numbers as the keys. In this experiment we should see that determining
numbers as the keys. In this experiment we should see that finding
whether or not a number is in the hash table is not only much faster,
but the time it takes to check should remain constant even as the
hash table grows larger.</p>
but the time it takes to search should remain roughly constant even as
the hash table grows larger. Note that in both cases we only time the
search itself — filling each container is done first and is not included
in the measurement, since we want to isolate the cost of the search
operation.</p>
<p><xref ref="expl-hashtable-analysis"/> implements this comparison. Notice that we are
performing exactly the same operation, <c>number in container</c>. The
difference is that on line 7 <c>x</c> is vector, and on line 9 <c>x</c> is a
hash table.</p>
performing an equivalent search operation in each container: <c>std::find</c>
against the vector, and <c>find</c> against the hash table in the C++
version, or the <c>in</c> operator against a list and a dict in the
Python version. In each case only the search loop is timed, not the
loop that builds the container.</p>
<exploration xml:id="expl-hashtable-analysis">
<title>Hash Table Analysis</title>
<task xml:id="lst-hashtable-analysis-cpp" label="lst-hashtable-analysis-cpp">
<title>C++ Implementation</title>
<statement><program language="cpp" label="HashTableAnalysis-prog" line-numbers="yes"><code>
#include &lt;algorithm&gt;
#include &lt;chrono&gt;
#include &lt;iomanip&gt;
#include &lt;iostream&gt;
#include &lt;ctime&gt;
#include &lt;vector&gt;
#include &lt;numeric&gt;
#include &lt;unordered_map&gt;
#include &lt;vector&gt;
using namespace std;

int main() {
for(int a = 10000; a &lt; 1000001; a = a + 20000) {
vector&lt;int&gt; avector;
unordered_map&lt;int, int&gt; amap;
cout &lt;&lt; setw(10) &lt;&lt; "size"
&lt;&lt; setw(15) &lt;&lt; "vector (s)"
&lt;&lt; setw(15) &lt;&lt; "hash table (s)" &lt;&lt; endl;

// create the vector and map
for( int i = 0; i &lt; a; i++){
avector.push_back(i);
amap[i] = -1;
for(int a = 10000; a &lt; 1000001; a = a + 20000) {
// build and fill the vector (not timed)
vector&lt;int&gt; avector(a);
iota(avector.begin(), avector.end(), 0);

// time searching the vector for every value
auto begin = chrono::steady_clock::now();
for(int i = 0; i &lt; a; i++){
if (find(avector.begin(), avector.end(), i) == avector.end()) {
cerr &lt;&lt; "value not found in vector!" &lt;&lt; endl;
return 1;
}
}
auto end = chrono::steady_clock::now();
chrono::duration&lt;double&gt; elapsed_secs = end - begin;
double avg_vector_time = elapsed_secs.count() / a;

// timing for vector access
srand(1);
clock_t begin = clock();
for( int i = 0; i &lt; a; i++){
find(avector.begin(), avector.end(), rand() % a);
// build and fill the hash table (not timed)
unordered_map&lt;int, int&gt; amap;
for(int i = 0; i &lt; a; i++){
amap[i] = i;
}
clock_t end = clock();
double elapsed_secs = double(end - begin) / CLOCKS_PER_SEC;

// timing for hash table
srand(1);
clock_t begin_ht = clock();
for( int i = 0; i &lt; a; i++ ){
amap.contains(rand() % a);

// time searching the hash table for every value
auto begin_ht = chrono::steady_clock::now();
for(int i = 0; i &lt; a; i++){
if (amap.find(i) == amap.end()) {
cerr &lt;&lt; "value not found in hash table!" &lt;&lt; endl;
return 2;
}
}
clock_t end_ht = clock();
double elapsed_secs_ht = double(end_ht - begin_ht) / CLOCKS_PER_SEC;
auto end_ht = chrono::steady_clock::now();
chrono::duration&lt;double&gt; elapsed_secs_ht = end_ht - begin_ht;
double avg_hashtable_time = elapsed_secs_ht.count() / a;

// Printing final output
cout &lt;&lt; a &lt;&lt; "\t" &lt;&lt; elapsed_secs &lt;&lt; "\t" &lt;&lt; elapsed_secs_ht &lt;&lt; endl;
cout &lt;&lt; setw(10) &lt;&lt; a
&lt;&lt; setw(15) &lt;&lt; avg_vector_time
&lt;&lt; setw(15) &lt;&lt; avg_hashtable_time &lt;&lt; endl;
}

return 0;
}
</code></program></statement>
</code></program></statement>
</task>
<task xml:id="lst-hashtable-analysis-py" label="lst-hashtable-analysis-py">
<title>Python Implementation</title>
Expand All @@ -173,20 +196,18 @@ for i in range(10000,1000001,20000):
</code></program></statement>
</task>
</exploration>
<p><xref ref="fig-vectvshash-cpp"/> summarizes the results of running
<xref ref="expl-hashtable-analysis"/>. You can see that the hash table is consistently
faster. For the smallest vector size of 10,000 elements a hash table is
89.4 times faster than a vector. For the largest vector size of 990,000
elements the hash table is 11,603 times faster! You can also see that
the time it takes for the contains operator on the vector grows linearly
with the size of the vector. This verifies the assertion that the contains
operator on a vector is <m>O(n)</m>. It can also be seen that the time
for the contains operator on a hash table is constant even as the
hash table size grows. In fact for a hash table size of 10,000 the
contains operation took 0.004 milliseconds and for the hash table size
of 990,000 it also took 0.004 milliseconds.</p>

<figure xml:id="fig-vectvshash-cpp">
<p>Running <xref ref="expl-hashtable-analysis"/> confirms that the hash table is
consistently faster. For the smallest vector size of 10,000 elements a hash table
search is approximately 320 times faster than a vector search. For the largest vector
size of 990,000 elements the hash table is approximately 27,500 times faster! You can also
see that the time it takes to search the vector grows linearly with the
size of the vector. This verifies the assertion that searching a vector
is <m>O(n)</m>. It can also be seen that the time to search a hash table
stays roughly constant even as the hash table grows larger. In fact for a
hash table size of 10,000 the search took approximately 2 nanoseconds
and for the hash table size of 990,000 it also took approximately 2 nanoseconds.</p>

<!-- <figure xml:id="fig-vectvshash-cpp">
<caption>Comparing the <c>contains</c> operation for C++ vectors and Hash Tables</caption>
<image source="AlgorithmAnalysis/vectvshash.png" width="90%">
<description><p>The primary take away is that the graph for vectors shows an almost linear climb from
Expand All @@ -195,7 +216,7 @@ for i in range(10000,1000001,20000):
in the hash table. This reflects the linear versus constant time growth of the two
functions.</p></description>
</image>
</figure>
</figure> -->

<p>Since C++ is an evolving language, there are always changes going on
behind the scenes. The latest information on the performance of C++
Expand Down