Friday, August 22, 2014

Out of memory? Easy ways to increase the memory available to your program

When you run your VB or C# application, you might get an OutOfMemoryException thrown, even if your machine has lots of  memory.

Every 32 bit process has a 2^32 bit (4 Gig) address space. That means every pointer has a size of 32 bits (4 bytes) and thus is limited to 4 Billion.

That’s the equivalent of saying a vehicle license plate number consists of 6 digits and thus there are 1 million possible numbers.

That 4 Gigs is divided into half: the user application gets the lower half and the OS gets the upper. (This boundary can be changed: see below).

Start VS 2010. File->New->Project->VB or C# Windows WPF Application.
Paste the VB or C# code below. It creates a heap then allocates 100Meg of memory in a loop continuously until an exception is thrown.

On my 64 bit Windows 7 machine with 8 Gigs of RAM (your digital camera or phone might have  more memory!), I get about 1.4Gig allocated before it dies.

Iter #10 1,048,576,000
Iter #11 1,153,433,600
Iter #12 1,258,291,200
Iter #13 1,363,148,800
Exception Exception of type 'System.OutOfMemoryException' was thrown.


Now choose Project->Properties->Compile->Build Events->PostBuildEvent Command and  added these 2 lines
call "$(DevEnvDir)..\..\vc\vcvarsall.bat" x86
"$(DevEnvDir)..\..\vc\bin\EditBin.exe" "$(TargetPath)"  /LARGEADDRESSAWARE

Note: the positions of the quotes are critical
The first line calls a BAT file that makes various tools available on the path.
The second runs EditBin on the target binary, using the LARGEADDRESSAWARE flag (that’s almost all left hand keys on the keyboard!)

The only effect of these 2 lines is to call EditBin to toggle a bit in the EXE. When the EXE starts a process, that entire process is flagged as able to work with pointers above 2G.
With such pointers, the high bit is a 1, which, in 2’s complement notation, is a negative number, and some applications may not be designed to work with “negative” pointers.

Now when I run the code I get 3.5 Gigs: More than twice as much memory!

Iter #30 3,145,728,000
Iter #31 3,250,585,600
Iter #32 3,355,443,200
Iter #33 3,460,300,800
Exception Exception of type 'System.OutOfMemoryException' was thrown.


This Editbin “trick” works fine on a 64bit OS. For a 32bit OS it works too, but you need to "bcdedit /set IncreaseUserVA 3072" (reboot) and you won’t get as much extra memory.

Want even more memory? If you’re on a 64 bit OS, try compiling to 64 bit:
VB: Project->Properties->Compile->Advanced->Target CPU->Any CPU (or x64)
C#: Project->Properies->Build->Platform Target->Any CPU (or x64)

Iter #110 11,534,336,000
Iter #111 11,639,193,600
Iter #112 11,744,051,200
Iter #113 11,848,908,800
Iter #114 11,953,766,400
Exception Exception of type 'System.OutOfMemoryException' was thrown.

Yes, that really is almost 12 gigs on my 8 gig machine!  You can also verify via Task Manager.

A 64 bit process has a pointer of size 64 bits (8 bytes). 2 ^ 64= 18,000,000,000,000,000,000. (1.8 x 10^19)
That’s about 18 exabytes, or 900,000 years of DVD quality video.

