title: Why does this piece of code written using uint8_t run faster than analogous code written with uint32_t or uint64_t on a 64bit machine?
body: <p>Isn't the common knowledge that math operations on 64bit systems run faster on 32/64 bit datatypes than the smaller datatypes like short due to implicit promotion? Yet while testing my bitset implementation(where the majority of the time depends on bitwise operations), I found I got a ~40% improvement using uint8_t over uint32_t. I'm especially surprised because there is hardly any copying going on that would justify the difference. The same thing occurred regardless of the clang optimisation level.</p>
  <p>8bit:</p>
  <pre><code>#define mod8(x) x&amp;7
  #define div8(x) x&gt;&gt;3
  
  template&lt;unsigned long bits&gt;
  struct bitset{
  private:
      uint8_t fill[8] = {};
      uint8_t clear[8];
      uint8_t band[(bits/8)+1] = {};
  
  public:
      template&lt;typename T&gt;
      inline bool operator[](const T ind) const{
          return band[div8(ind)]&amp;fill[mod8(ind)];
      }
  
      template&lt;typename T&gt;
      inline void store_high(const T ind){
          band[div8(ind)] |= fill[mod8(ind)];
      }
  
  
      template&lt;typename T&gt;
      inline void store_low(const T ind){
          band[div8(ind)] &amp;= clear[mod8(ind)];
  
      }
      bitset(){
          for(uint8_t ii = 0, val = 1; ii &lt; 8; ++ii){
              fill[ii] = val;
              clear[ii] = ~fill[ii];
              val*=2;
          }
      }
  };
  </code></pre>
  <p>32bit:</p>
  <pre><code>#define mod32(x) x&amp;31
  #define div32(x) x&gt;&gt;5
  
  template&lt;unsigned long bits&gt;
  struct bitset{
  private:
      uint32_t fill[32] = {};
      uint32_t clear[32];
      uint32_t band[(bits/32)+1] = {};
  
  public:
      template&lt;typename T&gt;
      inline bool operator[](const T ind) const{
          return band[div32(ind)]&amp;fill[mod32(ind)];
      }
  
      template&lt;typename T&gt;
      inline void store_high(const T ind){
          band[div32(ind)] |= fill[mod32(ind)];
      }
  
  
      template&lt;typename T&gt;
      inline void store_low(const T ind){
          band[div32(ind)] &amp;= clear[mod32(ind)];
  
      }
      bitset(){
          for(uint32_t ii = 0, val = 1; ii &lt; 32; ++ii){
              fill[ii] = val;
              clear[ii] = ~fill[ii];
              val*=2;
          }
      }
  };
  </code></pre>
  <p>And here is the benchmark I used(just moves a single 1 from position 0 till the end iteratively):</p>
  <pre><code>const int len = 1000000;   
  bitset&lt;len&gt; bs;
  
      {
          auto start = std::chrono::high_resolution_clock::now();
          bs.store_high(0);
          for (int ii = 1; ii &lt; len; ++ii) {
              bs.store_high(ii);
              bs.store_low(ii-1);
          }
          auto stop = std::chrono::high_resolution_clock::now();
          std::cout &lt;&lt; std::chrono::duration_cast&lt;std::chrono::microseconds&gt;((stop-start)).count()&lt;&lt;std::endl;
      }
  </code></pre>