9

Assume using a recent version of Visual Studio and C# on an x64 Windows box and allocating a significant amount of data.

Sure enough, when compiling using the default build settings (pictured below for VS 2019 Preview 2.1), you'll run out of user virtual address space when your process hits 4 GB. This is to be expected and the reason is discussed here.

enter image description here

The allocation itself can be done for example by creating a few hundred simple arrays, each containing several million int elements.

What I'd like to understand is why the Any CPU/Prefer 32-bit was chosen as the default build option. I've also noticed that VS 2015 has the same default setting too, and most likely every version that came out since VS 11, as described here.

The question usually asked is "What is AnyCPU...?" and it has been answered repeatedly (1 2 3 4 5), briefly touching on the advantages of targeting x86 / x64 / Any CPU + Prefer 32-bit. But I haven't found a definite answer to why was Any CPU + Prefer 32-bit chosen as the default setting in VS.

Going through the reasons stated against building for x64 by default:

  • The x64 process will use more memory: for the simple example described above (arrays of arrays of int) this shouldn't really be the case. Sure, the reference to the array itself is going to be double (8 bytes instead of 4), but that's about it. As per the "Windows Internals" book (Memory Management chapter), the PFN entries in the page table structures themselves are 64 bits wide on both x86 and x64 architectures, it's only that there are 3 (for x86) vs 4 (for x64) of levels of tables for resolving the virtual addresses to physical ones. As for the data referenced, it's the same size (4 bytes per int value). So allocating 20 arrays of 10 million int will roughly translate to 800 MB used for both architectures on the managed heap. Actually, the overall committed size on the x64 version of the simple example just described was about the same as the x86 one when tested (comparison follows, x64 on top, x86 below; and ignoring the 4 GB chunk that's simply in a reserved state for the x64 version). Interestingly enough, while running the 32 bit process on x64, each thread within the process will end up with 2 stacks, a 32-bit wow64 one and a 64-bit one, thus resulting in a higher memory consumption from the perspective of the stack. enter image description here
  • Portability across platforms: the answer here (careful, it's before the Prefer 32-bit option was invented) provides a link to a MS recommendation (article updated as of 2017 at the time of this writing). The referenced article also displays a compatibility matrix, but that's just for UWP. Specifically about ARM architecture being an out-of-the-box target for an output of a build using AnyCPU/Prefer 32-bit, this is supported with a nice table by this answer. This article is also quite referenced, showing the changes brought by .NET 4.5 and the impact on ARM.
  • Incompatibility with existing 32-bit apps: someone comments here about Office being an issue since it's usually installed as 32-bit. Not much further info though.
  • Loaded assemblies: A BadImageFormatException is thrown when trying to load an x86 assembly inside a x64 process or the other way around. However, within the comments to this comment it's stated that an assembly compiled with Any CPU/Prefer 32-bit can be loaded in a 64-bit process. I haven't been able to find an official article supporting this though later edit: It can be loaded just fine; I've detailed this and all possible tests for assembly loading here

In the end the default Any CPU/Prefer 32-bit setting is most likely a tradeoff, and somehow large (>4 GB) memory access was sacrificed for something else deemed more important.

The limit though for a x64 process user-mode virtual address space on Win10 is 128 TB and 4 GB of physical RAM come standard in today's entry-level laptops, thus potentially losing the advantage of all that extra RAM (the maximum physical memory limits for Windows versions are here).

Mihai Albert
  • 1,288
  • 1
  • 12
  • 27
  • 1
    Initially it was there for Windows RT, a failed Windows release for mobile devices, and left untouched for backward compatibility (outsider observation). – Lex Li Feb 10 '19 at 06:31
  • Because default means default desirable behavior. For VS build the default desirable behavior is to get an artifact with as many as possible runnable deployment variations which is covered by this setting. – Dmytro Mukalov Feb 11 '19 at 16:44
  • @DmytroMukalov I'm genuinely interested in what makes this platform setting the one that has the largest "reach". Whenever a decision is made, there are arguments going for and against it. Somehow the "Prefer 32-bit" tick box won, based on several arguments going for it. All I'd like to know is what they were. – Mihai Albert Mar 19 '19 at 20:04
  • Maybe it was something like "there are x% assemblies out there in the wild that have been built for 32-bit (where x>90)", so it would make sense to have the "Prefer 32-bit" as the default, since as long as you're building an app that will consume this kind of assemblies, you'll need that "Prefer 32-bit" against your process if compiling with AnyCPU. I don't believe the people that make VS simply kept the same setting as in the previous version, along the lines "let's better not touch this" – Mihai Albert Mar 19 '19 at 20:05
  • All I know for certain is that my current knowledge about .NET is limited, and any insight will help me on my quest to get better – Mihai Albert Mar 19 '19 at 20:05
  • 2
    It is mostly an historical accident, the debugger used to work better in a 32-bit process. Fixing it was a big project, they had to rewrite the x64 jitter to get rid of the quirks. That was done, but without changing the project templates. Not so easy to do, far too many programmers are used to the default settings. They, say, won't realize their beefy dev machine isn't a good match for the user's cranky old one. – Hans Passant Mar 20 '19 at 00:02
  • In these terms: there are N 32-bit platforms and M 64-bit platforms, 32 bit tick addresses N+M which is greater than M. – Dmytro Mukalov Mar 20 '19 at 05:18
  • @DmytroMukalov For a host process that's not consuming anything else but the builtin .Net assemblies, using only AnyCPU as target platform would also reach the same N+M platforms; it would also fare better on the M ones, since it'll see a far larger user address space on x64. If the resulting assembly will consume other foreign assemblies, or is to be consumed, then we're back at the "90% of the assemblies in the wild are 32-bit" discussion, which is not that clear-cut. – Mihai Albert Mar 20 '19 at 08:32

0 Answers0