Still that’s nowhere near the number of possible chess games or even the waaaay smaller number of electrons that will fit in the universe J(Hubble's law, big bang physics)

When I first worked for Microsoft, I developed a thunking mechanism to allow 32 bit applications to call 16 bit DLLs. (http://support.microsoft.com/kb/139070 )
We thought there’s no way anybody could run out of 32 bit address space!

If a license plate number were to double its length from 6 to 12 digits, that would go from 1 Million (population of a medium sized city) to 1Trillion (1x 10^12) , 200 times the world population.


See also

<C# Code>
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;
using System.Runtime.InteropServices;
using System.Diagnostics;

namespace Heapcs
{
    /// <summary>
    /// Interaction logic for MainWindow.xaml
    /// </summary>
    public partial class MainWindow : Window
    {
        public MainWindow()
        {
            InitializeComponent();
        }
        private void Window_Loaded(object sender, RoutedEventArgs e)
        {
            var hHeap = Heap.HeapCreate(Heap.HeapFlags.HEAP_GENERATE_EXCEPTIONS, 0, 0);
            // if the FriendlyName is "heap.vshost.exe" then it's using the VS Hosting Process and not "Heap.Exe"
            Trace.WriteLine(AppDomain.CurrentDomain.FriendlyName + " heap created");
            uint nSize = 100 * 1024 * 1024;
            ulong nTot = 0;
            try
            {
                for (int i = 0; i < 1000; i++)
                {
                    var ptr = Heap.HeapAlloc(hHeap, 0, nSize);
                    nTot += nSize;
                    Trace.WriteLine(String.Format("Iter #{0} {1:n0} ", i, nTot));
                }
            }
            catch (Exception ex)
            {
                Trace.WriteLine("Exception " + ex.Message);
            }


            Heap.HeapDestroy(hHeap);
            Trace.WriteLine("destroyed");
            Application.Current.Shutdown();
        }
    }

    public class Heap
    {
        [DllImport("kernel32.dll", SetLastError = true)]
        public static extern IntPtr HeapCreate(HeapFlags flOptions, uintdwInitialsize, uint dwMaximumSize);

        [DllImport("kernel32.dll", SetLastError = true)]
        public static extern IntPtr HeapAlloc(IntPtr hHeap, HeapFlags dwFlags, uintdwSize);

        [DllImport("kernel32.dll", SetLastError = true)]
        public static extern bool HeapFree(IntPtr hHeap, HeapFlags dwFlags, IntPtrlpMem);

        [DllImport("kernel32.dll", SetLastError = true)]
        public static extern bool HeapDestroy(IntPtr hHeap);

        [DllImport("kernel32.dll", SetLastError = true)]
        public static extern IntPtr GetProcessHeap();

        [Flags()]
        public enum HeapFlags
        {
            HEAP_NO_SERIALIZE = 0x1,
            HEAP_GENERATE_EXCEPTIONS = 0x4,
            HEAP_ZERO_MEMORY = 0x8
        }

    }

}


</C# Code>
<VB Code>
Option Strict On
Imports System.Runtime.InteropServices


Class MainWindow
    Private Sub Window_Loaded(ByVal sender As System.ObjectByVal e AsSystem.Windows.RoutedEventArgsHandles MyBase.Loaded
        Dim hHeap = Heap.HeapCreate(Heap.HeapFlags.HEAP_GENERATE_EXCEPTIONS, 0, 0)
        ' if the FriendlyName is "heap.vshost.exe" then it's using the VS Hosting Process and not "Heap.Exe"
        Trace.WriteLine(AppDomain.CurrentDomain.FriendlyName + " heap created")
        Dim nSize As UInteger = 100 * 1024 * 1024
        Dim nTot As ULong = 0
        Try
            For i = 1 To 1000

                Dim ptr = Heap.HeapAlloc(hHeap, 0, nSize)
                nTot += nSize
                Trace.WriteLine(String.Format("Iter #{0} {1:n0} ", i, nTot))
            Next
        Catch ex As Exception
            Trace.WriteLine("Exception " + ex.Message)
        End Try

        Heap.HeapDestroy(hHeap)
        Trace.WriteLine("destroyed")
        End
    End Sub
End Class

Public Class Heap

    <DllImport("kernel32.dll", SetLastError:=True)> _
    Public Shared Function HeapCreate(
               ByVal flOptions As HeapFlags,
               ByVal dwInitialSize As UInteger,
               ByVal dwMaximumSize As UInteger
         ) As IntPtr
    End Function

    <DllImport("kernel32.dll", SetLastError:=True)>
    Public Shared Function HeapAlloc(
               ByVal hHeap As IntPtr,
               ByVal dwFlags As HeapFlags,
               ByVal dwSize As UInteger
         ) As IntPtr
    End Function
    <DllImport("kernel32.dll", SetLastError:=True)>
    Public Shared Function HeapFree(
               ByVal hHeap As IntPtr,
               ByVal dwFlags As HeapFlags,
               ByVal lpMem As IntPtr
         ) As Boolean
    End Function

    <DllImport("kernel32.dll", SetLastError:=True)>
    Public Shared Function HeapDestroy(
               ByVal hHeap As IntPtr
         ) As Boolean
    End Function

    <DllImport("kernel32.dll", SetLastError:=True)> _
    Public Shared Function GetProcessHeap(
         ) As IntPtr
    End Function
    <Flags()>
    Public Enum HeapFlags
        HEAP_NO_SERIALIZE = &H1
        HEAP_GENERATE_EXCEPTIONS = &H4
        HEAP_ZERO_MEMORY = &H8
    End Enum
End Class


</VB Code>



ref: http://blogs.msdn.com/b/calvin_hsia/archive/2010/09/27/10068359.aspx


No comments:

Post a Comment