| 228 | } |
| 229 | |
| 230 | func tuneWithParameters(digester Argon2, desiredRuntime time.Duration) (Argon2, error) { |
| 231 | // Tell the tuning algorithm whether or not to pin the memory cost |
| 232 | var ( |
| 233 | pinMemoryCost = false |
| 234 | bestConfig = Argon2{ |
| 235 | NumThreads: digester.NumThreads, |
| 236 | MemoryKibibytes: minMemoryCostKibibytes, |
| 237 | NumIterations: 1, |
| 238 | } |
| 239 | ) |
| 240 | |
| 241 | if digester.MemoryKibibytes <= minMemoryCostKibibytes { |
| 242 | digester.MemoryKibibytes = minMemoryCostKibibytes |
| 243 | pinMemoryCost = true |
| 244 | } |
| 245 | |
| 246 | for { |
| 247 | // Take 10 samples and average the time deltas |
| 248 | var timeDelta time.Duration |
| 249 | |
| 250 | for sample := 0; sample < numTuningSamples; sample++ { |
| 251 | startingTime := time.Now() |
| 252 | |
| 253 | if _, err := digester.Digest(tuningDigestContent); err != nil { |
| 254 | return bestConfig, fmt.Errorf("failed while digesting: %w", err) |
| 255 | } |
| 256 | |
| 257 | timeDelta += time.Since(startingTime) |
| 258 | } |
| 259 | |
| 260 | timeDelta /= numTuningSamples |
| 261 | |
| 262 | if !pinMemoryCost { |
| 263 | if timeDelta > desiredRuntime { |
| 264 | // Half the memory cost if the time delta is greater than the desired runtime |
| 265 | digester.MemoryKibibytes /= 2 |
| 266 | |
| 267 | // Break out if we've already fallen below the floor |
| 268 | if digester.MemoryKibibytes < minMemoryCostKibibytes { |
| 269 | bestConfig.MemoryKibibytes = minMemoryCostKibibytes |
| 270 | break |
| 271 | } |
| 272 | } else { |
| 273 | // Pin the memory cost here and adjust upward. The desire here is to execute with the largest possible |
| 274 | // memory allocation that performs within the desired specification. Once a candidate memory allocation |
| 275 | // is found, further adjustments rely on increasing the number of digest iterations. |
| 276 | pinMemoryCost = true |
| 277 | } |
| 278 | } else { |
| 279 | // Copy the current tuning parameters since they perform under the desired runtime specification |
| 280 | if digester.NumIterations == 1 { |
| 281 | bestConfig.MemoryKibibytes = digester.MemoryKibibytes |
| 282 | } |
| 283 | |
| 284 | // Attempt to increase the number of digest iterations if there's headroom, saving the last known good iteration |
| 285 | if desiredRuntime-timeDelta > timeCostTuningRange { |
| 286 | bestConfig.NumIterations = digester.NumIterations |
| 287 | digester.NumIterations++ |