<div dir="ltr">Dmitry,<br><br>"On the contrary: rte_pktmbuf_pool_create() takes the amount<br>of usable memory (dataroom) and adds space for rte_mbuf and the headroom.<br>Furthermore, the underlying rte_mempool_create() ensures element (mbuf)<br>alignment, may spread the elements between pages, etc."<br><br>Thanks. This is a crucial correction to my erroneous statement. <br><br><div>I'd like to press-on then with one of my questions that, after some additional thought</div><div>is answered however implicitly. For the benefit of other programmers who are new </div><div>to this work. I'll explain. If wrong, please hammer on it.<br><br>The other crucial insight is: so long as memory is allocated on the same NUMA</div><div>node as the RXQ/TXQ runs that ultimately uses it, there is only marginal performance</div><div>advantage to having per-core caching of mbufs in a mempool as provided by the<br>private_data_size formal argument in rte_mempool_create() here:<br><br><a href="https://doc.dpdk.org/api/rte__mempool_8h.html#a503f2f889043a48ca9995878846db2fd">https://doc.dpdk.org/api/rte__mempool_8h.html#a503f2f889043a48ca9995878846db2fd</a><br><br>In fact the API doc should really point out the advantage; perhaps it eliminates some</div><div>cache sloshing to get the last few percent of performance. It probably is not a major</div><div>factor in latency or bandwidth with or without private_data_size==0.</div><div><br>Memory access from an lcore x (aka H/W thread, vCPU) on NUMA N is fairly unchanged</div><div>to any other distinct lcore y != x provided y also runs on N <b>and the memory was allocated</b></div><div><b>for N</b>. Therefore, lcore affinity to a mempool is pretty much a red herring.</div><div><br></div><div>Consider this code which originally I used as indicative of good mempool creation,</div><div>but upon further thinking got me confused:</div><div><br><a href="https://github.com/erpc-io/eRPC/blob/master/src/transport_impl/dpdk/dpdk_init.cc#L76">https://github.com/erpc-io/eRPC/blob/master/src/transport_impl/dpdk/dpdk_init.cc#L76</a><br>





