we have over-engineered "getrandom" to death

This commit is contained in:
dtookey 2023-08-15 08:07:08 -04:00
parent c36583631c
commit 0f0c343a85
5 changed files with 150 additions and 21 deletions

View File

@ -116,9 +116,10 @@ interface InputController {
/**
* Holds x and y variance bounds for random point generation.
*
* Used as parameter in [MousePointerObserver.getNearbyPoint] to control the allowed offset range.
* Used as parameter in [InputController.getNearbyPoint] to control the allowed offset range.
*
* @see MousePointerObserver.getNearbyPoint()
* @see InputController.getNearbyPoint
* @see InputController.animateMoveMouse
*/
data class PointerVarianceBounds(
val xVariance: Int = 25,

View File

@ -29,7 +29,7 @@ interface TemporalController {
val duration = if(maxAdditionalDuration == 0L){
baseDuration
}else{
baseDuration + util.HelperFunctions.getRandomLongFromNormalDistribution(maxAdditionalDuration)
baseDuration + util.HelperFunctions.getGaussianLong(maxAdditionalDuration)
}
TimeUnit.MILLISECONDS.sleep(duration)

View File

@ -115,7 +115,6 @@ interface WindowsOSProxy : MousePointerObserver, OSProxy {
}
enum class DwmWindowAttribute {
DWMWA_NCRENDERING_ENABLED,
DWMWA_NCRENDERING_POLICY,
DWMWA_TRANSITIONS_FORCEDISABLED,
@ -320,18 +319,15 @@ interface WindowsOSProxy : MousePointerObserver, OSProxy {
val hWnd = user32.GetForegroundWindow()
val rect = WinRect()
val success = user32.DwmGetWindowAttribute(
hWnd,
8,
rect.pointer,
rect.size()
)
return if (true){
rect.read()
Rectangle(rect.top, rect.left, (rect.right - rect.left), rect.bottom - rect.top)
}else{
null
}
// val attr = user32.DwmGetWindowAttribute(
// hWnd,
// 8,
// rect.pointer,
// rect.size()
// )
rect.read()
return Rectangle(rect.top, rect.left, (rect.right - rect.left), rect.bottom - rect.top)
}
fun barelyFunctionalWindowQuery(): Rectangle? {

View File

@ -42,21 +42,38 @@ object HelperFunctions {
* @param upperBound The upper bound to sample from
* @return A random long value following an approximate normal distribution
*/
fun getRandomLongFromNormalDistribution(upperBound: Long): Long{
fun getGaussianLong(upperBound: Long): Long {
// anything lower to 2 will round down to zero, so return zero. Additionally, this guarantees a positive upper
//bound in one step
if(upperBound <= 2L){
if (upperBound <= 2L) {
return 0L
}
// To create more natural distribution Take two random samples from 0 to upperBound/2 and add them. This
// approximates a normal distribution better than single random sample.
val subBound = upperBound/2L
val subBound = upperBound / 2L
val result1 = Random.nextLong(subBound)
val result2 = Random.nextLong(subBound)
return result1 + result2
}
/**
* Generates a random long following a Gaussian distribution within the given upper bound.
*
* Models the distribution observed in getRandomLongFromNormalDistribution
*
* @param upperBound The upper bound of the distribution
* @return A random long value
*/
fun getNextGaussian(upperBound: Long): Long {
return java.util.Random()
.nextGaussian(
upperBound.toDouble() / 2.0,
upperBound.toDouble() / 5.0
).toLong()
.coerceIn(0..upperBound)
}
/**
* Prints a progress report to console showing current step, total steps, elapsed time, and estimated remaining time.

View File

@ -1,7 +1,9 @@
package util
import kotlin.math.pow
import kotlin.test.*
import kotlin.system.measureNanoTime
import kotlin.system.measureTimeMillis
import kotlin.test.Test
class HelperFunctionsTest {
@ -14,7 +16,7 @@ class HelperFunctionsTest {
val numSamples = 10000
val upperBound = 1000L
val samples = (1..numSamples).map {
HelperFunctions.getRandomLongFromNormalDistribution(upperBound)
HelperFunctions.getGaussianLong(upperBound)
}
// Calculate mean and standard deviation
@ -26,4 +28,117 @@ class HelperFunctionsTest {
assert(stdDev > upperBound * 0.2 && stdDev < upperBound * 0.3)
}
@Test
fun `test benchmark getNextLongJanky`() {
val iterations = 1000000
val upperBound = 1000L
println("Starting benchmark N=$iterations, Range[0,$upperBound] using getRandomLongFromNormalDistribution...")
repeat(10) {
val samples = ArrayList<Long>(iterations)
val time = doBenchmark(iterations, upperBound) {
val v = HelperFunctions.getGaussianLong(it)
samples.add(v)
v
}
// Calculate mean and standard deviation
val mean = samples.average()
val stdDev = samples.map { (it - mean).pow(2) }.average().pow(0.5)
println("[Speed] Generated $iterations random longs in $time nanos [${(time.toDouble() / iterations.toDouble())}ns per operation]")
println("[Precision] Mean: $mean\tstDev: $stdDev\n")
}
}
@Test
fun `test benchmark getNextLongGaussian`() {
val iterations = 1000000
val upperBound = 1000L
println("Starting benchmark N=$iterations, Range[0,$upperBound] using getNextGaussian...")
repeat(10) {
val samples = ArrayList<Long>(iterations)
val time = doBenchmark(iterations, upperBound) {
val v = HelperFunctions.getNextGaussian(it)
samples.add(v)
v
}
// Calculate mean and standard deviation
val mean = samples.average()
val stdDev = samples.map { (it - mean).pow(2) }.average().pow(0.5)
println("[Speed] Generated $iterations random longs in $time nanos [${(time.toDouble() / iterations.toDouble())}ns per operation]")
println("[Precision] Mean: $mean\tstDev: $stdDev\n")
}
}
fun eat(d: Number) {
}
fun doBenchmark(iterations: Int, upperBound: Long, callback: (Long) -> Long): Long {
var total = 0L
return measureNanoTime {
repeat(iterations) {
total += callback(upperBound)
}
}
}
@Test
fun `test Direct Comparison of performance`() {
val heats = 1000
val iterations = 1000000
val upperBound = 1000L
val addTimes = ArrayList<Long>(heats)
val gaussianTimes = ArrayList<Long>(heats)
//pre-warm the JIT and caches a bit
println("Warming up for a few iterations")
val consumer = ArrayList<Long>(iterations)
repeat(iterations){
consumer.add(HelperFunctions.getGaussianLong(upperBound))
consumer.add(HelperFunctions.getNextGaussian(upperBound))
}
println("Benchmarking methods [$heats heats of $iterations iterations]{Range[0,$upperBound]}...")
repeat(heats) {
var total = 0L
val time1 = measureNanoTime {
repeat(iterations) {
total += HelperFunctions.getGaussianLong(upperBound)
}
}
addTimes.add(time1/iterations)
total = 0L
val time2 = measureNanoTime {
repeat(iterations) {
total += HelperFunctions.getNextGaussian(upperBound)
}
}
gaussianTimes.add(time2/iterations)
}
printStatisticsWithPrefix(addTimes, "Add Randoms method:\t", "ns")
printStatisticsWithPrefix(gaussianTimes, "NextGaussian method:", "ns")
}
fun printStatisticsWithPrefix(samples: ArrayList<Long>, prefix: String, unit: String = "") {
// Calculate mean and standard deviation
val mean = samples.average()
val stdDev = samples.map { (it - mean).pow(2) }.average().pow(0.5)
println("$prefix\tMean: $mean$unit\tstDev: $stdDev$unit")
}
}