Skip to content

gh-139122: Implement uuid6() in C - #160153

Draft
1st1 wants to merge 6 commits into
python:mainfrom
1st1:uuid6-c
Draft

1st1 wants to merge 6 commits into
python:mainfrom
1st1:uuid6-c

Conversation

@1st1

@1st1 1st1 commented Oct 21, 2026 •

Copy link
Copy Markdown
Member

Follow-up to #139123; refs #139122.

Add a C implementation of uuid.uuid6(), keeping the pure Python implementation as a fallback. Both implementations now use OS entropy for default clock sequences. The C implementation caches the default node ID per module, with a separate mutex for initialization and an atomic cache lookup. Critical sections around shared timestamp and entropy state stay short, and Python callbacks run outside the timestamp update.

The tests compare the Python and C implementations byte for byte with matching time and entropy streams, including repeated and backward timestamps, argument masking, entropy buffer refills, and interleaved UUID4/6/7 calls. Additional tests cover callback failures, node cache initialization and reset, reentrancy, and concurrent calls.

Validation:

  • Full test_uuid suite passed on debug GIL and free-threaded builds, including -R 3:3 reference leak checks.
  • Full suite passed on non-debug -O3 GIL and free-threaded builds; the expanded equivalence tests also passed on both.
  • Argument Clinic, generated global strings, Ruff, documentation lint, and strict integer-conversion compiler checks passed.

The benchmark below generates UUIDs for five seconds per implementation, retains them in per-thread sets, and checks for duplicates across all threads afterward. Each implementation runs in a fresh process. Sample results on non-debug -O3 builds (no PGO):

Build Threads Python UUIDs C UUIDs
GIL 1 2,710,528 23,790,592
GIL 8 1,037,312 24,800,256
Free-threaded 1 2,740,224 22,412,288
Free-threaded 8 2,233,856 8,353,792

These are individual runs and include set insertion costs. No duplicates were detected in the C runs. The eight-thread pure Python run produced 13 duplicates, exposing an existing race in its timestamp update; fixing that fallback race remains separate work. The new deterministic equivalence tests cover sequential behavior, while the concurrency tests exercise the C implementation.

Benchmark script (save as bench_uuid6.py)
"""Generate UUIDv6 values for five seconds and check for duplicates.

Run with the CPython build being measured, for example::

    ./python bench_uuid6.py
    ./python bench_uuid6.py --threads 8

Each implementation runs in a fresh subprocess. Workers keep separate sets
during generation, then merge them to detect duplicates across threads too.
Reported rates include set insertion; merging happens outside the timed run.
The benchmark retains all unique UUIDs and can consume substantial memory.
"""

import argparse
import json
import subprocess
import sys
import sysconfig
import threading
import time


def measure(implementation, seconds, threads, fixed_fields):
    from concurrent.futures import ThreadPoolExecutor

    if implementation == 'python':
        from test.support.import_helper import import_fresh_module
        uuid = import_fresh_module('uuid', blocked=['_uuid'])
        assert uuid.UUID is uuid._py_UUID
        assert uuid._c_uuid6 is None
    else:
        import uuid
        assert uuid._c_uuid6 is not None, 'C UUIDv6 implementation unavailable'
        assert uuid.uuid6 is uuid._c_uuid6

    uuid.getnode()  # Exclude initial hardware-address discovery.
    generate = uuid.uuid6
    if fixed_fields:
        def generate():
            return uuid.uuid6(node=0, clock_seq=0)

    start = deadline = 0.0

    def start_timer():
        nonlocal start, deadline
        start = time.perf_counter()
        deadline = start + seconds

    barrier = threading.Barrier(threads + 1, action=start_timer)

    def worker():
        seen = set()
        add = seen.add
        count = 0
        barrier.wait()
        while time.perf_counter() < deadline:
            # Amortize clock checks without exceeding the deadline by much.
            for _ in range(256):
                add(generate())
            count += 256
        return seen, count

    with ThreadPoolExecutor(threads) as pool:
        futures = [pool.submit(worker) for _ in range(threads)]
        barrier.wait()
        results = [future.result() for future in futures]
        elapsed = time.perf_counter() - start

    unique, generated = results.pop()
    duplicate_examples = []
    while results:
        seen, count = results.pop()
        if len(duplicate_examples) < 5:
            overlap = unique.intersection(seen)
            duplicate_examples.extend(
                str(value) for value in list(overlap)[:5 - len(duplicate_examples)])
        unique.update(seen)
        generated += count
    return {
        'implementation': implementation,
        'threads': threads,
        'gil_enabled': sys._is_gil_enabled(),
        'seconds': elapsed,
        'generated': generated,
        'unique': len(unique),
        'duplicates': generated - len(unique),
        'duplicate_examples': duplicate_examples,
        'per_second': generated / elapsed,
    }


def main():
    parser = argparse.ArgumentParser(description=__doc__)
    parser.add_argument('--seconds', type=float, default=5.0)
    parser.add_argument('--threads', type=int, default=1)
    parser.add_argument('--implementation', choices=('python', 'c', 'both'),
                        default='both')
    parser.add_argument('--fixed-fields', action='store_true',
                        help='use node=0 and clock_seq=0 to isolate timestamp uniqueness')
    parser.add_argument('--worker', action='store_true', help=argparse.SUPPRESS)
    args = parser.parse_args()
    if not 0 < args.seconds < float('inf') or args.threads < 1:
        parser.error('seconds and threads must be positive and finite')

    if args.worker:
        result = measure(args.implementation, args.seconds, args.threads,
                         args.fixed_fields)
        print(json.dumps(result))
        return bool(result['duplicates'])

    print(f'Python {sys.version.split()[0]}, '
          f'Py_DEBUG={sysconfig.get_config_var("Py_DEBUG")}, '
          f'GIL enabled={sys._is_gil_enabled()}, threads={args.threads}')
    print(f'CFLAGS: {sysconfig.get_config_var("PY_CFLAGS")}')
    print(f'{"Implementation":<16} {"Generated":>12} {"UUID/s":>12} '
          f'{"Seconds":>9} {"Duplicates":>12}')
    failed = False
    implementations = ('python', 'c') if args.implementation == 'both' else (
        args.implementation,)
    for implementation in implementations:
        command = [sys.executable, __file__, '--worker',
                   '--implementation', implementation,
                   '--seconds', str(args.seconds), '--threads', str(args.threads)]
        if args.fixed_fields:
            command.append('--fixed-fields')
        result = subprocess.run(command, check=False, text=True,
                                stdout=subprocess.PIPE)
        if result.returncode not in (0, 1) or not result.stdout.strip():
            return result.returncode or 1
        values = json.loads(result.stdout)
        print(f'{implementation:<16} {values["generated"]:>12,} '
              f'{values["per_second"]:>12,.0f} {values["seconds"]:>9.3f} '
              f'{values["duplicates"]:>12,}', flush=True)
        for value in values['duplicate_examples']:
            print(f'  Duplicate: {value}', flush=True)
        failed |= bool(result.returncode)
    return failed


if __name__ == '__main__':
    sys.exit(main())

@bedevere-app bedevere-app Bot added the type-feature A feature request or enhancement label Oct 21, 2026
@read-the-docs-community

read-the-docs-community Bot commented Oct 22, 2026 •

Copy link
Copy Markdown

Documentation build overview

📚 cpython-previews | 🛠️ Build #35313039 | 📁 Comparing 9d77065 against main (c72b435)

  🔍 Preview build  

5 files changed · ± 5 modified

± Modified

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

Labels

type-feature A feature request or enhancement

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant