Skip to content

Use only 10 bytes of RNG in Guid.CreateVersion7 on Unix#128055

Closed
EgorBo wants to merge 2 commits into
dotnet:mainfrom
EgorBo:uuidv7-rng-10bytes
Closed

Use only 10 bytes of RNG in Guid.CreateVersion7 on Unix#128055
EgorBo wants to merge 2 commits into
dotnet:mainfrom
EgorBo:uuidv7-rng-10bytes

Conversation

@EgorBo
Copy link
Copy Markdown
Member

@EgorBo EgorBo commented May 11, 2026

Note

This PR was authored with help from GitHub Copilot (AI-generated content).

Guid.CreateVersion7 only needs 74 bits of randomness (12 bits of rand_a + 62 bits of rand_b), all of which live in the trailing 10 bytes of the GUID. The first 6 bytes are immediately overwritten with the unix_ts_ms timestamp, so any random bytes generated for them are wasted work.

This PR introduces a small CreateVersion7Random helper:

  • Apple / Linux / Browser / WASI: requests just 10 bytes from GetCryptographicallySecureRandomBytes (or GetRandomBytes on WASI), filling only bytes 6..15 of the result.
  • Windows: unchanged — keeps using CoCreateGuid via NewGuid() (the existing comment notes this is faster than BCryptGenRandom for this size class).

Motivation: on Apple platforms CCRandomGenerateBytes has a fast path for requests <= 12 bytes, so cutting the request from 16 to 10 bytes hits that path. Other Unix platforms benefit slightly from pulling less entropy from the kernel.

Also moves the ArgumentOutOfRangeException.ThrowIfNegative(unix_ts_ms, ...) check ahead of the RNG call so an invalid timestamp short-circuits without paying for entropy.

No behavioral change: the randomness fed into rand_a/rand_b is still cryptographically secure, the version/variant fields are still applied, and existing tests should continue to pass.

I'll trigger @EgorBot on this PR for -osx_arm64 -osx_x64 -linux_x64 -linux_arm64 to confirm the speedup in a follow-up comment.

Benchmark

using BenchmarkDotNet.Attributes;
using BenchmarkDotNet.Running;

BenchmarkSwitcher.FromAssembly(typeof(Bench).Assembly).Run(args);

public class Bench
{
    [Benchmark]
    public Guid CreateVersion7() => Guid.CreateVersion7();
}

Results

Apple M4 macos26:

Method Toolchain Mean Error Ratio
CreateVersion7 PR #128055 79.63 ns 0.516 ns 1.00
CreateVersion7 main 193.90 ns 0.896 ns 2.44

Intel Core i5-8500B macos15:

Method Toolchain Mean Error Ratio
CreateVersion7 PR #128055 177.5 ns 3.45 ns 1.00
CreateVersion7 main 416.2 ns 32.15 ns 2.35

UUIDv7 only consumes 74 bits of randomness, all of which fit in the trailing 10 bytes of the GUID. The first 6 bytes are immediately overwritten with the unix_ts_ms timestamp, so generating them is wasted work.

On Apple platforms CCRandomGenerateBytes has a fast path for requests <= 12 bytes, so cutting the request from 16 to 10 bytes hits that path.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot AI review requested due to automatic review settings May 11, 2026 21:43
@EgorBo
Copy link
Copy Markdown
Member Author

EgorBo commented May 11, 2026

Note

This benchmark request was generated with help from GitHub Copilot (AI-generated content).

@EgorBot -osx_arm64 -osx_x64 -linux_x64 -linux_arm64

using BenchmarkDotNet.Attributes;
using BenchmarkDotNet.Running;

BenchmarkSwitcher.FromAssembly(typeof(Bench).Assembly).Run(args);

public class Bench
{
    [Benchmark]
    public Guid CreateVersion7() => Guid.CreateVersion7();
}

@dotnet-policy-service
Copy link
Copy Markdown
Contributor

Tagging subscribers to this area: @dotnet/area-system-runtime
See info in area-owners.md if you want to be subscribed.

Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR refactors Guid.CreateVersion7(DateTimeOffset) to use a new platform-specific CreateVersion7Random helper that only sources the random bytes actually consumed by UUIDv7, avoiding wasted RNG work for bytes that are immediately overwritten by the timestamp.

Changes:

  • Updates Guid.CreateVersion7(DateTimeOffset) to call CreateVersion7Random() after validating the timestamp.
  • Adds CreateVersion7Random() implementations:
    • Windows: delegates to NewGuid() (CoCreateGuid).
    • Unix/WASI: fills only bytes 6..15 with random data (10 bytes) instead of generating a full 16-byte GUID.

Reviewed changes

Copilot reviewed 3 out of 3 changed files in this pull request and generated 3 comments.

File Description
src/libraries/System.Private.CoreLib/src/System/Guid.cs Switches v7 creation to use CreateVersion7Random() after timestamp validation; updates rationale comment.
src/libraries/System.Private.CoreLib/src/System/Guid.Unix.cs Introduces a Unix/WASI helper that requests only 10 RNG bytes for v7.
src/libraries/System.Private.CoreLib/src/System/Guid.Windows.cs Introduces a Windows helper that reuses NewGuid() for v7 randomness sourcing.

Comment thread src/libraries/System.Private.CoreLib/src/System/Guid.Unix.cs
Comment thread src/libraries/System.Private.CoreLib/src/System/Guid.Windows.cs
Comment thread src/libraries/System.Private.CoreLib/src/System/Guid.Unix.cs
@EgorBo
Copy link
Copy Markdown
Member Author

EgorBo commented May 11, 2026

cc @vcsjones @bartonjs opinions? up to 2.5x faster on mac, no impact on linux (although, probably consumes less of random from cache or whatever linux has)

@EgorBo
Copy link
Copy Markdown
Member Author

EgorBo commented May 11, 2026

I learned about the cache long ago when I looked at why Guid.NewGuid is slow on mac under XCode Instruments #106525 (comment)

@vcsjones
Copy link
Copy Markdown
Member

@EgorBo isn't that linked PR that is closed more or less the same thing as what is being proposed here?

The conclusion at the time was

personally don't think this is worth the extra complexity.

and

I don't have a principled disagreement with the change... but I agree with @tannergooding's "I personally don't think this is worth the extra complexity."

@EgorBo
Copy link
Copy Markdown
Member Author

EgorBo commented May 11, 2026

@EgorBo isn't that linked PR that is closed more or less the same thing as what is being proposed here?

The conclusion at the time was

personally don't think this is worth the extra complexity.

and

I don't have a principled disagreement with the change... but I agree with @tannergooding's "I personally don't think this is worth the extra complexity."

I found that thread after I filed the PR, but is there a lot of complexity here?

@bartonjs
Copy link
Copy Markdown
Member

Echoing my sentiment from the past... the fact that you need a #if means this is too complicated for its value.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants