What happens when you mark a section as DISCARDABLE?


In the flags you pass to the linker, you can specify that a section be made discardable. What does that mean?

If you are a kernel-mode driver, the discardable flag means that the contents will be removed from memory after initialization is complete. This is where you put your initialization code and data.

But if you're writing user-mode code, the discardable flag has no effect.

Not relevant to the topic but people are going to ask anyway: The discardable flag on resources also has no effect.

The discardable attribute for user-mode code is a left-over from 16-bit Windows, which had to simulate a hardware memory manager in software. The rule in 16-bit code was that if you marked a segment or resource as discardable, then when memory ran out, the kernel was allowed to throw the segment away, safe in the knowledge that it could get the information back by reading it from the original image.

In 32-bit Windows, this marking of discardable versus non-discardable memory is not necessary because the memory manager (with the assistance of hardware) can manage it all transparently. For example, if you never modified a code segment, the memory manager knows that it can simply discard the memory because it can recover the data from the original image. If you allocated some zero-initialized memory and never modified it, then the memory manager can just throw the data away because it is very easy to "recover" a page full of zeroes. On the other hand, if you modified some memory, then there is nowhere the memory manager can go to recover the data, so it has to put it in the page file.

Bonus chatter: "If discardability is meaningless in user mode, should we just delete it?"

Well, the PE file format is used for both user-mode and kernel-mode components, so you can't delete it from one and not the other since they are the same thing.

"I have some code that uses the pragma to make a section discardable. Should I just delete it?"

Maybe. Or maybe that flag is being used by some other part of your application. After all, the flag exists. Maybe some other part of your program uses it as a "free flag" that it usurps for some other purpose. For example, it might be used as a signal to some post-processing tool to mean "This section is exempt from the frob catalog."

Hopefully there's a comment that tells you why the section is being marked as discardable, and that will help you decide whether it's safe to remove the marking. Windows doesn't care, but some other part of your program might.

