Fix game stutter on Win 10 1703-1809

Discussion in 'Videocards - NVIDIA GeForce Drivers Section' started by Exostenza, Apr 3, 2018.

  1. Wagnard

    Wagnard Ancient Guru

    Messages:
    2,749
    Likes Received:
    521
    GPU:
    MSI Geforce GTX 1080
    Yup I like this more, thanks for sharing, will test a bit more before releasing it to public.
     
    Exostenza likes this.
  2. mbk1969

    mbk1969 Ancient Guru

    Messages:
    15,646
    Likes Received:
    13,648
    GPU:
    GF RTX 4070
    @Wagnard How do you check the free memory? WMI? I stumbled upon API function GlobalMemoryStatusEx which fills structure MEMORYSTATUSEX with two interesting fields:
    dwMemoryLoad
    A number between 0 and 100 that specifies the approximate percentage of physical memory that is in use (0 indicates no memory use and 100 indicates full memory use).​
    ullAvailPhys
    The amount of physical memory currently available, in bytes. This is the amount of physical memory that can be immediately reused without having to write its contents to disk first. It is the sum of the size of the standby, free, and zero lists.​

    I wrote test console app which purges standby list calling GlobalMemoryStatusEx before and after the purge. That allows to see the achieved gain.
    Code:
    using System;
    using System.Runtime.InteropServices;
    using System.Security.Principal;
    
    
    namespace PurgeMemCache
    {
        class Program
        {
            [System.Security.SuppressUnmanagedCodeSecurity]
            [System.Runtime.ExceptionServices.HandleProcessCorruptedStateExceptions]
            static int Main(string[] args)
            {
                try
                {
                    MEMORYSTATUSEX memStatus1 = new MEMORYSTATUSEX()
                    {
                        dwLength = (uint)Marshal.SizeOf(typeof(MEMORYSTATUSEX))
                    };
                    uint res1 = GlobalMemoryStatusEx(ref memStatus1);
                    if (res1 != 0)
                        Console.WriteLine("Memory before the purge: {0}% used, {1} bytes free", memStatus1.dwMemoryLoad, memStatus1.ullAvailPhys);
    
                    using (var current = WindowsIdentity.GetCurrent(TokenAccessLevels.Query | TokenAccessLevels.AdjustPrivileges))
                    {
                        TokPriv1Luid newst;
                        newst.Count = 1; newst.Luid = 0L; newst.Attr = 2;// SE_PRIVILEGE_ENABLED;
                        if (!LookupPrivilegeValue(null, "SeProfileSingleProcessPrivilege", ref newst.Luid))
                        {
                            Console.Error.Write("Error in LookupPrivilegeValue: {0}", Marshal.GetLastWin32Error());
                            return 1;
                        }
                        if (!AdjustTokenPrivileges(current.Token, false, ref newst, 0, IntPtr.Zero, IntPtr.Zero))
                        {
                            Console.Error.Write("Error in AdjustTokenPrivileges: {0}", Marshal.GetLastWin32Error());
                            return 2;
                        }
    
                        int SystemMemoryListInformation = 0x0050;
                        int MemoryPurgeStandbyList = 4;
                        uint res = NtSetSystemInformation(SystemMemoryListInformation, ref MemoryPurgeStandbyList, Marshal.SizeOf(MemoryPurgeStandbyList));
                        if (res != 0)
                        {
                            Console.Error.Write("Error in NtSetSystemInformation: {0}", Marshal.GetLastWin32Error());
                            return 3;
                        }
                    }
    
                    MEMORYSTATUSEX memStatus2 = new MEMORYSTATUSEX()
                    {
                        dwLength = (uint)Marshal.SizeOf(typeof(MEMORYSTATUSEX))
                    };
                    uint res2 = GlobalMemoryStatusEx(ref memStatus2);
    
                    if (res2 != 0)
                    {
                        Console.WriteLine("Memory after the purge: {0}% used, {1} bytes free", memStatus2.dwMemoryLoad, memStatus2.ullAvailPhys);
    
                        if(res1 != 0)
                            Console.WriteLine("Memory gained by the purge: {0}% , {1} bytes", memStatus1.dwMemoryLoad - memStatus2.dwMemoryLoad, memStatus2.ullAvailPhys - memStatus1.ullAvailPhys);
                    }
    
                    return 0;
                }
                catch(Exception ex)
                {
                    Console.Error.Write("Unexpected error:\n{0}", ex.ToString());
                    return 4;
                }
            }
    
            [DllImport("advapi32.dll", SetLastError = true)]
            static extern bool LookupPrivilegeValue(string host, string name, ref long pluid);
    
            [DllImport("advapi32.dll", SetLastError = true)]
            static extern bool AdjustTokenPrivileges(IntPtr htok, bool disall, ref TokPriv1Luid newst, int len, IntPtr prev, IntPtr relen);
    
            [DllImport("ntdll.dll", SetLastError = true)]
            static extern UInt32 NtSetSystemInformation(int InfoClass, ref int Info, int Length);
    
            [DllImport("Kernel32", SetLastError = true)]
            static extern UInt32 GlobalMemoryStatusEx(ref MEMORYSTATUSEX lpBuffer); 
    
            [StructLayout(LayoutKind.Sequential, Pack = 1)]
            struct TokPriv1Luid
            {
                public int Count;
                public long Luid;
                public int Attr;
            }
    
            [StructLayout(LayoutKind.Sequential, Pack = 1)]
            struct MEMORYSTATUSEX
            {
                public UInt32 dwLength;
                public UInt32 dwMemoryLoad;
                public UInt64 ullTotalPhys;
                public UInt64 ullAvailPhys;
                public UInt64 ullTotalPageFile;
                public UInt64 ullAvailPageFile;
                public UInt64 ullTotalVirtual;
                public UInt64 ullAvailVirtual;
                public UInt64 ullAvailExtendedVirtual;
            }
        }
    }
    

    I compared values of printed ullAvailPhys and they were equal to "Memory\Available bytes" performance counter.

    1. I strongly suspect that using this API function is faster then using WMI.
    2. Using the field dwMemoryLoad allows to target % of free memory treshold instead of targeting free memory treshold in megabytes.
     
  3. Wagnard

    Wagnard Ancient Guru

    Messages:
    2,749
    Likes Received:
    521
    GPU:
    MSI Geforce GTX 1080
    ullAvailPhys - SystemCache (from GetPerformanceInfo)

    SystemCache
    The amount of system cache memory, in pages. This is the size of the standby list plus the system working set

    As you say, WMI is not as fast and I prefer to avoid it as much as possible.
     
    Last edited: Oct 1, 2018
    Exostenza likes this.
  4. mbk1969

    mbk1969 Ancient Guru

    Messages:
    15,646
    Likes Received:
    13,648
    GPU:
    GF RTX 4070
    Yeah, I will change to GetPerformanceInfo too. Have you created two versions of PERFORMANCE_INFORMATION for 32- and 64-bit?
     

  5. Wagnard

    Wagnard Ancient Guru

    Messages:
    2,749
    Likes Received:
    521
    GPU:
    MSI Geforce GTX 1080
    I did not.
     
  6. Wagnard

    Wagnard Ancient Guru

    Messages:
    2,749
    Likes Received:
    521
    GPU:
    MSI Geforce GTX 1080
    I updated my app to 1.0.0.4

    Changes since 1.0.0.3
    - Added mbk1969 safer code for purging the standby list.
    - Added a checkbox to run the application minimized and started.
    - Added a "Purge" button to manually purge the standby list.
    - User settings are now saved.

    Thanks to everyone contribution.
     
    Last edited: Oct 1, 2018
    akbaar, Driller_au, RzrTrek and 3 others like this.
  7. deathvirus

    deathvirus Ancient Guru

    Messages:
    4,507
    Likes Received:
    13
    GPU:
    RTX 2070 MaxQ 8GB
    I can't seem to get the latest download using the link on your website :(
     
  8. Wagnard

    Wagnard Ancient Guru

    Messages:
    2,749
    Likes Received:
    521
    GPU:
    MSI Geforce GTX 1080
    Try to clear your browser cache, I just tried it and it work.
     
    RzrTrek likes this.
  9. deathvirus

    deathvirus Ancient Guru

    Messages:
    4,507
    Likes Received:
    13
    GPU:
    RTX 2070 MaxQ 8GB
  10. deathvirus

    deathvirus Ancient Guru

    Messages:
    4,507
    Likes Received:
    13
    GPU:
    RTX 2070 MaxQ 8GB
    Oh well, popped-up dev tools, disabled cache and got the latest file :D

    now gonna do some testing :rolleyes:
     

  11. mbk1969

    mbk1969 Ancient Guru

    Messages:
    15,646
    Likes Received:
    13,648
    GPU:
    GF RTX 4070
    @Wagnard
    Look what I found in MSDN
    https://msdn.microsoft.com/en-us/library/windows/desktop/aa366541(v=vs.85).aspx

    and in "Windows Internals"
    Memory notification events
    Windows provides a way for user-mode processes and kernel-mode drivers to be notified when physical memory, paged pool, non-paged pool, and commit charge are low and/or plentiful. This information can be used to determine memory usage as appropriate. For example, if available memory is low, the application can reduce memory consumption. If available paged pool is high, the driver can allocate more memory. Finally, the memory manager also provides an event that permits notification when corrupted pages have been detected.
    User-mode processes can be notified only of low or high memory conditions. An application can call the CreateMemoryResourceNotification function, specifying whether low or high memory notification is desired. The returned handle can be provided to any of the wait functions. When memory is low (or high), the wait completes, thus notifying the thread of the condition. Alternatively, the QueryMemoryResourceNotification can be used to query the system memory condition at any time without blocking the calling thread.

    You can override the high and low memory values by adding the LowMemoryThreshold or HighMemoryThreshold DWORD registry value under HKLM\SYSTEM\CurrentControlSet\Session Manager\Memory Management. This specifies the number of megabytes to use as the low or high threshold.

    So easy to use this. Configure the LowMemoryThreshold value in registry, call CreateMemoryResourceNotification and create thread which waits on returned handle. I will test it tomorrow at work.
     
    Last edited: Oct 1, 2018
    AveYo likes this.
  12. Wagnard

    Wagnard Ancient Guru

    Messages:
    2,749
    Likes Received:
    521
    GPU:
    MSI Geforce GTX 1080
    Renaming done.
    It is possible some game doesn't like having the standbylist to be purge. I'm still suprized tho (saw same error on the net before even created the app). Can you tell if the issue happens too when the app is active but not started?
    How much time and Standby list purge it took for this to happen?
    Could be interesting, guess I will test this when I have some time ;)
     
    RzrTrek likes this.
  13. mbk1969

    mbk1969 Ancient Guru

    Messages:
    15,646
    Likes Received:
    13,648
    GPU:
    GF RTX 4070
    I will experiment with it tomorrow and will post results.
     
  14. mbk1969

    mbk1969 Ancient Guru

    Messages:
    15,646
    Likes Received:
    13,648
    GPU:
    GF RTX 4070
    Well, my upcoming version of standby list purging tool will have the way to purge only on specified app start.
     
    Wagnard likes this.
  15. AveYo

    AveYo Member

    Messages:
    43
    Likes Received:
    48
    GPU:
    8800GS 384MB
    It definitely have worked in all incarnations so far - You've probably skipped over the important part about the script being smart and only doing it if needed. Clearing every x minutes like clockwork is undesirable, but as mentioned before, you can force it by using a value higher than your ram for CLEAR_WHEN_UNDER_MB

    And an update: v5 features a fileless task run schedule!
    - snippet is exported in the registry (could have been a one-liner but schedule /TR limits command size to ~260chars).
    - only powershell running (not significantly slower or faster than wmic) - mostly for convenience + less av /monitoring triggers
    - added optional FILESYSTEMCACHEALSO=1 (disable by using 0 instead of 1)
    Older v4 cmd+wmic-based is still available via gist history

    Guess I could set the script to compile a local c# program instead of doing it on the fly,
    but I already stated my opinion on micro-optimizations in the first post and I stand by it.
    I'm more interested in observing any improvements (if any) for c#, wmi, powershell, scheduled tasks and registry repeated runs across multiple windows builds, .net and powershell versions and possible workarounds to benefit scripting in general, as opposed to compiled binaries.
     
    Last edited: Oct 2, 2018
    Dynarush_333 and Exostenza like this.

  16. Astyanax

    Astyanax Ancient Guru

    Messages:
    17,044
    Likes Received:
    7,380
    GPU:
    GTX 1080ti
    wagnards tool looks to be the easiest way t oge this done now,

    i wish microsoft would stop letting interns contribute unchecked code to the OS though.
     
  17. Wagnard

    Wagnard Ancient Guru

    Messages:
    2,749
    Likes Received:
    521
    GPU:
    MSI Geforce GTX 1080
    You may have another issue as I have tested Destiny 2 with quite heavy Standby list purge and no issues here.


    Next , I will see if purging the Priority 0 Standby list have any benefit, flushing the "Modified list" etc...

    Micro-optimization is fun, it's like tweaking a car to gain some more HP/ Torque. :cool:


    For all:
    1- For the purge to be triggered, do you prefer a % for the free memory or a manually entered xxxxMB ?
    2- Currently my app check at each seconds the status of the memory for checking if a purge is needed. Want the option to modify that?
     
    Last edited: Oct 2, 2018
  18. Exostenza

    Exostenza Maha Guru

    Messages:
    1,443
    Likes Received:
    791
    GPU:
    MSI 4090 GT
    I finally got a day off and did a lot of gaming without any script/task enabled or using Wagnard's program and I experienced zero stuttering... Could it be that it is actually fixed? Can someone else that definitely had stuttering corroborate this for us and try taking away all fixes to see if the stuttering comes back if a fix had previously stopped your stuttering?

    Also OP update with AveYo's v5 script.
     
    Last edited: Oct 2, 2018
  19. Astyanax

    Astyanax Ancient Guru

    Messages:
    17,044
    Likes Received:
    7,380
    GPU:
    GTX 1080ti
    Probably this

     
    Last edited: Oct 2, 2018
  20. AveYo

    AveYo Member

    Messages:
    43
    Likes Received:
    48
    GPU:
    8800GS 384MB
    Hint: if using my v5+ FreeStandbyMemory.bat, can check if working by forcing a manual clear:
    - directly in PowerShell (Admin):
    iex (Get-ItemProperty Registry::HKLM\SOFTWARE\AveYo).FreeStandbyMemory
    - directly in Command Prompt (Admin):
    powershell -noprofile -c "& {iex (Get-ItemProperty Registry::HKLM\SOFTWARE\AveYo).FreeStandbyMemory}"
    - with self-elevating via any cmd / powershell / .bat / .lnk:
    Code:
    powershell -noprofile -c "start powershell -ArgumentList '-noprofile -c & {iex (Get-ItemProperty Registry::HKLM\SOFTWARE\AveYo).FreeStandbyMemory}'  -verb RunAs -WindowStyle Hidden"
    gist mirror: https://git.io/FreeStandbyMemory.bat
     
    Last edited: Oct 2, 2018

Share This Page