当处理某些数据流(例如,来自网络的请求)时,使用某些临时存储器是很常见的。例如,URL可以分成多个字符串,每个字符串可能从堆中分配内存。这些实体的使用通常是短暂的,内存总量通常相对较小,应该适合CPU缓存。
当用于临时字符串的内存被释放时,字符串内容很可能只存在于缓存中。但是,CPU并不知道要释放的内存:释放只是内存管理系统中的更新。结果,当CPU高速缓存用于其他存储器时,CPU可能最终不必要地将未使用的内容写入实际存储器 - 除非存储器释放以某种方式向CPU指示存储器不再被使用。因此,问题变为:
释放内存的内存管理功能是否会以某种方式表明相应内存的内容可以被丢弃?是否有办法向CPU指示内存不再使用? (至少,对于某些CPU:显然,架构之间可能存在差异)由于不同的实现可能会在质量上有所不同,可能会或可能不会做任何花哨的事情,问题实际上是否有任何内存管理实现指示内存为未使用?
我确实意识到始终使用相同的内存区域可能是一种缓解策略,以避免对实际内存进行不必要的写入。在这种情况下,将使用相同的缓存内存。类似地,内存分配可能总是产生相同的内存,也避免了不必要的内存传输。但是,我可能不需要依赖任何适用的技术。
答案 0 :(得分:9)
您提到的缓存操作(将缓存内存标记为未使用且丢弃而不回写到主内存)称为缓存行无效而无需回写。这是通过带有操作数的特殊指令执行的,该操作数可能(或可能不)指示要使无效的高速缓存行的地址。
在我熟悉的所有架构中,这条指令很有特权,我认为这是有充分理由的。这意味着用户模式代码不能使用该指令;只有内核才可以。否则,可能存在的变态欺骗,数据丢失和拒绝服务的数量是令人难以置信的。
因此,没有内存分配器可以做你的建议;他们根本没有(在用户模式下)这样做的工具。
invd
指令,该指令使所有内部缓存无效而无需回写,并指示外部缓存也使自身无效。这是唯一能够在没有回写的情况下无效的指令,它确实是一种钝器。
clflush
指令指定了受害者地址,但它在无效之前写回,所以我只是顺便提一下。MCR p15, 0, <Rd>, c7, <CRm>, <Opcode_2>
进行回写。可以指定受害者缓存行。写入此寄存器是特权。dcbi
,可让您指定受害者,dci
并非指令和两者的指令缓存版本,但所有四个都是特权{ {3}}。CACHE
,但在6.04中,Imagination Technologies将水弄得一团糟,而且不再清楚什么是特权,什么不是。因此,如果没有在用户模式中直接刷新/写回,就会排除使用缓存失效。
但是,我认为在kernelmode中它仍然是一个坏主意,原因有很多:
kmalloc()
为不同大小的分配分配竞技场。特别是,它具有每个分配大小<=192
字节的竞技场,步长为8
;这意味着对象可能可能比高速缓存线更接近彼此,或者与下一个对象部分重叠,因此使用无效可能会破坏正确位于高速缓存中但尚未写回的附近对象。这是错误。
glibc
不会使释放的内存失效。jemalloc
不会使释放的内存失效。musl
- libc&#39; s dlmalloc不会使释放的内存失效。它们都没有使记忆无效,因为它们不能。为了使高速缓存行无效而进行系统调用将非常缓慢,并且会因为上下文切换而导致更多的流量进出缓存。
答案 1 :(得分:2)
我不知道任何架构会愿意将其缓存一致性协议暴露给像这样的软件(用户甚至内核)操作。这会产生几乎无法处理的警告。 请注意,用户启动的刷新是可接受的暴露,但绝不会破坏内存一致性。
举个例子,假设您有一个缓存行,其中包含您不再需要的临时数据。因为它被写入,它将被修改&#34;缓存中的状态。 现在你需要一种机制来告诉缓存以避免将其写回来,但这意味着你创建了一个竞争条件 - 如果其他人在你应用这个技巧之前要查找该行,他就会把它从核心中剔除收到了更新的数据。如果你的核心先行了,那么新数据就会丢失 - 因此,记忆中该地址的结果取决于种族。
您可能会争辩说,在多线程编程中经常出现这种情况,但是在运行单个线程时也可能出现这种情况(如果缓存已满,CPU可能会提前驱逐一行,或者某些较低的包含级别会丢失它) 。更糟糕的是,这打破了整个虚拟内存看起来平坦的前提,CPU只为性能维护缓存版本,但不能破坏一致性或一致性(除了一些记录多线程的情况,取决于内存排序模型,可以是通过软件保护克服。)
修改强> 如果您愿意扩展您认为的内存和内存的定义,您可以寻找非连贯类型的内存,这些内存在定义和实现方面有所不同,但有些可能会提供您所寻求的内容。一些架构暴露了&#34; scratchpad&#34;内存,由用户控制,允许快速访问,没有缓存一致性的麻烦(但也没有它的好处)。有些架构甚至可以提供可配置的硬件,允许您选择是否要在其中缓存主内存,还是将其用作暂存区。
答案 2 :(得分:1)
这是您正在使用的实现和库的问题。分配和释放的内存往往会很快重新分配。大多数分配都是小块,比需要时写入备份存储的页面要小得多。
今天,RAM大小通常很大,以至于当操作系统开始将脏页写入后备存储时,无论如何都会遇到麻烦。如果你有16 GB的RAM,你将不会写100千字节或兆字节,你将写入千兆字节,你的计算机将慢下来爬行。用户将通过不使用使用过多内存的应用程序来避免这种情况。
答案 3 :(得分:1)
相当多的分配器存储&#34;空闲阻止列表&#34;在自由块本身。即当你调用该释放函数时,分配的块被拼接到空闲列表中,这可能意味着用前向和后向指针覆盖旧数据。这些写入至少会覆盖分配的第一部分。
分配器使用的第二种技术是积极地回收内存。如果下一个分配可以与最新的重新分配匹配,则可能是缓存未被刷新到主存储器。
你的想法存在的问题是,每个人的写作实际上 非常昂贵,并且弄清楚什么可以丢弃会涉及相当多的昂贵的簿记。你不能现实地进行系统调用。这意味着您需要在每个应用程序中进行簿记(这是合理的:释放这些小块通常会将内存返回给应用程序,而不是操作系统)。这反过来意味着应用程序需要了解CPU缓存设计,这绝不是常量。应用程序甚至需要了解不同的缓存一致性方案!
答案 4 :(得分:1)
您在此处提出了许多相关问题。粗体字是最容易回答的。当您使用通用类型的版本发布内存时,您所说的只有 的内容是&#34;我不再需要这个&#34;。你是也含蓄地说,&#34;我不在乎你用它做什么&#34;。这&#34;我不在乎&#34;实际上是你问题的答案。你不说&#34;你可以放弃这个&#34;。你 说&#34;我不在乎你是否放弃&#34;。
要回答有关CPU支持的问题,MSI protocol是基本的缓存一致性协议。 I
状态代表&#34;无效&#34;,这就是你如何实现&#34;未使用的内存&#34;陈述你的问题。为了做到这一点,你要创建一个具有非泛型语义的发布接口,也就是说,这种发布意味着&#34;这个内存不再使用和你应该避免把它写回主内存&#34;。请注意,这种语义要求通用版本不具备CPU的行为。要实现这一点,您需要分配与CPU高速缓存协调的内存,然后使用可用的CPU指令使高速缓存项无效。您几乎肯定需要编写汇编代码来完成这项工作,以避免对使用显式缓存管理指令会导致的内存模型进行无根据(且不正确)的假设。
我个人无需在一段时间内在这个级别工作,所以我不熟悉到处都可以使用的东西,也就是说,这种技术是否可以合理地移植。 Intel CPU有INVLPG
条指令。这里的讨论应该成为您关注问题下一阶段的一个不错的起点:When to do or not do INVLPG, MOV to CR3 to minimize TLB flushing