Tutorial Optimizing: Difference between revisions
(→Optimizing code: Grammatical edits) |
mNo edit summary |
||
(2 intermediate revisions by the same user not shown) | |||
Line 11: | Line 11: | ||
TimerInit and TimerDiff. TimerInit is used to get a reference point and TimerDiff to get the difference between the reference point and the call to TimerDiff in milliseconds. | TimerInit and TimerDiff. TimerInit is used to get a reference point and TimerDiff to get the difference between the reference point and the call to TimerDiff in milliseconds. | ||
So the simplest possible sample could be: | So the simplest possible sample could be: | ||
< | <syntaxhighlight lang="autoit"> | ||
Local $i, $j, $sum, $count, $ts, $td | Local $i, $j, $sum, $count, $ts, $td | ||
For $j = 1 to 10 | For $j = 1 to 10 | ||
Line 24: | Line 24: | ||
$count = 0 | $count = 0 | ||
Next | Next | ||
</ | </syntaxhighlight> | ||
Now although this is a reasonable way of measuring execution time, it does not give an accurate picture to identify bottlenecks. And the reason is that it measures real time and not the time spent by the CPU to execute this part of the code. You can probably see this if one of the iterations reaches a $sum=0 in few iterations compared to one that uses max iterations. | Now although this is a reasonable way of measuring execution time, it does not give an accurate picture to identify bottlenecks. And the reason is that it measures real time and not the time spent by the CPU to execute this part of the code. You can probably see this if one of the iterations reaches a $sum=0 in few iterations compared to one that uses max iterations. | ||
Line 30: | Line 30: | ||
API notes: GetProcessTimes, GetThreadTimes | API notes: GetProcessTimes, GetThreadTimes | ||
Windows versions based on NT kernel has at least two API calls to make profiling easier for us. GetProcessTimes will return the CPU time spent by the given process. GetThreadTimes will return the CPU time spent by the given thread. As AutoIt does not support user defined threads, we will use the GetProcessTimes call. A [http://www.autoitscript.com/forum/index.php?s=&showtopic=38751 profiling UDF] has been created by [http://www.autoitscript.com/forum/index.php?showuser=7836 @uten] in the AutoItScript forum. As for now we will use that code. Save the code in an include file. I have called mine profiler.au3 and saved it in an include directory. (TODO: Link to page explaining userdefined include directories.) | Windows versions based on NT kernel has at least two API calls to make profiling easier for us. GetProcessTimes will return the CPU time spent by the given process. GetThreadTimes will return the CPU time spent by the given thread. As AutoIt does not support user defined threads, we will use the GetProcessTimes call. A [http://www.autoitscript.com/forum/index.php?s=&showtopic=38751 profiling UDF] has been created by [http://www.autoitscript.com/forum/index.php?showuser=7836 @uten] in the AutoItScript forum. As for now we will use that code. Save the code in an include file. I have called mine profiler.au3 and saved it in an include directory. (TODO: Link to page explaining userdefined include directories.) | ||
< | <syntaxhighlight lang="autoit"> | ||
Global $FILETIME = "dword;dword" ;Contains a 64-bit value representing the number of 100-nanosecond intervals since January 1, 1601 (UTC). | |||
Global $SYSTEMTIME = "ushort;ushort;ushort;ushort;ushort;ushort;ushort;ushort" | Global $SYSTEMTIME = "ushort;ushort;ushort;ushort;ushort;ushort;ushort;ushort" | ||
Func FileTimeToNum(ByRef $FT) | Func FileTimeToNum(ByRef $FT) | ||
Line 105: | Line 106: | ||
EndIf | EndIf | ||
Return $ret | Return $ret | ||
EndFunc</ | EndFunc | ||
</syntaxhighlight> | |||
Now that we have a some profiling routines. ProfileInit and ProfileDiff we can change our sample to make use of those UDF's. | Now that we have a some profiling routines. ProfileInit and ProfileDiff we can change our sample to make use of those UDF's. | ||
Note that the time returned from ProfileDiff are in 100-nanoseconds intervals. To get ms we have to divide by 10000. | Note that the time returned from ProfileDiff are in 100-nanoseconds intervals. To get ms we have to divide by 10000. | ||
< | <syntaxhighlight lang="autoit"> | ||
#include <profiler.au3> | #include <profiler.au3> | ||
Local $i, $j, $sum, $count, $ps, $pd, $uk | Local $i, $j, $sum, $count, $ps, $pd, $uk | ||
Line 124: | Line 126: | ||
$count = 0 | $count = 0 | ||
Next | Next | ||
</ | </syntaxhighlight> | ||
=Optimizing code= | =Optimizing code= | ||
Line 132: | Line 134: | ||
==Getting some data== | ==Getting some data== | ||
Let's grab some data from the AutoItScript forum. The data is not relevant for the sample so if you have a fairly big HTML file on your disk, you could use that instead. | Let's grab some data from the AutoItScript forum. The data is not relevant for the sample so if you have a fairly big HTML file on your disk, you could use that instead. | ||
< | <syntaxhighlight lang="autoit"> | ||
#include <inet.au3> | #include <inet.au3> | ||
$data = _InetGet("http://www.autoitscript.com/forum/index.php?showtopic=36941") | $data = _InetGet("http://www.autoitscript.com/forum/index.php?showtopic=36941") | ||
;$data = ReadFile("<your file>") | ;$data = ReadFile("<your file>") | ||
</ | </syntaxhighlight> | ||
==Data to plain text. Take one== | ==Data to plain text. Take one== | ||
< | <syntaxhighlight lang="autoit"> | ||
Func WordList(ByRef $data) | Func WordList(ByRef $data) | ||
;Shave of html tags | ;Shave of html tags | ||
Line 144: | Line 146: | ||
;Return string | ;Return string | ||
EndFunc | EndFunc | ||
</ | </syntaxhighlight> | ||
==Data to plain text. Take two== | ==Data to plain text. Take two== | ||
< | <syntaxhighlight lang="autoit"> | ||
Func Wordlist2(ByRef $data) | Func Wordlist2(ByRef $data) | ||
Local $res = StringRegExp($data, "TODO", 3) | Local $res = StringRegExp($data, "TODO", 3) | ||
Line 152: | Line 154: | ||
;Return string | ;Return string | ||
EndFunc | EndFunc | ||
</ | </syntaxhighlight> | ||
==Sorting the text. Take one== | ==Sorting the text. Take one== | ||
Using bouble sort | Using bouble sort | ||
==Sorting the text. Take two== | ==Sorting the text. Take two== | ||
Using something faster. | Using something faster. |
Latest revision as of 06:22, 20 June 2014
NOTE: This document is work in progress. Your view and contribution on content is welcome. Spellchecking and grammar will be worked on when the document approaches a stable state.
Introduction
This tutorial will be about profiling and optimizing code when we have identified a bottleneck. There are several paths down this road. You could analytically evaluate your code, or fragments of it. You could optimize parts that don't feel good. And you can measure how long time the code takes to run.
Profiling
When we profile the code we use a stopwatch to log how much time that is spent from the code starts until it ends execution. There are two types of time we can use. The real time spent and the CPU time spent to execute the code. Both methods have its usage. But measuring the CPU time should normally result in better knowledge than measuring clock time. The exception could be in routines where the code calls external resources that depends on other factors rather than the CPU. Ex: printer, network, disk IO.
The simplest approach
Autoit has two handy clock functions to do a regular stopwatch measure of code execution time. Namely TimerInit and TimerDiff. TimerInit is used to get a reference point and TimerDiff to get the difference between the reference point and the call to TimerDiff in milliseconds. So the simplest possible sample could be:
Local $i, $j, $sum, $count, $ts, $td
For $j = 1 to 10
$ts = TimerInit()
Do
$count += 1
$sum += Random(-5, 5, 1)
Until $sum = 0 OR $count = 100000
; Write/create report
$td = TimerDiff($ts)
ConsoleWrite( StringFormat("%#.6g ms, Iter=%.6d, avg=%.6g", $td, $count, $td/$count) & @LF)
$count = 0
Next
Now although this is a reasonable way of measuring execution time, it does not give an accurate picture to identify bottlenecks. And the reason is that it measures real time and not the time spent by the CPU to execute this part of the code. You can probably see this if one of the iterations reaches a $sum=0 in few iterations compared to one that uses max iterations.
The more accurate approach
API notes: GetProcessTimes, GetThreadTimes Windows versions based on NT kernel has at least two API calls to make profiling easier for us. GetProcessTimes will return the CPU time spent by the given process. GetThreadTimes will return the CPU time spent by the given thread. As AutoIt does not support user defined threads, we will use the GetProcessTimes call. A profiling UDF has been created by @uten in the AutoItScript forum. As for now we will use that code. Save the code in an include file. I have called mine profiler.au3 and saved it in an include directory. (TODO: Link to page explaining userdefined include directories.)
Global $FILETIME = "dword;dword" ;Contains a 64-bit value representing the number of 100-nanosecond intervals since January 1, 1601 (UTC).
Global $SYSTEMTIME = "ushort;ushort;ushort;ushort;ushort;ushort;ushort;ushort"
Func FileTimeToNum(ByRef $FT)
Return BITOr(BitShift(DllStructGetData($FT, 1), 32), DllStructGetData($FT, 2))
EndFunc
Func OpenProcess($pid = @AutoItPID)
Local $PROCESS_QUERY_INFORMATION = 0x0400
Local $PROCESS_VM_READ = 0x0010
$Process = DLLCall("kernel32.dll","int","OpenProcess","int", _
BitOR($PROCESS_QUERY_INFORMATION,$PROCESS_VM_READ),"int",0,"int", $pid)
If $Process[0] = 0 Then Return SetError(1, 0, 0)
Return $Process[0]
EndFunc
Func GetProcessTimes(ByRef $hProcess, ByRef $t1, ByRef $t2, ByRef $t3, ByRef $t4)
;BOOL WINAPI GetProcessTimes(
; HANDLE hProcess,
; LPFILETIME lpCreationTime,
; LPFILETIME lpExitTime,
; LPFILETIME lpKernelTime,
; LPFILETIME lpUserTime
; )
;
Local $p1 = DllStructGetPtr($t1)
Local $p2 = DllStructGetPtr($t2)
Local $p3 = DllStructGetPtr($t3)
Local $p4 = DllStructGetPtr($t4)
;ConsoleWrite($p1 & ", " & $p2& ", " & $p3 & ", " & $p4 & @LF)
Local $ret = dllcall("kernel32.dll","int", "GetProcessTimes","int",$hProcess , "ptr", $p1, "ptr", $p2, "ptr", $p3, "ptr", $p4)
If $ret[0] = 0 Then
ConsoleWrite("(" & @ScriptLineNumber & ") : = Error in GetProcessTimes call" & @LF)
SetError(1, 0 , $ret[0])
EndIf
Return $ret[0]
EndFunc
Func ProfileInit()
Local $process = OpenProcess(@AutoItPID)
if @error then
ConsoleWrite("!OpenProcess failed terminating" & @LF)
Exit
EndIf
Local $ret[4]
$ret[0] = 2
Local $t1 = DllStructCreate($FILETIME)
Local $t2 = DllStructCreate($FILETIME)
Local $t3 = DllStructCreate($FILETIME)
Local $t4 = DllStructCreate($FILETIME)
If GetProcessTimes($process, $t1, $t2, $t3, $t4) Then
$ret[$ret[0] + 1] = $process
$ret[1] = FileTimeToNum($t3)
$ret[2] = FileTimeToNum($t4)
Else
ConsoleWrite("(" & @ScriptLineNumber & ") := @error:=" & @error & ", @extended:?" & @extended & @LF)
SetError(@error, @extended, 0)
EndIf
;ArrayDump($ret, "ProfileInit Out")
Return $ret
EndFunc
Func ProfileDiff(ByRef $init)
Local $ret[3]
$ret[0] = 2
;ArrayDump($init, "ProfileDiff($init)")
Local $t1 = DllStructCreate($FILETIME)
Local $t2 = DllStructCreate($FILETIME)
Local $t3 = DllStructCreate($FILETIME)
Local $t4 = DllStructCreate($FILETIME)
Local $n1, $n2
If GetProcessTimes($init[$init[0] + 1], $t1, $t2, $t3, $t4) Then
$ret[1] = FileTimeToNum($t3) - $init[1]
$ret[2] = FileTimeToNum($t4) - $init[2]
Else
ConsoleWrite("(" & @ScriptLineNumber & ") := @error:=" & @error & ", @extended:=" & @extended & @LF)
SetError(@error, 0 , 0)
EndIf
Return $ret
EndFunc
Now that we have a some profiling routines. ProfileInit and ProfileDiff we can change our sample to make use of those UDF's. Note that the time returned from ProfileDiff are in 100-nanoseconds intervals. To get ms we have to divide by 10000.
#include <profiler.au3>
Local $i, $j, $sum, $count, $ps, $pd, $uk
For $j = 1 to 10
$ps = ProfileInit()
Do
$count += 1
$sum += Random(-5, 5, 1)
Until $sum = 0 OR $count = 100000
; Write/create report
$pd = ProfileDiff($ps)
$uk = Round(($pd[1] + $pd[2])/10000, 0) ;User and kernel time
ConsoleWrite( StringFormat("%#.6g ms, Iter=%.6d, avg=%.6g", $uk, $count, $uk/$count) & @LF)
$count = 0
Next
Optimizing code
When do we need to optimize our code? Well, that's a good question. A rule of thumb is to make the code work first. If we experience that the code is slow, we have to find the cause. When one or more causes are found, we have to consider if it is possible to optimize the code.
Let's have a look at a real world sample. Sorting stuff is a classic.
Getting some data
Let's grab some data from the AutoItScript forum. The data is not relevant for the sample so if you have a fairly big HTML file on your disk, you could use that instead.
#include <inet.au3>
$data = _InetGet("http://www.autoitscript.com/forum/index.php?showtopic=36941")
;$data = ReadFile("<your file>")
Data to plain text. Take one
Func WordList(ByRef $data)
;Shave of html tags
Local $res
;Return string
EndFunc
Data to plain text. Take two
Func Wordlist2(ByRef $data)
Local $res = StringRegExp($data, "TODO", 3)
;Array to string
;Return string
EndFunc
Sorting the text. Take one
Using bouble sort
Sorting the text. Take two
Using something faster.