This code disables several ON-by-default "obscure" delays, unnecessary debug functions, gives a higher CPU priority to your scripts and contains a high precision Sleep function.
Please note that inside the spoiler of note #8 you can find a good benchmark script to test the speed of your scripts.
Generic optimizations
Just copy this at the beginning of any AHK script to make use of the performance optimizations:
;OPTIMIZATIONS START
#NoEnv
#MaxHotkeysPerInterval 99000000
#HotkeyInterval 99000000
#KeyHistory 0
ListLines Off
Process, Priority, , A
SetBatchLines, -1
SetKeyDelay, -1, -1
SetMouseDelay, -1
SetDefaultMouseSpeed, 0
SetWinDelay, -1
SetControlDelay, -1
SendMode Input
DllCall("ntdll\ZwSetTimerResolution","Int",5000,"Int",1,"Int*",MyCurrentTimerResolution) ;setting the Windows Timer Resolution to 0.5ms, THIS IS A GLOBAL CHANGE
;OPTIMIZATIONS END
;YOUR SCRIPT GOES HERE
DllCall("Sleep","UInt",1) ;I just slept exactly 1ms!
DllCall("ntdll\ZwDelayExecution","Int",0,"Int64*",-5000) ;you can use this to sleep in increments of 0.5ms if you need even more granularity
^NOTES 1. #NoEnv is recommended for all scripts, it disables environment variables. 2. The default #MaxHotkeysPerInterval along with #HotkeyInterval will stop your script by showing message boxes if you have some kind of rapid autofire/turbo loop in it. Just put some insane unreachable high number to ignore this limit. 3. #KeyHistory and ListLines are functions used to "log your keys". Disable them as they're only useful for debugging purposes. 4. Setting a higher Priority to a Windows program is supposed to improve its performance. Use AboveNormal/A. If you feel like it's making things worse, comment or remove this line.
Spoiler
Using "Realtime/R" is usually counterproductive (Windows Task Manager warns you about this instability as well, same for AHK's documentation). So why not use High/H instead?
May i put in a small warning against "Process, Priority, , H" especially for big CPU heavy scripts
Process, Priority, , H puts you priority in the group with keyboard input and network drivers. So any CPU heavy sections of the script is going to infuse lag on both KB and network.
However if you script does not have any CPU heavy.sections there is no real benefit from adjusting priory. may i instead suggest do do only "Process, Priority, , A" it will still keep you priority above any other running software but below network and keyboard. Since Windows priority model is blocking priority ( With a lot of exceptions) there is no difference from being in High vs Above, when all other software is in Normal anyway. ( aka multiple steps higher priority is just as good as one step higher)
in short: unless you have a very specific technically reason. use only "Process, Priority, , A" to increase script priority.
5. The default SetBatchLines value makes your script sleep around 10-15.6 milliseconds every line. Make it -1 to not sleep (but remember to include at least one Sleep in your loops to avoid high CPU waste!). THIS IS THE SINGLE MOST IMPORTANT LINE, and will be the default in AutoHotkey v2. Honestly you could ignore everything else in this thread and just use this line. That's how much difference it makes. 6. Even though SendInput ignores SetKeyDelay, SetMouseDelay and SetDefaultMouseSpeed, having these delays at -1 improves SendEvent's speed just in case SendInput is not available and falls back to SendEvent. 7. SetWinDelay and SetControlDelay may affect performance depending on the script. 8. SendInput is the fastest send method. SendEvent (the default one) is 2nd place, SendPlay a far 3rd place (it's the most compatible one though). SendInput does not obey to SetKeyDelay, SetMouseDelay, SetDefaultMouseSpeed; there is no delay between keystrokes in that mode.
Spoiler
You can test this yourself by benchmarking the 3 methods. Here's a script you can use to do it (read the commented line for instructions):
;OPTIMIZATIONS START
#NoEnv
#MaxHotkeysPerInterval 99000000
#HotkeyInterval 99000000
#KeyHistory 0
ListLines Off
Process, Priority, , A
SetBatchLines, -1
SetKeyDelay, -1, -1
SetMouseDelay, -1
SetDefaultMouseSpeed, 0
SetWinDelay, -1
SetControlDelay, -1
SendMode Input
DllCall("ntdll\ZwSetTimerResolution","Int",5000,"Int",1,"Int*",MyCurrentTimerResolution) ;setting the Windows Timer Resolution to 0.5ms, THIS IS A GLOBAL CHANGE
;OPTIMIZATIONS END
BenchmarkLoopCount := 1000
DllCall("QueryPerformanceFrequency", "Int64*", Frequency)
StartTime := A_TickCount
DllCall("QueryPerformanceCounter", "Int64*", CounterBefore)
;BENCHMARK START
Loop,%BenchmarkLoopCount%
{
SendEvent {Pause} ;run the script 3 times by switching SendEvent to SendInput and SendPlay to benchmark the 3 different Send methods
}
;BENCHMARK END
DllCall("QueryPerformanceCounter", "Int64*", CounterAfter)
ElapsedTime := A_TickCount - StartTime
MsgBox % "Loops:`n" BenchmarkLoopCount "`n`n" "TickCount:`n" ElapsedTime " ms`n`n" "QueryPerformanceCounter:`n" . (CounterAfter - CounterBefore)*1000/Frequency . " ms"
9. It is well-documented that Sleep is not precise. But it can be if you change the Windows Timer Resolution first and then Sleep through a DllCall, with no CPU waste!
Spoiler
NOTES:
- a major disadvantage of this Sleep method is that it freezes the entire AHK thread, so for example hotkeys will be ignored while AHK is frozen for long Sleeps, and SetTimer's will be delayed
- NtSetTimerResolution/ZwSetTimerResolution is an undocumented Windows function
- setting the timer to any (positive) value lower than the minimum will set it to the minimum instead. Same behavior for the maximum
- the maximum (most precise) timer is usually 0.5ms (varies by OS version)
- the minimum (least precise) timer is usually 15.6ms (varies by OS version)
- the current timer varies from machine to machine and can change constantly. You can use OpenTimerResolution to monitor this (there are countless other programs that can show your Windows Timer Resolution out there, but only this one updates its interface continuously to reflect the truth)
- if multiple programs request different timer resolutions, the most precise one will take priority (this is documented in timeBeginPeriod)
- using NtDelayExecution/ZwDelayExecution, if your current Timer Resolution is 0.5ms, it will only sleep in increments of 0.5ms even though you can type any number in it. So -5000=0.5ms, -10000=1ms, -15000=1.5ms etc.
- here's a RegEx to replace in bulk your default sleeps with high precision ones: SEARCH:Sleep,\s*(\d+) REPLACE:DllCall\("Sleep","UInt",\1\)
FREQUENTLY ASKED QUESTIONS: Q. Why not just use DllCall("Winmm\timeBeginPeriod","UInt",1) like the documentation suggests? A. The lowest accepted value for that command is only 1ms, while NtSetTimerResolution supports 0.5ms
Q. Since changing the Timer Resolution affects all programs, why don't you set it back to normal with DllCall("Winmm\timeEndPeriod", "UInt", TimePeriod) like the documentation suggests? A. Why bother, quitting the program that changes the Timer Resolution will set it back to its normal value automatically. But still if you want to do that, you can run DllCall("ntdll\ZwSetTimerResolution","UInt",5000,"UInt",0,"UInt*",MyCurrentTimerResolution)
Q. What happens if you ZwDelayExecution for less... say, -1000 (0.1ms)? A. You will still sleep in increments of -5000 (0.5ms), BUT in a benchmark where I looped ZwDelayExecution 100 times, for some reason using -5000 was less accurate than using -1000 or -1 (on Win10)
Q. Why do you use Zw instead of Nt calls? A. Becauseif you are in User Mode, use whatever variant you think makes your code look pretty. In Kernel Mode, use the ZwXxx routines and get your previous mode set properly, to Kernel Mode.
10. When using PixelSearch to scan a single pixel of a single color variation, don't use the Fast parameter. According to my benchmarks, regular PixelSearch is faster than PixelSearch Fast in that case. 11. According to the documentation, the Unicode x64bit version of AHK is faster, use it when available (this text is found in the setup file).
Spoiler
Misc optimizations
Spoiler
Defining variables (benchmark)
jNizM wrote:Speed Test (Benchmark): Define Variables
dd900 wrote:
Documentation wrote:Performance: In v1.0.48+, the comma operator is usually faster than writing separate expressions, especially when assigning one variable to another (e.g. x:=y, a:=b). Performance continues to improve as more and more expressions are combined into a single expression; for example, it may be 35% faster to combine five or ten simple expressions into a single expression.
a := 1
, b := 2
, c := 3
, d := Function( param )
, e := AnotherFunc( d )
return
My project is rather large and doing this over my entire script has caused a noticeable increase in speed.
VarSetCapacity optimization
Sam_ wrote:in my experience (and according to the documentation), using VarSetCapacity() improves performance when building a string by means of gradual concatenation. From the documentation:
In addition to its uses described at DllCall, this function can also be used to enhance performance when building a string by means of gradual concatenation. This is because multiple automatic resizings can be avoided when you have some idea of what the string's final length will be.
From the example in the documentation (although not a working example...):
; Optimize by ensuring MyVar has plenty of space to work with.
VarSetCapacity(MyVar, 10240000) ; ~10 MB
Loop
{
...
MyVar = %MyVar%%StringToConcatenate%
...
}
SvenBent wrote:Avoid spreading math over multiple lines and using variable for intermediate results if they are only used once.
As much as possible condense math into one line and only use variables for storing math results if you need the results being used multiple times later.
You get around a 27% speedup on that emptymem call
Checking Boolean values with if
SvenBent wrote:a few tips for IF checking of Boolean values
Tested on a Core2Quad Q6600 system.
if VariableName
Seems to be the fastest way to check if a variable is True
if VariableName = 0
Is the fastest way to check if a variable is false however it does not take into account of the variable is not set, aka empty. The IF commands does not get activaged if the variable is not set/empty
if VariableName <> 1
is almost as fast and an empty variable is considere false ( aka the IF settings get activated) just like if it contained a 0
if Not VariableName
Seems to be slower than both of the two above
I REALLY like where this thread is heading! I've always wanted to know more about speed optimizations in AHK! Please post more benchmarks/comparisons if possible on ANY functions in AHK that essentially do the same thing. For instance, i use a LOT of settimer commands in my code and would love to know if you have any thoughts on it.
Also can you please comment more on these values (what the trade off is in using them):
jNizM wrote:Speed Test (Benchmark): Define Variables
Would you mind explaining what this is doing?
QPC(R := 0)
{
static P := 0, F := 0, Q := DllCall("QueryPerformanceFrequency", "Int64P", F)
return ! DllCall("QueryPerformanceCounter", "Int64P", Q) + (R ? (P := Q) / F : (Q - P) / F)
}
I dont understand how this function manages to measure the time it takes to assign variables by using QPC(0) and QPC(1)
1. Realtime priority is bad news almost always. Don't do that. It almost never works, even if your script is written correctly. If your script is written incorrectly, then you can cause your entire system to hang.
2. High priority/above normal priority sometimes work. You can try them, but they are a very big potential source of problems. Disable them if something is going wrong.
3. Spintest sleep (sleepz) doesn't work when scripting for programs that require CPU time to process. For example, if you're trying to script for a game, then your script will hog all the processor time and the game will lag.
4. Batchlines 0 forces your script to consume CPU time even when you sleep. That absolutely kills performance in the rest of the system. Don't do it.
5. Setting batchlines to normal right before Sleep seems to not change anything at all. It still consumes huge CPU timeslices.
6. WAZAA is absolutely correct about the speed of SendMode Input and SendMode Event, but the difference is tiny compared to the variability of Sleep. I suspect that Event may still be less reliable, but I am not sure and don't have meaningful tests for reliability.
Batchlines 0 forces your script to consume CPU time even when you sleep.
No. SetBatchLines does not affect Sleep at all. It determines how often the program "rests" (sleeps for 10ms) in between execution of lines of script. If the script is not executing code (i.e. it is idle, waiting or sleeping), the setting has no effect.
That absolutely kills performance in the rest of the system. Don't do it.
On my 5yo system, a single script utilizing maximum CPU time does not noticeably affect performance at all, let alone "kill" it. Most systems have at least a dual-core CPU, and a script is only capable of tying up one core.
It still consumes huge CPU timeslices.
There's something wrong with your script, or your system. While sleeping, the program checks its message queue and then yields its CPU time slice, and repeats. There is generally no measurable CPU usage unless there is a flood of messages to process (having been sent to the script's own window(s)). Edit:
To be clear, I am not condoning the OP's suggestions. For instance, these will not make the script run faster, and might even be undesirable:
#MaxThreads 255
#MaxMem 4095
#MaxThreadsBuffer On
#MaxHotkeysPerInterval 99000000
#HotkeyInterval 99000000
Most scripts will never need more than the default #MaxThreads (10). That means something like a hotkey starts, then is interrupted by another, and another, etc. so that there are 10 running that have not yet completed. If that happens and you hit the default limit, there's probably a problem with the design of your script.
#MaxMem: "The purpose of limiting each variable's capacity is to prevent a buggy script from consuming all available system memory. Raising or lowering the limit does not affect the performance of a script"
The default settings for #MaxHotkeysPerInterval and #HotkeyInterval enable a safe-guard against accidentally causing infinite loops with hotkeys that send keys which can trigger hotkeys. Using the settings shown above would disable this safe-guard. (However, disabling it is sometimes useful - for example, some Logitech mice have a free-spinning mouse wheel, which can easily exceed the default hotkey limit for a WheelUp/WheelDown hotkey.)
I'm glad I posted. Thanks for the informative response!
lexikos wrote:
Batchlines 0 forces your script to consume CPU time even when you sleep.
No. SetBatchLines does not affect Sleep at all. It determines how often the program "rests" (sleeps for 10ms) in between execution of lines of script. If the script is not executing code (i.e. it is idle, waiting or sleeping), the setting has no effect.
That absolutely kills performance in the rest of the system. Don't do it.
On my 5yo system, a single script utilizing maximum CPU time does not noticeably affect performance at all, let alone "kill" it. Most systems have at least a dual-core CPU, and a script is only capable of tying up one core.
This was my mistake. This is affected by my test setup I forgot to mention. I'm on VMWare, with only one core. So anything that behaves badly causes system-wide lockdown for me.
It still consumes huge CPU timeslices.
There's something wrong with your script, or your system. While sleeping, the program checks its message queue and then yields its CPU time slice, and repeats. There is generally no measurable CPU usage unless there is a flood of messages to process (having been sent to the script's own window(s)).
You are right, my testing script had a mistake. I was testing a special sleep() function which used normal Sleep for a long duration, then a spintest Sleepz() to have fine resolution at the end. But my function calculated used "Sleep % %period% - 15.7" which meant I was just testing the high resolution timer.
The OP and I are actually both scripting for the same game. The context is that frames take ~16ms - 22ms, and it's important that sent keys resolve on discrete frames.
I'm thinking about letting my script force Windows's built in tick from 15.6 ms to maybe 5 ms. The downside would be power consumption, and probably excessive context switches, but maybe it will improve Sleep consistency. I'm also kind of wondering how VMWare will handle this tick resolution, given that I've disabled its "backdoor" port that lets it communicate with the host system. Would my host system get the changed tick as well? And if my script crashes and forgets to reset the timer, will my system be stuck at a 5ms tick until reset?
SetBatchLines, -1 should be enough, as long as you sleep somewhere in your loops.
That absolutely kills performance in the rest of the system. Don't do it.
On my 5yo system, a single script utilizing maximum CPU time does not noticeably affect performance at all, let alone "kill" it. Most systems have at least a dual-core CPU, and a script is only capable of tying up one core.
Whether or not it kills performance really depends on the system and script, but I'd still recommend against anything non-critical running as real-time (possibly starving the process your scripting for); if you need that much of the cpus time for a script it might be better to revisit the code to see if it can be reduced.
edit*:
The default settings for #MaxHotkeysPerInterval and #HotkeyInterval enable a safe-guard against accidentally causing infinite loops with hotkeys that send keys which can trigger hotkeys. Using the settings shown above would disable this safe-guard. (However, disabling it is sometimes useful - for example, some Logitech mice have a free-spinning mouse wheel, which can easily exceed the default hotkey limit for a WheelUp/WheelDown hotkey.)
It would be nice to be able to turn of the popup you get when these limits are exceeded, and just halt the script for a time.
KuroiLight wrote:It would be nice to be able to turn of the popup you get when these limits are exceeded, and just halt the script for a time.
On the surface that seems like a good idea, but the point of the limit and the popup is that hitting the limit often indicates an error in the script which should be fixed, and the popup directs users to the solution. Suppressing the popup and instead blocking the hotkey for a time would likely result in the script being left as is, not behaving the way the user expects - either because they get an occasional flood of hotkeys (from a send-hotkey loop) that wasn't intentional, or because their rapid-fire hotkey doesn't work consistently.
If you're specifically opting to suppress the popup but keeping the limit, that basically means you expect the hotkey to fire very rapidly sometimes, but still want to limit the number of activations per interval. I don't think that would be a common case.
KuroiLight wrote:It would be nice to be able to turn of the popup you get when these limits are exceeded, and just halt the script for a time.
On the surface that seems like a good idea, but the point of the limit and the popup is that hitting the limit often indicates an error in the script which should be fixed, and the popup directs users to the solution. Suppressing the popup and instead blocking the hotkey for a time would likely result in the script being left as is, not behaving the way the user expects - either because they get an occasional flood of hotkeys (from a send-hotkey loop) that wasn't intentional, or because their rapid-fire hotkey doesn't work consistently.
If you're specifically opting to suppress the popup but keeping the limit, that basically means you expect the hotkey to fire very rapidly sometimes, but still want to limit the number of activations per interval. I don't think that would be a common case.
I agree, I've only encountered this once so far with my scroll script, since someones mouse wheel could potentially send 10s or 100s of events at a time. I think it would be better if after a set limit of it would end the thread, e.g.
One of three things can happen if the hotkey is activated when it already has a thread running:
The new thread interrupts the older ones, which can be resumed some time after the new thread finishes.
The hotkey is running the maximum number of threads, so the event is ignored/discarded (#MaxThreadsBuffer Off).
The hotkey is running the maximum number of threads, so the event is buffered/postponed (#MaxThreadsBuffer On).
Maybe what you're saying is that one of the hotkey's previous threads should be terminated before starting the new thread, but that simply can't be done with the current thread design. It also has nothing to do with the problem #MaxHotkeysPerInterval is meant to detect: rapidly repeated (not simultaneous) activations of a hotkey.
#NoEnv
Process, Priority, , H
SetBatchLines, -1
ListLines Off
#KeyHistory 0
SendMode Input
SetTitleMatchMode 2
SetTitleMatchMode Fast
SetKeyDelay, -1, -1, Play
SetMouseDelay, -1
SetWinDelay, 0 ; Changed to 0 upon recommendation of documentation
SetControlDelay, 0 ; Changed to 0 upon recommendation of documentation
SetDefaultMouseSpeed, 0
; YOUR_SCRIPT_HERE
Edited SetKeyDelay. See next post.
Last edited by Sam_ on 22 Jun 2015, 20:27, edited 2 times in total.