Comments (31)
  1. dave says:

    "I have some code that uses the pragma to make a section discardable. Should I just delete it?"

    Or, "is my 'DISCARDABLE' discardable?"

  2. Matt says:

    Interesting fact: Relocations are always marked as discardable, since the entire image gets mapped into memory when the image is loaded, but the relocations aren't useful as soon as the image has been mapped.

    Also, just because discardable sections aren't useful now, doesn't mean they won't be again in the future. For example, if you're the pagefile driver, wouldn't YOU want to page out discardable sections before code sections, given a choice? Discardable sections are much less likely to be read again in future, once the image has been initialized.

  3. if you're the pagefile driver, wouldn't YOU want to page out discardable sections before code sections, given a choice?

    Any code, data and stacks in the paging path MUST always be resident (non-pageable).

  4. Avi says:

    @alegr1

    Matt wasn't referring to the pager's own code.

  5. For example, if you're the pagefile driver, wouldn't YOU want to page out discardable sections before code sections, given a choice?

    Each page is periodically checked for access – there is "Accessed" bit in the page descriptor. If a page is not being accessed, it's moved to a candidate list for discarding. Suppose you have a bunch of functions in your app you're not using. Their pages will get discarded. There is no point in having the pager bother about the section attributes, when the existing mechanism of page aging works well enough.

    Even if one app just started and doesn't need the "discardable" section anymore, another app may be starting shortly and need these pages.

    One enhancement Windows may need is to have /SWAPRUN:ALWAYS feature for the executables, which would allow them to delete/update themselves easily. It could be made as when both SWAPRUN:CD and SWAPRUN:NET flags are set, it would be interpreted as SWAPRUN:ALWAYS.

  6. @alger1:

    The thing is, if you have a bunch of functions in a page but that page has a small function that gets used frequently, then obviously that page will never get discarded. So while it may be seen as usesless to mark some code as discardable, it should naturally segregate this code into its own section and annotate it at the same time.

    Of course, it is all pointless to an extent, but a section which is only accessed once and isn't modified should be pretty high on the pagers list of pages to remove from memory.

    As for "Even if one app just started and doesn't need the "discardable" section anymore, another app may be starting shortly and need these pages.", it is trivial to reload the pages, so even if you put the uncertenty factor into play, isn't that the same problem that the pager has for all code/data? How many times have you seen a system thrash like crazy because the way it is using memory is having something paged out just to be paged back in.

  7. L says:

    "If you allocated some zero-initialized memory and never modified it, then the memory manager can just throw the data away because it is very easy to "recover" a page full of zeroes."

    So aside from being a good programming practice, zeroing buffers could actually help program performance?

    [No, it makes it worse because you took a clean page and made it dirty (full of zeroes). -Raymond]
  8. "The thing is, if you have a bunch of functions in a page but that page has a small function that gets used frequently, then obviously that page will never get discarded. So while it may be seen as usesless to mark some code as discardable, it should naturally segregate this code into its own section and annotate it at the same time."

    Then the thing to do is make sure that the linker places all the "hot spot" functions in a contiguous block of memory.  It seems like I've read that Microsoft actually does this on some of their products, but I can't remember which ones, when, or what the best technique is for making the linker do this.  There would also be maintenance implications (how to keep all this linking information up-to-date in your source code?)

    If you keep "hot spot" code/data in one place, and then put the rest of the code afterwards, then the pager will naturally do its job and discard the unused code if the memory is needed.  To do this, there's no need to invent new sections in the EXE file, invent new flags in the PE file, or any such nonsense that would require updating the operating system or PE file format.  Just make sure the linker puts all the "hot spot" code at the beginning of the ".text" section, and the rest can come at the end of the ".text" section.  I'm not sure how one might make the linker do that though, but that's the best solution I think.

  9. How many times have you seen a system thrash like crazy because the way it is using memory is having something paged out just to be paged back in.

    If you have an XP box around, try copying a few 700MB-sized files from a DVD-R to the hard drive, and then verifying the copy (FC /B). Have fun with insane code thrashing.

  10. I'm not sure how one might make the linker do that though, but that's the best solution I think.

    You can make a file with a list of function names for the linker. See /ORDER option.

  11. voo says:

    This seems like something where PGO should be able to help quite easily – whether it does it is another thing. But that'd be much simpler, more accurate and less prone to getting outdated..

  12. Danny says:

    What happens when you mark a section as DISCARDABLE?

    On a short note – for 90% of you programmers we don't care and for rest 10% we almost don't care half a second later :P

  13. @JamesJohnston:

    Depending on the size of the size of the single use code, even with ordering the functions, you can end up with all of the rarely used functions on the same page as a frequently used function. Even if you use /ORDER, you can't guarantee much at all. For example, you can't control static functions and you can't pad to page boundries, so you can't guarantee that all of the rarely used code will go onto the same page. /ORDER is really meant for increasing the probability that the code you want is in memory already, not allowing you to page out single use functions.

    Also remember, discardable and sections are already in the PE file format, so you aren't adding anything at all. You are just using what already exists in the format.

  14. Myria says:

    Microsoft has some sort of proprietary profile-guided optimization they use for their own code that is the standard PGO on crack.  Their secret profiling tool takes in data showing which code blocks in an executable execute the most often, and rearranges the code so that infrequently-executed code blocks reside on separate pages than the normal path.

    For example, code like if (unlikely_error_condition) { … } will become a conditional jump to the … code, and the … code will be moved far away from the rest of the function.  This way, the working set of the process is smaller, reducing the likelihood of swapping.

  15. Cheong says:

    I think .NET code always automatically zeroing any buffers being "new-ed". Does that mean the memory being controlled by GC is always dirty (if never been paged out) from the system's perspective?

  16. Matt says:

    @cheong: Yes. But then most allocations you get from the heap are made dirty because HeapAlloc puts stuff on the page. The memory that is "clean" is memory that you've mapped from a file, module or VirtualAlloc and haven't touched since. Heap allocations are almost always dirty, regardless of whether you zero them out or not.

  17. @cheong00 says:

    Yes. Newly committed pages are provided by the OS with all zeros in them (it can't give you pages with content, this could reveal very sensitive informations, from other processes or from the OS). Only if the process never write to such a page, not even zeros, the OS can be sure that it still contains only zero bytes.

    The .NET memory manager for sure does not track which sub-part of its memory arenas are still in that initial stage. It fills the heaps until there is no more free space, then triggers a GC to compact the used parts to the start of the heap. Its complicated enough so it should not care about the corner case of a page allocated but never touched.

  18. Nitpicker of the day :) says:

    > I think .NET code always automatically zeroing any buffers being "new-ed".

    Technically I think the specifications say memory will be initialized before managed code is run, not that it will always be zero-ed.

    This means that if you have a struct like:

    struct S

    {

     int a;

     int b = 1;

    }

    The CLR is free to zero memory and write the 1 later or it could write 00 00 00 00 01 00 00 00 directly, don't know.

  19. asdbsd says:

    So in theory, I can allocate a non-zeroed page, check it's contents, then do nothing until it gets paged out (and since it was non-dirty, trashed), and then access the page again and it's going to have a different random data in it?

  20. dave says:

    So aside from being a good programming practice,

    Not where I come from. In fact, the reverse. I fail you on code review if you pull stupid stuff like this:

       char buff[100];

       memset(buff, 0, sizeof buff); // <<< pointless waste of cpu cycles

       snprintf(buff, sizeof buff, …..);

  21. Matt says:

    @dave: I hope the reason you failed them on the codereview is because snprintf is an SDL banned function, rather than because of your premature optimisation relating to memset and CPU cycles (if a memset of your stack is the bottleneck in your program, something else has gone wrong).

  22. @asdbsd says:

    No. You can't allocate a non-zeroed or mapped page from the kernel. It's true that when you allocate a zeroed page, check that it's full of zeros and come back to it later that you might be seeing DIFFERENT zeroes, but that's not going to affect your app.

  23. dave says:

    @Matt

    snprintf is perfectly well-behaved. It never writes beyond end of buffer and it always zero-terminates the result (I agree my example code should have checked the return value).  

    See the C99 specification.

    Perhaps you're getting confused with the ill-designed Windows equivalent which does not guarantee termination?

    I'm programming in standard C rather than a vendor-specific language, apologies for not saying so.

    As far as whether it's a bottleneck: well, probably not, though reflexive zeroing every single buffer will likely be measurable. But really I want programmers to not write lines of code that have no good reason for existing. I want them to think.

  24. asdbsd says:

    @dave: Mind is a resource. There's only so much of thinking that can be done until you're tired. What's the point of making someone's life miserable and forcing them to think on stuff which doesn't matter?

    It's enough that they understand that maybe in some cases zeroing isn't needed, but it hurts nobody. Knowing that, they can decide to just zero out everything and pay their attention to something important instead. That is the reason for those lines of code.

  25. dave says:

    We're getting OT here, but I claim that mindless programming, as exemplified by knee-jerk coding of function calls that you don't need to code, is a major source of software problems. My aim is not to make life miserable for anyone, but to build competent and professional programmers.

  26. Matt says:

    @dave:

    Just as a case-in-point, Microsoft have had tons of critical security updates by failing to zero stack buffers in the past, and accidentally having stack-garbage treated as valid (http://www.iss.net/…/HTML_IE_Uninitialized_Memory_Corruption.htm)

    I am not aware of any security vulnerability being caused by excessive initialization of variables. Indeed, if it's obviously superflous, the compiler will remove the memset (since memset is a compiler intrinsic for Visual Studio and why SecureZeroMemory had to be introduced).

    In your example, snprintf on its own isn't equivalent because the bytes after the nul-terminated string are still not zero – so if you send buff to someone across a trust-boundary then removing the memset might just have leaked a whole load of data (like ASLR addresses).

  27. asdbsd says:

    @dave: What exactly is "competent" about not knowing where you can safely hand-wave it away, and wasting your brain's resources on thinking about stuff which doesn't matter? That's incompetent in my book. Mindless thinking about everything :) True professional spends all resources, even his own attention and brainpower, wisely.

  28. @alegr1: "You can make a file with a list of function names for the linker. See /ORDER option."

    From the docs: "Ordering allows you to optimize your program's paging behavior through swap tuning by grouping a function with the functions it calls. You can also group frequently called functions together. These techniques increase the probability that a called function is in memory when it is needed and will not have to be paged from disk."

    Well, that's exactly what I'm suggesting then.  The next question obviously is going to be how do you systematically keep the list up-to-date from a maintenance standpoint; what procedures should be followed?

    @voo: "This seems like something where PGO should be able to help quite easily – whether it does it is another thing."

    That's kind of what I was thinking.  I've never really looked into PGO very much though so I don't know if it does that.  The one time I experimented with it produced a very very modest performance gain, at the cost of greatly increased hassle in the build process.  Did not seem worth it.

    @Crescens2k:  "you can end up with all of the rarely used functions on the same page as a frequently used function"

    If you only have one small frequently used function and the rarely used functions happen to be in the same page, then what does it matter?  The memory system works in integer multiples of pages, not fractions of a page.  Your choices are to put rarely used functions in that space, or null / undefined data.

    "increasing the probability that the code you want is in memory already, not allowing you to page out single use functions"

    Then logically wouldn't that increase the probability that the code you don't need might be paged out?

    @dave: "I fail you on code review if you pull stupid stuff like this"

    Unless this is some very hot performance-critical piece of code… why?!?  How will zeroing 100 bytes hurt anybody?  I do this all the time.  It is far easier to rule out undefined behavior at a glance when reading code by just making every variable set to zero, rather than trying to understand the actual workings of the code.  Only when proven to be a bottleneck would I actually look at removing it.  99% of code in most products is not going to be a bottleneck.  Profilers are your friend – use them!

  29. @JamesJohnston: I would fail you for blindly using a zero there instead of some other value. If I initialize a string buffer with all zeros, and am writing some custom string processing function, how do I know my function is properly terminating the string?

    I believe that variables should be initialized with data that will maximize chances of discovering bugs and vulnerabilities. Because if you don't discover them, then someone else will.

    Personally, if I had to memset a string buffer, the value 0xff would be my choice.

  30. Gabe says:

    Brian EE: Defense in depth — by initializing your string to all-0s, you avoid a buffer overflow in the case of certain failures to properly terminate. Maybe you should have a standard initialization byte. You can set it to 0xff for debugging and leave it at 0x00 for all other times.

  31. @BrianEE says:

    "If you only have one small frequently used function and the rarely used functions happen to be in the same page, then what does it matter?"

    It's more efficient to group your frequently used functions on the same page, because then those pages will have a high usage and won't ever get pushed to the pagefile. Other, less important functions might suffer pagein hits, but since those are outside of your performance critical paths, you take a slight speed improvement.

    This happens because when everything gets paged out (e.g. during a hibernate, during a long pause or context switch or during memory pressure) a fault in one high-usage function automatically pages in other high-usage functions so you don't take the hit twice or three times when calling into them shortly later.

Comments are closed.