-
Notifications
You must be signed in to change notification settings - Fork 114
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Optimize MakeAlphaString #295
Optimize MakeAlphaString #295
Conversation
When building the dataset I was able to max out the CPU on the node that's running HammerDB (as opposed to the database that this node is connecting to). Since generating data takes a significant amount of time I took some time trying to find some low hanging fruit for optimization. After some profiling, by far the most CPU heavy function turned out to be `MakeAlphaString`. After digging down even further it turns out that the thing that makes this function slow is the amount of `rand` calls it does. Since we cannot completely remove this `rand` call, I investigated some ways of reducing the number of calls. The new implementation is able to generate a sequence of 4 random characters with a single `rand` call. This is done in two ways: 1. Pre-compute an array of all combinations of two alphanumeric characters. 2. Use a single random number to generate two random numbers by creating a single random number in the range of 0-N^2. Then two get two independent random numbers out of that, you can divide by N. Then use the result of this division as the first random number and the remainder as the second random number. The result of these changes is that generating a random string with a length between 300 and 500 characters now only takes ~43% of the time it did before (on my machine). Apart from I tried some more approaches, but this turned out to run the fastest on my machine. I used the script at the end of this commit message to compare different approaches (including the simple approach). Please run the benchmark on your own machine. One thing to note is that the suggested `MakeAlphaString` implementation ignores the 3rd and 4th argument, and instead always uses all alphanumeric ASCII characters. Looking at the existing code there's only one place where a different character set is used. This is in `tpchcommon-1.0.tm`, which has its own `MakeAlphaString` implementation. All other uses use the same alphanumeric ASCII character array. Before merging this it would be good to remove the `ignored1` and `ignored2` arguments from the new `MakeAlphaString` implementation. I did not do that so far so first there could be some discussion if this faster implementation makes sense. ``` package require Tclx namespace eval benchutils { namespace export alphanumArray alphanumLength MakeAlphaString MakeAlphaStringFast MakeAlphaStringFastest proc RandomNumber {m M} {return [expr {int($m+rand()*($M+1-$m))}]} set alphanumArray [ list 0 1 2 3 4 5 6 7 8 9 A B C D E F G H I J K L M N O P Q R S T U V W X Y Z a b c d e f g h i j k l m n o p q r s t u v w x y z ] set alphanumLength [ llength $alphanumArray ] proc MakeAlphaString { x y chArray chalen } { set len [ RandomNumber $x $y ] for {set i 0} {$i < $len} {incr i} { append alphastring [lindex $chArray [ expr {int(rand()*$chalen)}]] } return $alphastring } proc CreateListProduct2 { chArray chalen } { for {set i 0} {$i < $chalen} {incr i} { set char1 [ lindex $chArray $i] for {set j 0} {$j < $chalen } {incr j } { set char2 [ lindex $chArray $j] set combined $char1 append combined $char2 lappend products $combined } } return $products } set alphanum2Array [CreateListProduct2 $alphanumArray $alphanumLength ] set alphanum2Length [ llength $alphanum2Array ] proc MakeAlphaStringFast { x y ignore1 ignore2 } { variable alphanum2Array; variable alphanum2Length; set len [ RandomNumber $x $y ] for {set i 0} {$i < $len} {incr i 2} { set randnum [ expr {int(rand() * $alphanum2Length)} ] append alphastring [lindex $alphanum2Array $randnum ] } return [ string range $alphastring 0 $len-1 ] } set alphanum2LengthSquared [ expr { $alphanum2Length * $alphanum2Length } ] proc MakeAlphaStringFastest { x y ignore1 ignore2} { variable alphanum2Array; variable alphanum2Length; variable alphanum2LengthSquared; set len [ RandomNumber $x $y ] for {set i 0} {$i < $len} {incr i 4} { set randnumFull [ expr {int(rand() * $alphanum2LengthSquared)} ] set randnum1 [ expr {$randnumFull / $alphanum2Length} ] set randnum2 [ expr {$randnumFull % $alphanum2Length} ] append alphastring [lindex $alphanum2Array $randnum1 ] append alphastring [lindex $alphanum2Array $randnum2 ] } return [ string range $alphastring 0 $len-1 ] } proc CreateListProduct3 { chArray chalen } { for {set i 0} {$i < $chalen } {incr i } { set char1 [ lindex $chArray $i] for {set j 0} {$j < $chalen } {incr j } { set char2 [ lindex $chArray $j] for {set k 0} {$k < $chalen } {incr k } { set char3 [ lindex $chArray $k ] set combined $char1 append combined $char2 $char3 lappend products $combined } } } return $products } set alphanum3Array [CreateListProduct3 $alphanumArray $alphanumLength ] set alphanum3Length [ llength $alphanum3Array ] set alphanum3LengthSquared [ expr { $alphanum3Length * $alphanum3Length } ] proc MakeAlphaStringMedium { x y ignore1 ignore2} { variable alphanum3Array; variable alphanum3Length; variable alphanum3LengthSquared; set len [ RandomNumber $x $y ] for {set i 0} {$i < $len} {incr i 6} { set randnumFull [ expr {int(rand() * $alphanum3LengthSquared)} ] set randnum1 [ expr {$randnumFull / $alphanum3Length} ] set randnum2 [ expr {$randnumFull % $alphanum3Length} ] append alphastring [lindex $alphanum3Array $randnum1 ] append alphastring [lindex $alphanum3Array $randnum2 ] } return [ string range $alphastring 0 $len-1 ] } proc MakeAlphaStringMedium2 { x y ignore1 ignore2 } { variable alphanum3Array; variable alphanum3Length; set len [ RandomNumber $x $y ] for {set i 0} {$i < $len} {incr i 3} { set randnum [ expr {int(rand() * $alphanum3Length)} ] append alphastring [lindex $alphanum3Array $randnum ] } return [ string range $alphastring 0 $len-1 ] } } puts [time {benchutils::MakeAlphaString 300 500 $alphanumArray $alphanumLength } 100000] puts [time {benchutils::MakeAlphaStringFast 300 500 0 0 } 100000] puts [time {benchutils::MakeAlphaStringFastest 300 500 0 0 } 100000] puts [time {benchutils::MakeAlphaStringMedium 300 500 0 0 } 100000] puts [time {benchutils::MakeAlphaStringMedium2 300 500 0 0 } 100000] ``` Timing results of this benchmark script on my machine: ``` 72.20168 microseconds per iteration 39.30159 microseconds per iteration 31.65163 microseconds per iteration 51.54411 microseconds per iteration 56.4996 microseconds per iteration ```
This is a great suggestion and if performance can be improved, here definitely something that should be adopted. I will test it out. |
I have tested this out for server builds using MariaDB and PostgreSQL with the following results: postgres orig = 12 min 40 secs / postgres new = 8 min 54 secs = 30% improvement TPROC-H SF10 with 64VUs For the environments tested, it gives very good improvement for TPROC-C, TPROC-H has no impact, however this is likely because MakeAlphaString is not the slowest function as there are additional grammar rules in building the text strings. I have also tested on a PC with SQL Server and this shows similar results, with the TPROC-C build being faster and the TPROC-H build similar. I have also tested performance with both builds and have not detected any differences between them. So, yes, this looks good and the recommendation to the subcommittee is that we accept the pull request. |
Resolve merge conflict for PR #295
Merging Pull Request as voted on by TPC-OSS subcommittee on 11th Jan 2022 with manual resolve of conflicts. |
When building the dataset I was able to max out the CPU on the
node that's running HammerDB (as opposed to the database that this node
is connecting to). Since generating data takes a significant amount of
time I took some time trying to find some low hanging fruit for
optimization.
After some profiling, by far the most CPU heavy function turned out to
be
MakeAlphaString
. After digging down even further it turns out thatthe thing that makes this function slow is the amount of
rand
calls itdoes. Since we cannot completely remove this
rand
call, I investigatedsome ways of reducing the number of calls.
The new implementation is able to generate a sequence of 4 random
characters with a single
rand
call. This is done in two ways:a single random number in the range of 0-N^2. Then two get two
independent random numbers out of that, you can divide by N. Then use
the result of this division as the first random number and the remainder
as the second random number.
The result of these changes is that generating a random string with a
length between 300 and 500 characters now only takes ~43% of the time it
did before (on my machine).
Apart from I tried some more approaches, but this turned out to run the
fastest on my machine. I used the script at the end of this commit
message to compare different approaches (including the simple approach).
Please run the benchmark on your own machine.
One thing to note is that the suggested
MakeAlphaString
implementationignores the 3rd and 4th argument, and instead always uses all
alphanumeric ASCII characters. Looking at the existing code there's only
one place where a different character set is used. This is in
tpchcommon-1.0.tm
, which has its ownMakeAlphaString
implementation.All other uses use the same alphanumeric ASCII character array. Before
merging this it would be good to remove the
ignored1
andignored2
arguments from the new
MakeAlphaString
implementation and all itscall sites. I did not do that so far so first there could be some discussion if
this faster implementation makes sense.
Timing results of this benchmark script on my machine: