<p><em>Single-threaded</em> memory bandwidth on modern CPUs is limited by <code>max_concurrency / latency</code> of the transfers from L1D to the rest of the system, not by DRAM-controller bottlenecks.  Each core has 10 Line-Fill Buffers (LFBs) which track outstanding requests to/from L1D.  (And 16 "superqueue" entries which track lines to/from L2).</p>

<p>(Update: experiments show that Skylake probably has 12 LFBs, up from 10 in Broadwell.  e.g. Fig7 in <a href="https://arxiv.org/pdf/1905.05726.pdf" rel="noreferrer">the ZombieLoad paper</a>, and other performance experiments including <a href="https://github.com/Kobzol/hardware-effects/issues/1#issuecomment-467629288" rel="noreferrer">@BeeOnRope's testing of multiple store streams</a>)</p>

<hr>

<p><strong>Intel's many-core chips have higher latency to L3 / memory than quad-core or dual-core desktop / laptop chips, so <em>single-threaded</em> memory bandwidth is actually much worse</strong> on a big Xeon, even though the max aggregate bandwidth with many threads is much better.  They have many more hops on the ring bus that connects cores, memory controllers, and the System Agent (PCIe and so on).</p>

<p>SKX (Skylake-server / AVX512, including the i9 "high-end desktop" chips) is really bad for this: L3 / memory latency is significantly higher than for Broadwell-E / Broadwell-EP, so single-threaded bandwidth is even worse than on a Broadwell with a similar core count.  (SKX uses a mesh instead of a ring bus because that scales better, <a href="https://www.anandtech.com/show/11550/the-intel-skylakex-review-core-i9-7900x-i7-7820x-and-i7-7800x-tested/5" rel="noreferrer">see this for details on both</a>.  But apparently the constant factors are bad in the new design; maybe future generations will have better L3 bandwidth/latency for small / medium core counts.  The private per-core L2 is bumped up to 1MiB though, so maybe L3 is intentionally slow to save power.)</p>

<p>(Skylake-client (SKL) like in the question, and later quad/hex-core desktop/laptop chips like Kaby Lake and Coffee Lake, still use the simpler ring-bus layout.  Only the server chips changed.  We don't yet know for sure what Ice Lake client will do.)</p>

<hr>

<p>A quad or dual core chip only needs a couple threads (especially if the cores + uncore (L3) are clocked high) to saturate its memory bandwidth, and a Skylake with fast DDR4 dual channel has quite a lot of bandwidth.</p>

<p>For more about this, see the Latency-bound Platforms section of <a href="https://stackoverflow.com/questions/43343231/enhanced-rep-movsb-for-memcpy/43574756#43574756">this answer</a> about x86 memory bandwidth.  (And read the other parts for memcpy/memset with SIMD loops vs. <code>rep movs/rep stos</code>, and NT stores vs. regular RFO stores, and more.)</p>

<p>Also related: <a href="https://stackoverflow.com/questions/8126311/what-every-programmer-should-know-about-memory/47714514#47714514">What Every Programmer Should Know About Memory?</a> (2017 update on what's still true and what's changed in that excellent article from 2007).</p>
Why is Skylake so much better than Broadwell-E for single-threaded memory throughput?
<p>We've got a simple memory throughput benchmark. All it does is memcpy repeatedly for a large block of memory.</p>

<p>Looking at the results (compiled for 64-bit) on a few different machines, Skylake machines do significantly better than Broadwell-E, keeping OS (Win10-64), processor speed, and RAM speed (DDR4-2133) the same. We're not talking a few percentage points, <strong>but rather a factor of about 2</strong>. Skylake is configured dual-channel, and the results for Broadwell-E don't vary for dual/triple/quad-channel.</p>

<p>Any ideas why this might be happening? The code that follows is compiled in Release in VS2015, and reports average time to complete each memcpy at:</p>

<p><strong>64-bit: 2.2ms for Skylake vs 4.5ms for Broadwell-E</strong></p>

<p><strong>32-bit: 2.2ms for Skylake vs 3.5ms for Broadwell-E</strong>.</p>

<p>We can get greater memory throughput on a quad-channel Broadwell-E build by utilizing multiple threads, and that's nice, but to see such a drastic difference for single-threaded memory access is frustrating. <strong>Any thoughts on why the difference is so pronounced?</strong></p>

<p>We've also used various benchmarking software, and they validate what this simple example shows - single-threaded memory throughput is way better on Skylake.</p>

<pre><code>#include &lt;memory&gt;
#include &lt;Windows.h&gt;
#include &lt;iostream&gt;

//Prevent the memcpy from being optimized out of the for loop
_declspec(noinline) void MemoryCopy(void *destinationMemoryBlock, void *sourceMemoryBlock, size_t size)
{
    memcpy(destinationMemoryBlock, sourceMemoryBlock, size);
}

int main()
{
    const int SIZE_OF_BLOCKS = 25000000;
    const int NUMBER_ITERATIONS = 100;
    void* sourceMemoryBlock = malloc(SIZE_OF_BLOCKS);
    void* destinationMemoryBlock = malloc(SIZE_OF_BLOCKS);
    LARGE_INTEGER Frequency;
    QueryPerformanceFrequency(&amp;Frequency);
    while (true)
    {
        LONGLONG total = 0;
        LONGLONG max = 0;
        LARGE_INTEGER StartingTime, EndingTime, ElapsedMicroseconds;
        for (int i = 0; i &lt; NUMBER_ITERATIONS; ++i)
        {
            QueryPerformanceCounter(&amp;StartingTime);
            MemoryCopy(destinationMemoryBlock, sourceMemoryBlock, SIZE_OF_BLOCKS);
            QueryPerformanceCounter(&amp;EndingTime);
            ElapsedMicroseconds.QuadPart = EndingTime.QuadPart - StartingTime.QuadPart;
            ElapsedMicroseconds.QuadPart *= 1000000;
            ElapsedMicroseconds.QuadPart /= Frequency.QuadPart;
            total += ElapsedMicroseconds.QuadPart;
            max = max(ElapsedMicroseconds.QuadPart, max);
        }
        std::cout &lt;&lt; "Average is " &lt;&lt; total*1.0 / NUMBER_ITERATIONS / 1000.0 &lt;&lt; "ms" &lt;&lt; std::endl;
        std::cout &lt;&lt; "Max is " &lt;&lt; max / 1000.0 &lt;&lt; "ms" &lt;&lt; std::endl;
    }
    getchar();
}
</code></pre>