osg*_*sgx 10 memory-management realloc linux-kernel
在阅读/sf/answers/223334261/时,我有一个问题.Qt作者在Qt 4容器内部所说的内容:
... QVector使用realloc()以4096字节的增量增长.这是有道理的,因为现代操作系统在重新分配缓冲区时不会复制整个数据; 物理内存页面只需重新排序,只需要复制第一页和最后一页上的数据.
我的问题是:
1)现代操作系统(Linux - 对我来说最有趣; FreeBSD,OSX,Windows)及其realloc实现是否真的能够使用虚拟到物理映射的重新排序重新分配数据页面并且无需逐字节复制?
2)用于实现此内存移动的系统调用是什么?(我认为可以splice使用SPLICE_F_MOVE,但它现在有缺陷且没有操作(?))
3)它是有利可图使用这些页面重排,而不是逐字节复制,尤其是在多核多线程的世界里,虚拟到物理映射的每一个变化需要刷新(无效)改变从页表项TLB中的所有特殊数十个CPU核心与IPI?(在linux中,这就像是flush_tlb_range或者flush_tlb_page)
upd for q3:mremap vs memcpy的一些测试
1)现代操作系统(Linux - 对我来说最有趣; FreeBSD,OSX,Windows)及其realloc实现是否真的能够使用虚拟到物理映射的重新排序重新分配数据页面并且无需逐字节复制?
2)用于实现此内存移动的系统调用是什么?(我认为它可以与SPLICE_F_MOVE拼接,但它现在有缺陷和无操作(?))
看到你的答案.
你的Qt例子至少有三个演员.
realloc()mremapQVector::capacity()表明Qt分配的元素多于要求.这意味着元素的典型添加不会是realloc()任何东西.该glibc的分配是基于Doug Lea的分配.这是一个binning分配器,支持Linux的使用mremap.一个分级分配器组类似大小的分配在垃圾箱,所以典型的随机分配的大小仍会有一定的空间,而无需调用系统增长.即,空闲池或松弛位于分配的内存的末尾.
3)使用这种页面改组而不是逐字节拷贝是否有利可图,特别是在多核多线程世界中,虚拟到物理映射的每次更改都需要从所有数十的TLB中清除(无效)更改的页表条目IPI的CPU内核?(在linux中,这就像flush_tlb_range或flush_tlb_page一样)
首先,更快......比mremap误用,mremap()因为那里有R笔记.
有几件东西可以mremap()作为原始的东西realloc().
这个答案中的所有内容都基于Linux的实现,但语义可以转移到其他操作系统.
考虑一个天真的 realloc().
void *realloc(void *ptr, size_t size)
{
size_t old_size = get_sz(ptr); /* From bin, address, map table, etc */
if(size <= old_size) {
resize(ptr);
return ptr;
}
void * new_p = malloc(size);
if(new_p) {
memcpy(new_p, ptr, old_size); /* fully committed old_size + new size */
free(ptr);
}
return new_p;
}
Run Code Online (Sandbox Code Playgroud)
为了支持这一点,您可能需要realloc()在进行交换之前加倍内存,或者只是无法重新分配.
默认情况下,Linux会将新分配映射到零页面 ; 一个4k页的零数据.这对稀疏映射的数据结构很有用.如果没有人写入数据页,则除了可能的表之外没有分配物理内存PTE.这些是写入或COW的副本.通过使用naive realloc(),将不会保留这些映射,并为所有零页分配完整的物理内存.
如果任务涉及a fork(),则初始realloc()数据可以在父和子之间共享.同样,COW将导致页面的物理分配.在天真的实施将打折这一点,并要求每个过程分开的物理内存.
如果系统处于内存压力下,则现有realloc()页面可能不在物理内存中,而是在交换中.在天真的 realloc会导致交换页的磁盘读取到内存中,复制到更新的位置,然后将数据写入可能出到磁盘.
与数据相比,您考虑更新TLB的问题很少.单个TLB通常是4个字节,表示物理数据的页面(4K).如果为4GB系统刷新整个TLB,则需要恢复4MB数据.复制大量数据会破坏L1和L2缓存. TLB比d-cache和i-cache更好地获取自然流水线.由于大多数代码是连续的,因此代码很少会连续获得两个TLB未命中.
CPU有两种变体,VIVT(非x86)和VIPT(根据x86).该VIVT版本通常有机制来单无效TLB项.对于VIPT系统,缓存不需要因为它们被物理标记而无效.
在多核系统上,在所有核上运行一个进程是不典型的.只有执行流程的核心才mremap()需要更新页面表.当一个进程迁移到一个核心(典型的上下文切换)时,它需要移植页面表.
您可以构建一些病态案例,其中天真的副本将更好地工作.由于Linux(以及大多数操作系统)用于多任务处理,因此将运行多个进程.此外,最糟糕的情况是交换时,这里的天真实现总是会更糟(除非你的磁盘比内存快).对于最小realloc() 尺寸,dlmalloc或QVector应具有休闲空间以避免系统级别mremap().典型的mremap()可以通过从空闲池中随机页面增长区域来扩展虚拟地址范围.只有当虚拟地址范围必须移动时才需要tlb刷新,以下所有内容都为真,mremap()
realloc()内存不能与父母或儿童进程共享.该TLB冲洗和IPI需要发生只有在同一进程上的其他核心电流.不需要L1缓存加载mremap(),但是对于天真版本.L2通常在核心之间共享,并且在所有情况下都是最新的.在天真的版本将迫使L2重新加载.在mremap()可能离开了二级缓存的一些未使用的数据; 这通常是一件好事,但在一些工作负荷下可能是一个缺点.可能有更好的方法来执行此操作,例如预取数据.
| 归档时间: |
|
| 查看次数: |
1281 次 |
| 最近记录: |