Speed up Guid.NewGuid() on Linux #123540
Conversation
… to offset generation costs
There was a problem hiding this comment.
Pull request overview
This PR optimizes Guid.NewGuid() on Unix, primarily targeting Linux, by reducing syscall overhead and reusing entropy to significantly improve performance while maintaining cryptographic-strength randomness.
Changes:
- Teach the native random infrastructure to use
getrandom()(when available) instead of/dev/urandom, reducing per-call overhead for cryptographically secure random bytes. - Add CMake configuration and header wiring to detect and expose the availability of
getrandom. - Reimplement
Guid.NewGuid()on Unix to use a per-thread cache of GUIDs filled in batches via a single secure-random call, including logic to correctly set RFC 4122 version and variant bits for each cached GUID.
Reviewed changes
Copilot reviewed 4 out of 4 changed files in this pull request and generated 1 comment.
| File | Description |
|---|---|
src/native/minipal/random.c |
Adds a HAVE_GETRANDOM-guarded implementation of minipal_get_cryptographically_secure_random_bytes using the getrandom() syscall, looping until the requested buffer is fully populated and handling EINTR, to provide a faster secure RNG backend than /dev/urandom. |
src/native/minipal/minipalconfig.h.in |
Introduces the HAVE_GETRANDOM configuration macro so native code can be conditionally compiled against getrandom() support. |
src/native/minipal/configure.cmake |
Extends the CMake configuration to probe for getrandom in sys/random.h, defining HAVE_GETRANDOM when present so the new native path in random.c is enabled on supported Unix platforms. |
src/libraries/System.Private.CoreLib/src/System/Guid.Unix.cs |
Replaces the per-call secure RNG usage with a [ThreadStatic] cache of 64 GUIDs, filled via a single Interop.GetCryptographicallySecureRandomBytes call and post-processed to set version/variant bits, and updates NewGuid() to draw from this cache (with a WASI-specific non-cached fallback). |
|
Managed side cache of secure RNG doesn't sound secure. If NewGuid becomes a bottleneck there are other strategies to make it fast |
|
@dotnet-policy-service agree |
|
@reedz can you please update your PR's description with performance numbers now that the batching has been removed? |
|
@EgorBot -arm -x64 using BenchmarkDotNet.Attributes;
using BenchmarkDotNet.Running;
BenchmarkSwitcher.FromAssembly(typeof(Bench).Assembly).Run(args);
[MemoryDiagnoser]
public class Bench
{
[Benchmark]
public Guid New() => Guid.NewGuid();
} |
|
/ba-g timeouts, known NAOT failure |
|
/ba-g infrastructure timeouts |
@EgorBo I am curious as to why it is considered insecure to store them in a private field and delete them as it is used? Is it possible for attackers to access arbitrary memory? Is't the result of |
Managed arrays are not secure and unrelated to the accessibility. It can be moved around by the GC and only the latest in-use location will get deleted. |
Fixes #13628.
Fixes #107989.
This PR implements two optimizations:
getrandom()syscall (if available) instead of reading from/dev/urandomis ~14% quicker >getrandom()call as performed inRandom.SharedBenchmark dot net benchmark is showing the following changes: