A survey of the various ways of declaring pages of memory to be uninteresting

The list of ways a program can declare pages of memory to be uninteresting seems to be growing steadily. Let's look at what we have so far today.

The most old-fashioned way of declaring a page to be uninteresting is to free it. The catch with that is that freeing the memory with the Virtual­Free function and the MEM_RELEASE flag frees the entire allocation, not individual pages. If you allocated a 64KB chunk of memory, then you have to release the whole thing. You can't release half of it.

But all is not lost. Because while you cannot free a single page from a larger allocation, you can decommit it, which is almost as good. Decommitting page is like freeing it, except that the address space is still reserved. To decommit a page, call Virtual­Free with the MEM_DECOMMIT flag.

For quite some time, those were the only tools you had available. Around the Windows NT 4 era, a new trick arrived on the scene: You could Virtual­Unlock memory that was already unlocked in order to remove it from your working set. This was a trick, because it took what used to be a programming error and gave it additional meaning, but in a way that didn't break backward compatibility because the contractual behavior of the memory did not change: The contents of the memory remain valid and the program is still free to access it at any time. The new behavior is that unlocking unlocked memory also takes it out of the process's working set, so that it becomes a prime candidate for being paged out and used to satisfy another memory allocation.

The fact that it preserved contractual behavior means that you could scatter Virtual­Unlock calls randomly throughout the program and have no effect on correctness. It might run slower (or faster), but it will still run.

Around the Windows 2000 era, the MEM_RESET flag was added to Virtual­Alloc. If you pass this flag, this tells the memory manager that the memory in question is no longer interesting to your program, and the memory manager is free to discard it without saving the contents. The memory itself remains accessible to the program, and doing so before the memory gets discarded will read the old values. On the other hand, if the memory manager decides that it needs to evict the memory (in order to satisfy a memory request elsewhere), it will throw away the contents without saving it, and then turn the page into a demand-zero-initialized page. Later, if your program tries to access the memory, it will see a page full of zeroes.

Windows 8 added the MEM_RESET_UNDO flag which says, "Hey, um, I changed my mind. I don't want you to discard the contents of the memory after all." If the memory hasn't yet been discarded, then it is "rescued" and behaves like normal memory again. But if the memory has already been discarded, then the memory manager will say, "Sorry, too late."

And then at some point, I don't know exactly when, my colleague Adrian added code to check if a page of memory is all zeroes before paging it out, and turning it into a demand-zero-initialized page if so. So another way to say that you are not interested in a page of memory is to explicitly zero it. That causes it to turn into a demand-zero-initialized page at page-out time, which avoids the I/O of writing a page full of zeroes to disk. This is another one of those things that has no effect on the programming model; it's just an optimization. If you are running on a system that doesn't perform this optimization, everything still behaves the same as before, just a little slower.

Note that writing the zeroes to the page does have its own side effects. (Well, aside from the obvious side effect of, y'know, filling the page with zeroes.) Writing to the page will set both the Dirty and Accessed bits in the page table, which will bring it into the process's working set, and therefore will reduce its likelihood of being selected for eviction. In other words, zeroing out the page "resets the clock" on the eviction calendar. Therefore, if you're going to do this, do it as soon as you're done with the memory.

In Windows 8.1 we got the function Offer­Virtual­Memory which mixes in a few new wrinkles. First of all, when you call Offer­Virtual­Memory, you pass a flag that says how much you don't care about this memory: You can say that you totally don't care, you mostly don't care, you sort of don't care, or you have no opinion on the concept of caring.

Okay, formally, what you're doing is saying how to prioritize the memory for discarding. At one extreme, you can make it a prime candidate for discarding. At the other extreme, you can say, "No special priority here. Just prioritize it according to the standard rules, as if it were plain old regular process memory."

The other wrinkle to the Offer­Virtual­Memory function is that once you offer the memory, it is no longer accessible to your program. Trying to access memory that has been offered will take an access violation.

If you later decide that you want the memory back, you can call Reclaim­Virtual­Memory, which will try to bring the memory back into your process. If it fails, then the contents are garbage.

There's also a companion function Discard­Virtual­Memory which forces an immediate discard and leaves the page contents undefined. It's the equivalent of Offer­Virtual­Memory, and then calling Reclaim­Virtual­Memory, and forcing the reclaim to fail.

Okay, so here we go with the table.

Virtual­Free +
Virtual­Free +
Virtual­Unlock Virtual­Alloc +
Zero­Memory Discard­Virtual­Memory Offer­Virtual­Memory
Is address space still reserved? N Y Y Y Y Y Y
Is memory accessible? N N Y Y Y Y N
Is memory removed from working set? Y Y Y N¹ N Y Y
Can control eviction priority? N N N N N N Y
Are previous contents recoverable? N N Y Y until eviction N N Y until eviction
Contents if recovery failed N/A N/A N/A Zeroes Zeroes Garbage Garbage

Bonus chatter: The flip side of discarding memory is prefetching it. I've discussed the Prefetch­Virtual­Memory before, so I'll leave it at a mention this time. (And here's a non-mention.)

¹ The fact that MEM_RESET does not remove the page from the working set is not actually mentioned in the documentation for the MEM_RESET flag. Instead, it's mentioned in the documentation for the Offer­Virtual­Memory function, and in a sort of backhanded way:

Note that offering and reclaiming virtual memory is similar to using the MEM_RESET and MEM_RESET_UNDO memory allocation flags, except that Offer­Virtual­Memory removes the memory from the process working set and restricts access to the offered pages until they are reclaimed.

Comments (16)
  1. Jonimoose says:

    What is unclear to me about all this is why you would want to use most of these options. 99% of the time I would think you either want the memory or you don’t. I can see a few cases where you could use the old pages as a cache for data you can recreate and are unsure if you are going to want again.

    1. Pseudonym says:

      I think that’s precisely it. Some data is inherently cache-like, in the sense that it can be recreated. Some programs, if they want to be good citizens, would like to indicate that cache-like data can be dumped if the OS really needs to free up memory. Most Windows programs are not good citizens, of course…

      Having said that, I’d bet that whoever asked for OfferVirtualMemory to be added wasn’t an application. More likely a service or a driver.

  2. nathan_works says:

    So I’m a bit slow, but I read the pre-fetch for memory mapped files .. What would the use for some of the other functionality be ? (ie why tell the OS you don’t want this page or that page of memory any more ? If you’re writing your own memory manager/cache/pool ? )

    1. I imagine this would be useful for garbage collectors and other applications that do advanced heap management.

      1. Joshua says:

        *ding*ding*ding* We have a winner!

        Garbage collection + paging -> thrashing unless the GC can tell the memory manager to not care anymore.

  3. Yukkuri says:

    Where does the gargbage come from in those cases? Hopefully not from some other process…

    1. Richard says:

      I believe that by “garbage” he means “gone to the great ramdisk in the sky”.
      Whatever you’d put there before has been destroyed, and you get back a demand-zero’d page.

      Though even on a modern machine some memory is insecure – many (possibly most) GPU drivers don’t zero memory on allocation, so it’s relatively easy to accidentally get a texture memory that contains private data like emails from Outlook!
      With tecture data the original format and layout are rather simple to work out as well because there are so few possibilities.

      1. Harry Johnston says:

        I think the only guarantee is that the page you get back won’t contain data from other processes. That doesn’t rule out random data taken from your own process. (I have no idea whether Windows actually does that, I’m just saying I don’t think it would violate the contract.)

        1. I doubt there’s even that guarantee. If it doesn’t say it’ll give you your own garbage, there’s no reason to assume it will; just assume it’s like an uninitialized stack variable and can contain anything at all. One of the features of the new APIs is that they maximize performance; you can zero or not zero as you need to, or try to read whatever was there. Your choice.

          1. Harry Johnston says:

            If it gave you pages from a process you’re not entitled to have access to, that would violate the security model. I guess that’s not technically the same thing as a violation of the API contract, though. From the programmer’s point of view it doesn’t make any difference, garbage is garbage and if your program’s behaviour varies depending on what content you’re given, that’s a bug.

        2. Medinoc says:

          Well technically I guess it could also give you memory from other processes of the same user (and integrity level) without it being a breach of security, couldn’t it?

          1. Harry Johnston says:

            True. Although I suspect that would be a layering violation, because it means the memory manager would have to know about the security model.

    2. Austin Donnelly (MSFT) says:

      Yukkuri’s questions is still a good one: in practice, where does the garbage come from?

  4. Kevin says:

    The Unix counterpart is called posix_madvise(2) (unless you just use free(3)). It does a number of things in this vein, including both eviction and prefectching, as well as advising on random vs. sequential access (i.e. turning cache read ahead off or increasing it). However, it has no impact on program semantics (i.e. any implementation must be semantically equivalent to the null implementation). Not all implementations actually bother with all of the functionality POSIX specifies, which has no effect on correctness and is therefore explicitly allowed. Linux in particular ignores (via the glibc wrapper) the “please evict this memory” signal because the underlying syscall has different semantics (see below).

    Unix also has madvise(2), but there are a lot of different implementations with varying semantics, and some systems don’t have it at all, so the POSIX people just threw their hands up and invented a new system call with “posix” in front of it as a distinguishing mark. madvise(2) still exists on Linux (and probably other systems) with semantics that do affect correctness. It can also be used to do a variety of surprisingly weird things to your memory such as case-by-case vfork(2)-like semantics.

  5. felixb says:

    > Windows 8 added the MEM_RESET_UNDO flag which says, “Hey, um, I changed my mind. I don’t want you to discard the contents of the memory after all.” If the memory hasn’t yet been discarded, then it is “rescued” and behaves like normal memory again. But if the memory has already been discarded, then the memory manager will say, “Sorry, too late.”

    What inspired this?

    1. If the cache is still valid, why not use it?

Comments are closed.

Skip to main content