<p class="gmail-p1" style="margin:0px;font-variant-numeric:normal;font-variant-east-asian:normal;font-stretch:normal;font-size:11px;line-height:normal;font-family:Menlo;color:rgb(33,255,6);background-color:rgb(0,0,0)"><span class="gmail-s1" style="font-variant-ligatures:no-common-ligatures"><span class="gmail-Apple-converted-space">  </span>for (size_t i = 0; i < kMaxQueuesPerPort; i++) {</span></p>
<p class="gmail-p1" style="margin:0px;font-variant-numeric:normal;font-variant-east-asian:normal;font-stretch:normal;font-size:11px;line-height:normal;font-family:Menlo;color:rgb(33,255,6);background-color:rgb(0,0,0)"><span class="gmail-s1" style="font-variant-ligatures:no-common-ligatures"><span class="gmail-Apple-converted-space">    </span>const std::string pname = get_mempool_name(phy_port, i);</span></p>
<p class="gmail-p1" style="margin:0px;font-variant-numeric:normal;font-variant-east-asian:normal;font-stretch:normal;font-size:11px;line-height:normal;font-family:Menlo;color:rgb(33,255,6);background-color:rgb(0,0,0)"><span class="gmail-s1" style="font-variant-ligatures:no-common-ligatures"><span class="gmail-Apple-converted-space">    </span>rte_mempool *mempool =</span></p>
<p class="gmail-p1" style="margin:0px;font-variant-numeric:normal;font-variant-east-asian:normal;font-stretch:normal;font-size:11px;line-height:normal;font-family:Menlo;color:rgb(33,255,6);background-color:rgb(0,0,0)"><span class="gmail-s1" style="font-variant-ligatures:no-common-ligatures"><span class="gmail-Apple-converted-space">        </span></span><span class="gmail-s2" style="font-variant-ligatures:no-common-ligatures;color:rgb(0,0,0);background-color:rgb(33,255,6)">rte_pktmbuf_pool_create</span><span class="gmail-s1" style="font-variant-ligatures:no-common-ligatures">(pname.c_str(), kNumMbufs, 0 /* cache */,</span></p>
<p class="gmail-p1" style="margin:0px;font-variant-numeric:normal;font-variant-east-asian:normal;font-stretch:normal;font-size:11px;line-height:normal;font-family:Menlo;color:rgb(33,255,6);background-color:rgb(0,0,0)"><span class="gmail-s1" style="font-variant-ligatures:no-common-ligatures"><span class="gmail-Apple-converted-space">                                </span>0 /* priv size */, kMbufSize, numa_node);</span></p><div><br>This has the appearance of creating one mempool per each RXQ and each TXQ. And in</div><div>fact this is what it does. The programmer here ensures the numa_node passed in as the</div><div>last argument is the same numa_node the RXQ/TXQ eventually runs. Since each lcore</div><div>has its own mempool and because rte_pktmbuf_create never calls into rte_mempool_create()</div><div>with a non-zero private_data_size, per lcore caching doesn't arise. (I briefly checked </div><div>mbuf/rte_mbuf.c to confirm). Indeed <b>lcore v. mempool affinity is irrelevant</b> provided the RXQ </div><div>for a given mempool runs on the same numa_node as specified in the last argument to</div><div>rte_pktmbuf_pool_create.</div><div><br></div><div>Let's turn then to a larger issue: what happens if different RXQ/TXQs have radically different</div></div><div>needs? </div><div><br></div><div>As the code above illustrates, one merely allocates a size appropriate to an individual RXQ/TXQ<br>by changing the count and size of mbufs ---- which is as simple as it can get. You have 10 queues</div><div>each with their own memory needs? OK, then allocate one memory pool for each. None of the other</div><div>9 queues will have that mempool pointer. Each queue will use the mempool only that was specified</div><div>for it. To beat a dead horse just make sure the numa_node in the allocation and the numa node which </div><div>will ultimately run the RXQ/TXQ are the same.</div><div><br></div></div><br><div class="gmail_quote"><div dir="ltr" class="gmail_attr">On Sat, Jan 29, 2022 at 8:23 PM Dmitry Kozlyuk <<a href="mailto:dmitry.kozliuk@gmail.com">dmitry.kozliuk@gmail.com</a>> wrote:<br></div><blockquote class="gmail_quote" style="margin:0px 0px 0px 0.8ex;border-left:1px solid rgb(204,204,204);padding-left:1ex">2022-01-29 18:46 (UTC-0500), fwefew 4t4tg:<br>
[...]<br>
> 1. Does cache_size include or exclude data_room_size?<br>
> 2. Does cache_size include or exclude sizeof(struct rtre_mbuf)?<br>
> 3. Does cache size include or exclude RTE_PKTMBUF_HEADROOM?<br>
<br>
Cache size is measured in the number of elements, irrelevant of their size.<br>
It is not a memory size, so the questions above are not really meaningful.<br>
<br>
> 4. What lcore is the allocated memory pinned to? <br>
<br>
Memory is associated with a NUMA node (DPDK calls it "socket"), not an lcore.<br>
Each lcore belongs to one NUMA node, see rte_lcore_to_socket_id().<br>
<br>
> The lcore of the caller<br>
> when this method is run? The answer here is important. If it's the lcore of<br>
> the caller when called, this routine should be called in the lcore's entry<br>
> point so it's on the right lcore the memory is intended. Calling it on the<br>
> lcore that happens to be running main, for example, could have a bad side<br>
> effect if it's different from where the memory will be ultimately used.<br>
<br>
The NUMA node is controlled by "socket_id" parameter.<br>
Your considerations are correct, often you should create separate mempools<br>
for each NUMA node to avoid this performance issue. (You should also<br>
consider which NUMA node each device belongs to.)<br>
<br>
> 5. Which one of the formal arguments represents tail room indicated in<br>
> <a href="https://doc.dpdk.org/guides/prog_guide/mbuf_lib.html#figure-mbuf1" rel="noreferrer" target="_blank">https://doc.dpdk.org/guides/prog_guide/mbuf_lib.html#figure-mbuf1</a><br>
[...]<br>
> 5. Unknown. Perhaps if you want private data which corresponds to tail room<br>
> in the diagram above one has to call rte_mempool_create() instead and focus<br>
> on private_data_size.<br>
<br>
Incorrect; tail room is simply an unused part at the end of the data room.<br>
Private data is for the entire mempool, not for individual mbufs.<br>
<br>
> Mempool creation is like malloc: you request the total number of absolute<br>
> bytes required. The API will not add or remove bytes to the number you<br>
> specify. Therefore the number you give must be inclusive of all needs<br>
> including your payload, any DPDK overheader, headroom, tailroom, and so on.<br>
> DPDK is not adding to the number you give for its own purposes. Clearer?<br>
> Perhaps ... but what needs? Read on ...<br>
<br>
On the contrary: rte_pktmbuf_pool_create() takes the amount<br>
of usable memory (dataroom) and adds space for rte_mbuf and the headroom.<br>
Furthermore, the underlying rte_mempool_create() ensures element (mbuf)<br>
alignment, may spread the elements between pages, etc.<br>
<br>
[...]<br>
> No. I might not. I might have half my TXQ and RXQs dealing with tiny<br>
> mbufs/packets, and the other half dealing with completely different traffic<br>
> of a completely different size and structure. So I might want memory pool<br>
> allocation to be done on a smaller scale e.g. per RXQ/TXQ/lcore. DPDK<br>
> doesn't seem to permit this.<br>
<br>
You can create different mempools for each purpose<br>
and specify the proper mempool to rte_eth_rx_queue_setup().<br>
When creating them, you can and should also take NUMA into account.<br>
Take a look at init_mem() function of examples/l3fwd.<br>
</blockquote></div>