2
$\begingroup$

The GetProcessMemoryInfo from psapi.dll is a helpful function that can be used to monitor the memory wolframkernel.exe used, where how much memory can be used and is actually used by wolframkernel is an important factor which seems silently determines whether symbolic process will be utilized when numerical methods is not work or work well (this "intelligent" decision will sometimes crash the computer due to too heavy use of the resources), but I cannot make it work using NETLink. Any help for this will be very much appreciated. The following is one version that I have tried:

    5/31/20 18:58:56 In[738]:= Needs["NETLink`"]

    5/31/20 18:59:02 In[739]:= ReinstallNET[]

    5/31/20 18:59:04 Out[739]= LinkObject["F:\\work\\Frank\\WolframWorkspaces\\Mathematica12.0\\SystemFiles\\\
    Links\\NETLink\\InstallableNET.exe", 66545, 18]

    5/31/20 18:59:32 In[740]:= GetCurrentProcess = 
     DefineDLLFunction["GetCurrentProcess", 
      "C:\\Windows\\System32\\kernel32.dll", "IntPtr", {}]

    5/31/20 18:59:32 Out[740]= Function[Null, 
     If[NETLink`DLL`Private`checkArgCount["GetCurrentProcess", {##1}, 0], 
      Wolfram`NETLink`DynamicDLLNamespace`DLLWrapper1`GetCurrentProcess[##1], \
    $Failed], {HoldAll}]

    5/31/20 18:59:37 In[741]:= GetLastError = 
     DefineDLLFunction["GetLastError", "C:\\Windows\\System32\\kernel32.dll", 
      "DWORD", {}]

    5/31/20 18:59:37 Out[741]= Function[Null, 
     If[NETLink`DLL`Private`checkArgCount["GetLastError", {##1}, 0], 
      Wolfram`NETLink`DynamicDLLNamespace`DLLWrapper2`GetLastError[##1], \
    $Failed], {HoldAll}]

    5/31/20 19:10:01 In[747]:= getProcessMemoryInfo6 = DefineDLLFunction["[StructLayout(LayoutKind.Sequential,
      CharSet = CharSet.Auto)]
      public class PROCESSMEMORYCOUNTERS
      {
          public uint cb;
          public UInt64 PageFaultCount;
          public UInt64 PeakWorkingSetSize;
          public UInt64 WorkingSetSize;
          public UInt64 QuotaPeakPagedPoolUsage;
          public UInt64 QuotaPagedPoolUsage;
          public UInt64 QuotaPeakNonPagedPoolUsage;
          public UInt64 QuotaNonPagedPoolUsage;
          public UInt64 PagefileUsage;
          public UInt64 PeakPagefileUsage;
        public PROCESSMEMORYCOUNTERS()
        {
            this.cb = (uint)
            Marshal.SizeOf(typeof( PROCESSMEMORYCOUNTERS ));
        }
      }

      [return: MarshalAs(UnmanagedType.Bool)]
      [DllImport(\"psapi.dll\",
      CharSet = CharSet.Auto, SetLastError = true)]
      public static extern bool
      GetProcessMemoryInfo(IntPtr hProcess, out PROCESSMEMORYCOUNTERS counters, \
    uint size);"]

    5/31/20 19:10:03 Out[747]= Function[Null, 
     If[NETLink`DLL`Private`checkArgCount["GetProcessMemoryInfo", {##1}, 3], 
      Wolfram`NETLink`DynamicDLLNamespace`DLLWrapper5`GetProcessMemoryInfo[##1], \
    $Failed], {HoldAll}]

    5/31/20 19:10:16 In[748]:= struct8 = NETNew[
      "Wolfram.NETLink.DynamicDLLNamespace.DLLWrapper5+PROCESSMEMORYCOUNTERS"]

    5/31/20 19:10:16 Out[748]= NETLink`Objects`NETObject$854307884761089

    5/31/20 19:10:21 In[749]:= struct8@cb

    5/31/20 19:10:21 Out[749]= 80

    5/31/20 19:10:36 In[750]:= getProcessMemoryInfo6[GetCurrentProcess[], struct8, struct8@cb]

    5/31/20 19:10:44 During evaluation of In[750]:= LinkObject::linkd: Unable to communicate with closed link LinkObject[F:\work\Frank\WolframWorkspaces\Mathematica12.0\SystemFiles\Links\NETLink\InstallableNET.exe,66545,18].

    5/31/20 19:10:44 Out[750]= $Failed
$\endgroup$
3
  • 1
    $\begingroup$ “ which seems silently determines whether symbolic process will be utilized when numerical methods is not work or work well” - I don’t think I’m familiar with that. Do you have a reference for that behavior where I can read up more on it. $\endgroup$
    – MarcoB
    May 31, 2020 at 23:52
  • $\begingroup$ With an expression involving Hypergeometric2F1 function, for some parameters values, if the private byte the kernel allowed to use is less than some threshold, it will do numerical calculation nicely and quickly, but without this limit, it will use all the physical memory up, run long or forever returning failed. $\endgroup$
    – Frank
    Jun 1, 2020 at 12:30
  • $\begingroup$ Related: mathematica.stackexchange.com/q/164/280 $\endgroup$ Jun 1, 2020 at 14:06

2 Answers 2

1
$\begingroup$

Rather than meddle with NETLink or C# that are focusing on or struggling with "trivial" things such as structure definition and type conversion, a succinct C++ solution processjobmemlimit.cpp revised from the following links:

https://www.installsetupconfig.com/win32programming/processstatushelperapis10_4.html https://cpp.hotexamples.com/examples/-/-/CreateJobObject/cpp-createjobobject-function-examples.html#0xc489e4f04d4ed6153d23ba6b003eb830af0ad00da050e9e2fbd570befcf8de2d-60,,82,

is as follows:

    #include <windows.h>
    #include <stdio.h>
    #include <wchar.h>
    #include <psapi.h>
    #include <locale>
    #include <iostream>
    #include <memory>

    struct separate_thousands : std::numpunct<char> {
        char_type do_thousands_sep() const override { return ','; }  // separate with commas
        string_type do_grouping() const override { return "\3"; } // groups of 3 digit
    };

    // Link to Psapi.lib
    #pragma comment(lib, "C:\\Program Files (x86)\\Microsoft SDKs\\Windows\\v7.1A\\Lib\\x64\\Psapi.Lib")

    using namespace std;

    void limitMem( DWORD processID, size_t limit, int wait )
    {
        static HANDLE hJob = NULL;
        JOBOBJECT_EXTENDED_LIMIT_INFORMATION jobInfo;

        jobInfo.BasicLimitInformation.LimitFlags = JOB_OBJECT_LIMIT_PROCESS_MEMORY | JOB_OBJECT_LIMIT_WORKINGSET;
        jobInfo.BasicLimitInformation.MinimumWorkingSetSize = 204800;
        jobInfo.BasicLimitInformation.MaximumWorkingSetSize = limit? limit*1024*1024 : 2*1024*1024*1024;
        jobInfo.ProcessMemoryLimit = limit? limit*1024*1024 : 2*1024*1024*1024;
        if (NULL == hJob) {
            if (NULL == (hJob = CreateJobObject(NULL, NULL))) {
                printf("Can't assign create job object: %ld\n", GetLastError());
                exit(1);
            }
            if (0 == AssignProcessToJobObject(hJob, OpenProcess(PROCESS_SET_QUOTA | PROCESS_TERMINATE, 1, processID))) {
                printf("Can't assign process to job object: %ld\n", GetLastError());
                exit(1);
            }
        }
        if (0 == SetInformationJobObject(hJob, JobObjectExtendedLimitInformation,
                                         &jobInfo, sizeof(jobInfo))) {
            printf("Can't set limits: %ld\n", GetLastError());
            exit(1);
        }
        if(wait)_getwch();
    }

    void PrintMemoryInfo(DWORD processID)
    {
        HANDLE hProcess;
        PROCESS_MEMORY_COUNTERS pmc;

        // Print the process identifier.
        wprintf(L"\nProcess ID: %d\n", processID );

        // Print information about the memory usage of the process.
        hProcess = OpenProcess(  PROCESS_QUERY_INFORMATION | PROCESS_VM_READ,FALSE, processID );

        if (hProcess == NULL)
        {
            wprintf(L"OpenProcess() failed! Error %d\n", GetLastError());
            return;
        }

        if (GetProcessMemoryInfo(hProcess, &pmc, sizeof(pmc)))
        {
            auto thousands = std::unique_ptr<separate_thousands>(new separate_thousands());
            std::cout.imbue(std::locale(std::cout.getloc(), thousands.release()));
            std::cout << "\tPageFaultCount:\t" << pmc.PageFaultCount << '\n';
            std::cout << "\tPeakWorkingSetSize:\t" << pmc.PeakWorkingSetSize << '\n';
            std::cout << "\tWorkingSetSize:\t" << pmc.WorkingSetSize << '\n';
            std::cout << "\tQuotaPeakPagedPoolUsage:\t" << pmc.QuotaPeakPagedPoolUsage << '\n';
            std::cout << "\tQuotaPagedPoolUsage:\t" << pmc.QuotaPagedPoolUsage << '\n';
            std::cout << "\tQuotaPeakNonPagedPoolUsage:\t" << pmc.QuotaPeakNonPagedPoolUsage << '\n';
            std::cout << "\tQuotaNonPagedPoolUsage:\t" << pmc.QuotaNonPagedPoolUsage << '\n';
            std::cout << "\tPagefileUsage:\t" << pmc.PagefileUsage << '\n';
            std::cout << "\tPeakPagefileUsage:\t" << pmc.PeakPagefileUsage << '\n';
        }
        CloseHandle(hProcess);
    }


    int main(int argc, char* argv[])
    {
        if(argc < 2)
        {
            cout << "proper usage: " << argv[0] << " <Pid> <PBWSinMB>  <wait>" << endl
                << "where the first argument are necessary and is the processid we want to set private bytes and working set limit on" << endl
                << "the second arg is that limit in MB default 2000 MB, the third arg is a wait flag waiting for a key stroke to exit default not 

    wait" << endl;
            exit(-1);
        }

        int Pid = atoi(argv[1]);
        int limit =  (argc>2) ? atoi(argv[2]) : 2000; //default 2000 MB = 2 GB
        int wait = (argc>3) ? atoi(argv[3]) : 0; //default not wait

        PrintMemoryInfo(Pid);
        limitMem(Pid, limit, wait);
        PrintMemoryInfo(Pid);

        return 0;
    }

this code can be compiled by:

cl /O2 /EHsc /Fox64\Release\ /Fex64\Release\ processjobmemlimit.cpp

and called inside Mathematica by

RunProcess[{"F:\\work\\Frank\\test\\x64\\Release\\processjobmemlimit.exe", $ProcessID}, "StandardOutput"]

that can be insert into the beginning of the work load of wolfram sub-kernel to limit the private bytes and work set those kernels can utilize. While work set limit will prevent exorbitant physical memory usage, only private byte limit will prevent symbolic calculation/long-time-running that are unresponsive to AbortKernels[] or

Parallel`Developer`Send[#,Quit[]]&/@Parallel`Protected`$sortedkernels.

Multiple call of it will only honor the minimum of the max memory limit.

$\endgroup$
0
$\begingroup$

If C# or powershell script is prefered over calling external executable, we will face the problem that Mathematica almost surelly cannot pass escaped double quote to powershell. Even base64 encoding would not work since Mathematica have many encodings but not Unicode, exactly the one that are needed by powershell script. Now that an internal calling is impossible, a standalone monitor from powershell console is as follows:

    Do {
    Get-CimInstance Win32_Process -Filter "Name='WolframKernel.exe' and Priority=6" | foreach {
    $title=$_.ProcessId;
    If($_.PageFileUsage -gt 2000000){
    Stop-Process $_.Handle -Force
    }
    If( (Get-Process | Where-Object { $_.MainWindowTitle -eq "$title" }).Id -eq $null){
    start-process cmd -Argument "/c start ""$title"" /MIN F:\\work\\Frank\\test\\x64\\Release\\processjobmemlimit.exe $($title) 2000 1" 
    }
    }
    Start-Sleep -Seconds 60
    } Until ( $Host.UI.RawUI.KeyAvailable -and ($Host.UI.RawUI.ReadKey("IncludeKeyUp,NoEcho").VirtualKeyCode -eq 27 ) )

where wolfram subkernels default have priority 6 and we kill all newly spawn subkernel out of not so rare kernel crash that surpassed the 2G memory constraints and impliment this constraints if not already committed.

The procgov.exe from https://github.com/lowleveldesign/process-governor "F:\\work\\Frank\\tools\\procgov.exe -m 2G -p $($title)" can also be used here: the only problem with it is its --maxws option is not working. To use procgov.exe in the monitor, we can combine this with an internal call to set working set limit in each of the subkernels:

    Needs["NETLink`"]
    InstallNET[]
    OpenProcess=DefineDLLFunction["OpenProcess", "C:\\Windows\\System32\\kernel32.dll", "Int64", {"Int64", "Int64", "Int64"}]
    SetProcessWorkingSetSizeEx=DefineDLLFunction["SetProcessWorkingSetSizeEx", "C:\\Windows\\System32\\kernel32.dll", "BOOL", {"Int64", "Int64", "Int64", "Int64"}]
    SetProcessWorkingSetSizeEx[OpenProcess[256,1,$ProcessID],204800, 2048000000, 4]

To verify the limit setted:

    GetProcessWorkingSetSize=DefineDLLFunction["GetProcessWorkingSetSize", "C:\\Windows\\System32\\kernel32.dll", "BOOL", {"Int64", "out Int64", "out Int64"}]
    GetProcessWorkingSetSize[OpenProcess[1024,0,$ProcessID], minws, maxws]
    {minws, maxws}
$\endgroup$
1
  • $\begingroup$ If we do not care about also limit the master wolfram kernel memory, we can run the processjobmemlimit.exe once in the master kernel, then alll sub-kernel spawned latter will inherit these limits. Otherwise, the best way is to use this PowerShell standalone monitor that implemented a simple mechanism to run it only once and is very fast. Calling the external executable from within Mathematica have various problems: the StartProcess or RunProcess is relative fast but is not "reentrant", the ReadList and Import do not have this problem but is very slow. $\endgroup$
    – Frank
    Jun 5, 2020 at 2:42

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.