Opened 2 hours ago

Last modified 94 minutes ago

#37435 assigned Cleanup/optimization

Speed up post-commit callback dispatch — at Version 1

Reported by: harshit gupta Owned by:
Component: Database layer (models, ORM) Version: dev
Severity: Normal Keywords:
Cc: Triage Stage: Unreviewed
Has patch: yes Needs documentation: no
Needs tests: no Patch needs improvement: no
Easy pickings: no UI/UX: no

Description (last modified by harshit gupta)

Summary:
Speed up post-commit callback dispatch

Description:

BaseDatabaseWrapper.run_and_clear_commit_hooks() drains its detached callback list with pop(0). Each removal shifts the remaining entries, so queue-management overhead is O(N²) in the number of callbacks.

This can matter for bulk work that registers a post-commit callback per item, such as deferred cache updates. Callback work itself may dominate in applications that make network or task-broker calls; those costs are not part of the queue-complexity claim.

A local patch keeps the existing list-detachment step and visits each entry once, clearing its slot before calling it. This makes queue-management overhead O(N), without allocating another queue. Clearing the slot prevents completed callbacks from being retained by the detached list or a saved traceback from a later callback failure.

The existing robust/nonrobust handling stays unchanged. The list allocation still lasts until the frame is released, and failing or unrun callbacks can still be retained by a saved traceback. An external alias to this private list would see None slots instead of a shrinking list, so private-list inspection and direct mutation are not claimed to be identical.

Reproduction and measurements:

Checked against d430084791a61b31cecec132bce3dca19194f7f2 on Python 3.14.5, Windows, SQLite.

The runner compiles the original method from that commit in memory and compares it with the changed method. Its benchmarks include transaction begin/commit, callback registration and callbacks updating an in-memory dictionary. Two workloads give callbacks separately captured bytearrays to check retained state. These are controlled workloads, not production traffic, model writes or broker calls.

There are 48 rows: 12 workloads measured separately on each of four CPU pairs. Each row has five paired timing samples per implementation and five separate tracemalloc measurements. Only two physical cores are allowed at a time, with one process and rotating CPU pairs. All rows, including slower cases, are retained. Different CPU pairs are not pooled. Peaks describe newly traced Python allocations, not total process RAM.

Verification:

The same 195 focused tests ran on the original and changed methods, with no failures and the same 10 SQLite skips.
Five regression tests cover exceptions, execution ordering and callback lifetimes, including retained tracebacks and error logs.
468 before/after comparisons matched callback order, error/log outcomes, queue state and transaction state.
Black, isort, flake8 and git diff --check passed for the changed files.
The full Django suite and other database backends have not been run. The benchmark runner, raw samples and full result table are available to attach for reproduction.

Would this slot-clearing approach be acceptable, particularly the retained list allocation and the change visible through aliases to the private queue?

Assistance disclosure:

Codex helped run benchmarks, and draft the description and submission helper.

CODE CHANGES IN THE PR :-

  • MEANS REMOVED

+ MEANS ADDED

-     while current_run_on_commit:
-        _, func, robust = current_run_on_commit.pop(0)


+    for index, (_, func, robust) in enumerate(current_run_on_commit):
+        current_run_on_commit[index] = None         # Release the queue's reference before invoking the callback.

BENCHMARK CODE { THE RESULT OF THE BENCHMARK AT THE BOTTOM } :-

from __future__ import annotations

import argparse
import ast
import gc
import hashlib
import importlib.metadata
import json
import logging
import math
import os
import platform
import runpy
import statistics
import subprocess
import sys
import time
import tracemalloc
import types
import unittest
from pathlib import Path

if not __debug__:
    raise RuntimeError(
        "Run these checks without -O or PYTHONOPTIMIZE; assertions must be enabled."
    )

HERE = Path(__file__).resolve().parent
REPO = Path(
    os.environ.get("DJANGO_REVIEW_REPO", HERE.parent / "django-current")
).resolve()
BASE = "d430084791a61b31cecec132bce3dca19194f7f2"
FILES = ("django/db/backends/base/base.py", "tests/transaction_hooks/tests.py")
REPEATS = 5
COUNTS = (0, 1, 2, 10, 100, 500, 1000, 2000, 5000, 10000)
WORKLOADS = [(count, 0) for count in COUNTS] + [(100, 65536), (1000, 1024)]

CPU_HELPER = HERE / "benchmark_cpu.py"
if not CPU_HELPER.exists():
    CPU_HELPER = HERE.parent / "benchmarks/benchmark_cpu.py"
sys.path.insert(0, str(CPU_HELPER.parent))
from benchmark_cpu import RotatingCores

sys.path.insert(0, str(REPO))
from django.db.backends.base import base as base_module

if Path(base_module.__file__).resolve() != REPO / FILES[0]:
    raise RuntimeError("The wrong Django checkout was imported")


def digest(path):
    return hashlib.sha256(path.read_bytes()).hexdigest()


def metadata():
    return {
        "base_commit": BASE,
        "python": platform.python_version(),
        "platform": platform.platform(),
        "source_hashes": {name: digest(REPO / name) for name in FILES},
        "runner_sha256": digest(Path(__file__)),
        "affinity_helper_sha256": digest(CPU_HELPER),
        "dependencies": {
            name: importlib.metadata.version(name)
            for name in ("Django", "asgiref", "sqlparse", "tzdata")
        },
    }


def methods():
    source = subprocess.check_output(
        ["git", "show", f"{BASE}:{FILES[0]}"],
        cwd=REPO,
        text=True,
        encoding="utf-8",
    )
    cls = next(
        node
        for node in ast.parse(source).body
        if isinstance(node, ast.ClassDef) and node.name == "BaseDatabaseWrapper"
    )
    node = next(
        node
        for node in cls.body
        if isinstance(node, ast.FunctionDef)
        and node.name == "run_and_clear_commit_hooks"
    )
    namespace = dict(vars(base_module))
    exec(
        compile(
            ast.Module(body=[node], type_ignores=[]),
            "<pinned upstream callback dispatch>",
            "exec",
        ),
        namespace,
    )
    compiled = namespace[node.name]
    original = types.FunctionType(
        compiled.__code__, vars(base_module), compiled.__name__, compiled.__defaults__
    )
    return {
        "base": original,
        "pr": base_module.BaseDatabaseWrapper.run_and_clear_commit_hooks,
    }


def save(name, value):
    (HERE / name).write_text(json.dumps(value, indent=2), encoding="utf-8")


def run_tests(version, cores):
    choices = methods()
    base_module.BaseDatabaseWrapper.run_and_clear_commit_hooks = choices[version]
    report = {
        **metadata(),
        "version": version,
        "backend": "SQLite",
        "parallel_workers": 1,
    }
    original_start = unittest.TestResult.startTest
    original_run = unittest.TextTestRunner.run
    count = 0

    def start_test(result, test):
        nonlocal count
        if count % 50 == 0:
            cores.rotate()
        count += 1
        return original_start(result, test)

    def run_suite(runner, suite):
        result = original_run(runner, suite)
        report.update(
            tests_run=result.testsRun,
            failures=[test.id() for test, _ in result.failures],
            errors=[test.id() for test, _ in result.errors],
            skipped=[test.id() for test, _ in result.skipped],
            expected_failures=len(result.expectedFailures),
            unexpected_successes=[test.id() for test in result.unexpectedSuccesses],
        )
        return result

    unittest.TestResult.startTest = start_test
    unittest.TextTestRunner.run = run_suite
    os.chdir(REPO / "tests")
    sys.path.insert(0, str(REPO / "tests"))
    sys.argv = [
        "runtests.py",
        "transaction_hooks",
        "transactions",
        "backends.base",
        "--settings=test_sqlite",
        "--parallel=1",
        "--verbosity=1",
    ]
    try:
        try:
            runpy.run_path(str(REPO / "tests/runtests.py"), run_name="__main__")
            code = 0
        except SystemExit as error:
            code = error.code or 0
    finally:
        unittest.TestResult.startTest = original_start
        unittest.TextTestRunner.run = original_run
        base_module.BaseDatabaseWrapper.run_and_clear_commit_hooks = choices["pr"]
    report["exit_code"] = code
    save(f"tests_{version}.json", report)
    return code


def setup():
    from django.conf import settings

    settings.configure(
        DATABASES={
            "default": {"ENGINE": "django.db.backends.sqlite3", "NAME": ":memory:"}
        },
        INSTALLED_APPS=[],
        SECRET_KEY="local-callback-check",
        USE_TZ=True,
    )
    import django

    django.setup()
    from django.db import connection

    connection.ensure_connection()


class CallbackAbort(BaseException):
    pass


def outcome(method, count, failure_at, robust_mode, nested_mode, exception_class):
    from django.db import connection, transaction

    previous = base_module.BaseDatabaseWrapper.run_and_clear_commit_hooks
    base_module.BaseDatabaseWrapper.run_and_clear_commit_hooks = method
    events, errors = [], []

    class Capture(logging.Handler):
        def emit(self, record):
            errors.append(
                (
                    record.getMessage(),
                    type(record.exc_info[1]).__name__,
                    str(record.exc_info[1]),
                )
            )

    handler = Capture()
    logger = base_module.logger
    logger.addHandler(handler)
    old_propagate = logger.propagate
    logger.propagate = False
    error = None

    def callback(index):
        events.append(("callback", index))
        if index == failure_at:
            raise exception_class("callback failed")
        if nested_mode != "none" and index % 3 == 0:
            if nested_mode == "transaction":
                with transaction.atomic():
                    transaction.on_commit(lambda: events.append(("nested", index)))
            else:
                transaction.on_commit(lambda: events.append(("nested", index)))

    try:
        try:
            with transaction.atomic():
                for index in range(count):
                    robust = robust_mode == "all" or (
                        robust_mode == "alternating" and index % 2 == 0
                    )
                    transaction.on_commit(
                        lambda index=index: callback(index), robust=robust
                    )
        except BaseException as caught:
            error = type(caught).__name__, str(caught)
        return (
            events,
            error,
            errors,
            len(connection.run_on_commit),
            connection.in_atomic_block,
            connection.get_autocommit(),
        )
    finally:
        logger.removeHandler(handler)
        logger.propagate = old_propagate
        base_module.BaseDatabaseWrapper.run_and_clear_commit_hooks = previous


def verify(choices, cores):
    comparisons = 0
    for count in (0, 1, 2, 3, 10, 30, 100, 250):
        cores.rotate()
        positions = (
            list(dict.fromkeys((None, 0, count // 2, count - 1))) if count else [None]
        )
        for failure_at in positions:
            for robust in ("none", "all", "alternating"):
                for nested in ("none", "immediate", "transaction"):
                    for exception in (ValueError, CallbackAbort):
                        old = outcome(
                            choices["base"],
                            count,
                            failure_at,
                            robust,
                            nested,
                            exception,
                        )
                        new = outcome(
                            choices["pr"], count, failure_at, robust, nested, exception
                        )
                        if old != new:
                            raise AssertionError(
                                (count, failure_at, robust, nested, exception, old, new)
                            )
                        comparisons += 1
    print(f"Differential checks: {comparisons} matched", flush=True)
    result = {**metadata(), "comparisons": comparisons, "differences": []}
    save("differential.json", result)
    return result


def workflow(method, count, payload_size):
    from django.db import connection, transaction

    previous = base_module.BaseDatabaseWrapper.run_and_clear_commit_hooks
    base_module.BaseDatabaseWrapper.run_and_clear_commit_hooks = method
    updates = {}
    try:
        with transaction.atomic():
            for index in range(count):
                if payload_size:
                    transaction.on_commit(
                        lambda index=index, payload=bytearray(
                            payload_size
                        ): updates.__setitem__(f"record:{index}", index + len(payload))
                    )
                else:
                    transaction.on_commit(
                        lambda index=index: updates.__setitem__(
                            f"record:{index}", index
                        )
                    )
        if connection.run_on_commit:
            raise AssertionError("Pending callbacks after commit")
        return list(updates.items())
    finally:
        base_module.BaseDatabaseWrapper.run_and_clear_commit_hooks = previous


def write_report(report):
    save("benchmark_results.json", report)
    lines = [
        "# Post-commit callback benchmark",
        "",
        "Final slot-clearing implementation, not the earlier plain-loop prototype.",
        "",
        f"Python {report['python']}, Windows, SQLite. Five paired timing samples and five separate tracemalloc peaks per version, workload and fixed CPU pair. Short calls are batched. GC is enabled. Only two physical cores are allowed at once; pairs rotate between rows. One process, not parallel workers. Different CPU pairs are not pooled.",
        "",
        "The complete workflow includes transaction begin/commit, callback registration and callbacks updating a dictionary. Payload cases give each callback a separate bytearray of the stated size. No model writes, network or task broker. Large queues are stress cases, not typical requests. Peak memory is newly traced Python allocations, not total process RAM. All measured rows, including losses, are shown.",
        "",
        "| Callbacks | Captured payload/callback | CPU mask | Base time | Changed time | Speed | Base peak | Changed peak | Memory |",
        "|---:|---:|---|---:|---:|---|---:|---:|---|",
    ]
    for row in report["results"]:
        old, new = (statistics.median(row[v]["seconds"]) for v in ("base", "pr"))
        old_peak, new_peak = (
            statistics.median(row[v]["peak_bytes"]) for v in ("base", "pr")
        )
        speed = f"{old / new:.3f}x faster" if new < old else f"{new / old:.3f}x slower"
        memory = (
            "same"
            if old_peak == new_peak
            else (
                f"{new_peak / old_peak:.6f}x more"
                if new_peak > old_peak
                else f"{old_peak / new_peak:.6f}x less"
            )
        )
        lines.append(
            f"| {row['callbacks']:,} | {row['payload_bytes']:,} B | {row['mask']} | {old * 1000:.4f} ms | {new * 1000:.4f} ms | {speed} | {old_peak / 1024:.3f} KiB | {new_peak / 1024:.3f} KiB | {memory} |"
        )
    lines += [
        "",
        f"Completed {len(report['results'])}/{report['planned_rows']} rows. Complete: {report['complete']}.",
        "",
        "Raw samples and source, runner and helper hashes are in benchmark_results.json.",
        "",
    ]
    (HERE / "benchmark_results.md").write_text("\n".join(lines), encoding="utf-8")


def benchmark(choices, cores):
    report = {
        **metadata(),
        "repeats": REPEATS,
        "planned_rows": len(WORKLOADS) * len(cores.pairs),
        "cpu_pairs": [hex(mask) for mask in cores.pairs],
        "complete": False,
        "results": [],
    }
    write_report(report)
    for count, payload in WORKLOADS:
        for _ in cores.pairs:
            mask = cores.rotate()
            expected = workflow(choices["base"], count, payload)
            assert workflow(choices["pr"], count, payload) == expected
            start = time.perf_counter()
            workflow(choices["base"], count, payload)
            batch = max(
                1, min(1000, math.ceil(0.05 / max(time.perf_counter() - start, 1e-9)))
            )
            row = {
                "callbacks": count,
                "payload_bytes": payload,
                "mask": mask,
                "batch_size": batch,
                "outputs_matched": True,
                "base": {"seconds": [], "peak_bytes": []},
                "pr": {"seconds": [], "peak_bytes": []},
            }
            for repeat in range(REPEATS):
                for version in (("base", "pr") if repeat % 2 == 0 else ("pr", "base")):
                    gc.collect()
                    start = time.perf_counter()
                    for _ in range(batch):
                        output = workflow(choices[version], count, payload)
                    elapsed = (time.perf_counter() - start) / batch
                    assert output == expected
                    row[version]["seconds"].append(elapsed)
                progress = (
                    (len(report["results"]) + (repeat + 1) / REPEATS)
                    / report["planned_rows"]
                    * 100
                )
                print(
                    f"{count} callbacks, {payload} B, {mask}: base + changed {repeat + 1}/5; overall {progress:.1f}%",
                    flush=True,
                )
            for repeat in range(REPEATS):
                for version in (("base", "pr") if repeat % 2 == 0 else ("pr", "base")):
                    gc.collect()
                    tracemalloc.start()
                    try:
                        output = workflow(choices[version], count, payload)
                        peak = tracemalloc.get_traced_memory()[1]
                    finally:
                        tracemalloc.stop()
                    assert output == expected
                    row[version]["peak_bytes"].append(peak)
            report["results"].append(row)
            write_report(report)
            print(
                f"Saved row {len(report['results'])}/{report['planned_rows']}",
                flush=True,
            )
    report["complete"] = True
    write_report(report)


def validate():
    current = metadata()
    for name in (
        "tests_base.json",
        "tests_pr.json",
        "differential.json",
        "benchmark_results.json",
    ):
        result = json.loads((HERE / name).read_text(encoding="utf-8"))
        for key in (
            "base_commit",
            "python",
            "source_hashes",
            "runner_sha256",
            "affinity_helper_sha256",
            "dependencies",
        ):
            if result[key] != current[key]:
                raise RuntimeError(f"Stale {name}: {key} changed; run -Check again")
        if name.startswith("tests_"):
            assert result["exit_code"] == 0 and result["tests_run"] > 0
            assert (
                not result["failures"]
                and not result["errors"]
                and not result["unexpected_successes"]
            )
        elif name == "differential.json":
            assert result["comparisons"] >= 300 and not result["differences"]
        else:
            assert (
                result["complete"] and len(result["results"]) == result["planned_rows"]
            )
            assert result["planned_rows"] == len(WORKLOADS) * len(result["cpu_pairs"])
            seen = set()
            for row in result["results"]:
                key = row["callbacks"], row["payload_bytes"], row["mask"]
                assert key not in seen and row["outputs_matched"]
                seen.add(key)
                for version in ("base", "pr"):
                    assert (
                        len(row[version]["seconds"])
                        == len(row[version]["peak_bytes"])
                        == REPEATS
                    )
            write_report(result)
    print(
        "Source hashes, tests, differential checks and complete raw benchmarks validated."
    )


def main():
    parser = argparse.ArgumentParser(description=__doc__)
    group = parser.add_mutually_exclusive_group(required=True)
    group.add_argument("--tests", choices=("base", "pr"))
    group.add_argument("--verify", action="store_true")
    group.add_argument("--benchmark", action="store_true")
    group.add_argument("--validate", action="store_true")
    args = parser.parse_args()
    if args.validate:
        validate()
        return 0
    cores = RotatingCores()
    try:
        if args.tests:
            return run_tests(args.tests, cores)
        setup()
        choices = methods()
        if args.verify:
            verify(choices, cores)
        else:
            benchmark(choices, cores)
        return 0
    finally:
        cores.restore()


if __name__ == "__main__":
    raise SystemExit(main())

BENCHMARKS

| .Callbacks. | .Captured payload/callback. | .CPU mask. | ..Base time.. | .Changed time. | ....Speed.... | .Base peak.. | .Changed peak. | .Memory. |
| ----------- | --------------------------- | ---------- | ------------- | -------------- | ------------- | ------------ | -------------- | -------- |
| .....0..... | ............0 B............ | ...0xf.... | ..0.0292 ms.. | ..0.0461 ms... | 1.578x slower | .2.406 KiB.. | ..2.406 KiB... | ..same.. |
| .....0..... | ............0 B............ | ...0xf0... | ..0.0178 ms.. | ..0.0181 ms... | 1.014x slower | .2.406 KiB.. | ..2.406 KiB... | ..same.. |
| .....0..... | ............0 B............ | ..0x300... | ..0.0936 ms.. | ..0.0999 ms... | 1.067x slower | .2.406 KiB.. | ..2.406 KiB... | ..same.. |
| .....0..... | ............0 B............ | ..0xc00... | ..0.0970 ms.. | ..0.0982 ms... | 1.012x slower | .2.406 KiB.. | ..2.406 KiB... | ..same.. |
| .....1..... | ............0 B............ | ...0xf.... | ..0.0329 ms.. | ..0.0368 ms... | 1.118x slower | .2.938 KiB.. | ..2.938 KiB... | ..same.. |
| .....1..... | ............0 B............ | ...0xf0... | ..0.0291 ms.. | ..0.0307 ms... | 1.056x slower | .2.938 KiB.. | ..2.938 KiB... | ..same.. |
| .....1..... | ............0 B............ | ..0x300... | ..0.1181 ms.. | ..0.1092 ms... | 1.082x faster | .2.938 KiB.. | ..2.938 KiB... | ..same.. |
| .....1..... | ............0 B............ | ..0xc00... | ..0.1121 ms.. | ..0.1123 ms... | 1.002x slower | .2.938 KiB.. | ..2.938 KiB... | ..same.. |
| .....2..... | ............0 B............ | ...0xf.... | ..0.0516 ms.. | ..0.0520 ms... | 1.006x slower | .3.492 KiB.. | ..3.492 KiB... | ..same.. |
| .....2..... | ............0 B............ | ...0xf0... | ..0.0544 ms.. | ..0.0579 ms... | 1.064x slower | .3.492 KiB.. | ..3.492 KiB... | ..same.. |
| .....2..... | ............0 B............ | ..0x300... | ..0.1270 ms.. | ..0.1260 ms... | 1.008x faster | .3.492 KiB.. | ..3.492 KiB... | ..same.. |
| .....2..... | ............0 B............ | ..0xc00... | ..0.1317 ms.. | ..0.1247 ms... | 1.056x faster | .3.492 KiB.. | ..3.492 KiB... | ..same.. |
| ....10..... | ............0 B............ | ...0xf.... | ..0.0767 ms.. | ..0.0923 ms... | 1.204x slower | .8.023 KiB.. | ..8.023 KiB... | ..same.. |
| ....10..... | ............0 B............ | ...0xf0... | ..0.0749 ms.. | ..0.0622 ms... | 1.205x faster | .8.023 KiB.. | ..8.023 KiB... | ..same.. |
| ....10..... | ............0 B............ | ..0x300... | ..0.2008 ms.. | ..0.2087 ms... | 1.039x slower | .8.023 KiB.. | ..8.023 KiB... | ..same.. |
| ....10..... | ............0 B............ | ..0xc00... | ..0.1976 ms.. | ..0.1916 ms... | 1.031x faster | .8.023 KiB.. | ..8.023 KiB... | ..same.. |
| ....100.... | ............0 B............ | ...0xf.... | ..0.3739 ms.. | ..0.3492 ms... | 1.071x faster | .58.664 KiB. | ..58.664 KiB.. | ..same.. |
| ....100.... | ............0 B............ | ...0xf0... | ..0.3486 ms.. | ..0.3282 ms... | 1.062x faster | .58.664 KiB. | ..58.664 KiB.. | ..same.. |
| ....100.... | ............0 B............ | ..0x300... | ..0.9927 ms.. | ..0.9764 ms... | 1.017x faster | .58.664 KiB. | ..58.664 KiB.. | ..same.. |
| ....100.... | ............0 B............ | ..0xc00... | ..0.9607 ms.. | ..1.0001 ms... | 1.041x slower | .58.664 KiB. | ..58.664 KiB.. | ..same.. |
| ....500.... | ............0 B............ | ...0xf.... | ..1.9030 ms.. | ..1.7034 ms... | 1.117x faster | 291.352 KiB. | .291.352 KiB.. | ..same.. |
| ....500.... | ............0 B............ | ...0xf0... | ..1.5466 ms.. | ..1.5289 ms... | 1.012x faster | 291.352 KiB. | .291.352 KiB.. | ..same.. |
| ....500.... | ............0 B............ | ..0x300... | ..4.8458 ms.. | ..4.4118 ms... | 1.098x faster | 291.352 KiB. | .291.352 KiB.. | ..same.. |
| ....500.... | ............0 B............ | ..0xc00... | ..4.6101 ms.. | ..4.3460 ms... | 1.061x faster | 291.352 KiB. | .291.352 KiB.. | ..same.. |
| ...1,000... | ............0 B............ | ...0xf.... | ..2.7289 ms.. | ..2.9886 ms... | 1.095x slower | 588.852 KiB. | .588.852 KiB.. | ..same.. |
| ...1,000... | ............0 B............ | ...0xf0... | ..4.2581 ms.. | ..4.2725 ms... | 1.003x slower | 588.852 KiB. | .588.852 KiB.. | ..same.. |
| ...1,000... | ............0 B............ | ..0x300... | .10.2119 ms.. | ..11.7489 ms.. | 1.151x slower | 588.852 KiB. | .588.852 KiB.. | ..same.. |
| ...1,000... | ............0 B............ | ..0xc00... | ..9.3432 ms.. | ..9.6990 ms... | 1.038x slower | 588.852 KiB. | .588.852 KiB.. | ..same.. |
| ...2,000... | ............0 B............ | ...0xf.... | .13.1922 ms.. | ..7.3413 ms... | 1.797x faster | 1181.945 KiB | .1181.945 KiB. | ..same.. |
| ...2,000... | ............0 B............ | ...0xf0... | .16.3497 ms.. | ..9.6548 ms... | 1.693x faster | 1181.945 KiB | .1181.945 KiB. | ..same.. |
| ...2,000... | ............0 B............ | ..0x300... | .21.9616 ms.. | ..20.7825 ms.. | 1.057x faster | 1181.945 KiB | .1181.945 KiB. | ..same.. |
| ...2,000... | ............0 B............ | ..0xc00... | .19.8812 ms.. | ..20.8758 ms.. | 1.050x slower | 1181.945 KiB | .1181.945 KiB. | ..same.. |
| ...5,000... | ............0 B............ | ...0xf.... | .60.9961 ms.. | ..21.3436 ms.. | 2.858x faster | 2964.852 KiB | .2964.852 KiB. | ..same.. |
| ...5,000... | ............0 B............ | ...0xf0... | .58.4972 ms.. | ..21.5353 ms.. | 2.716x faster | 2964.852 KiB | .2964.852 KiB. | ..same.. |
| ...5,000... | ............0 B............ | ..0x300... | .58.3460 ms.. | ..51.6651 ms.. | 1.129x faster | 2964.852 KiB | .2964.852 KiB. | ..same.. |
| ...5,000... | ............0 B............ | ..0xc00... | .58.5147 ms.. | ..50.5312 ms.. | 1.158x faster | 2964.852 KiB | .2964.852 KiB. | ..same.. |
| ..10,000... | ............0 B............ | ...0xf.... | .205.7161 ms. | ..45.2413 ms.. | 4.547x faster | 5936.820 KiB | .5936.820 KiB. | ..same.. |
| ..10,000... | ............0 B............ | ...0xf0... | .192.8131 ms. | ..43.0341 ms.. | 4.480x faster | 5936.820 KiB | .5936.820 KiB. | ..same.. |
| ..10,000... | ............0 B............ | ..0x300... | .140.0813 ms. | .107.9537 ms.. | 1.298x faster | 5936.820 KiB | .5936.852 KiB. | ..same.. |
| ..10,000... | ............0 B............ | ..0xc00... | .136.9749 ms. | .120.7018 ms.. | 1.135x faster | 5936.820 KiB | .5936.820 KiB. | ..same.. |
| ....100.... | .........65,536 B.......... | ...0xf.... | ..6.7584 ms.. | ..5.8872 ms... | 1.148x faster | 6465.012 KiB | .6465.012 KiB. | ..same.. |
| ....100.... | .........65,536 B.......... | ...0xf0... | ..4.3995 ms.. | ..4.5368 ms... | 1.031x slower | 6465.012 KiB | .6465.012 KiB. | ..same.. |
| ....100.... | .........65,536 B.......... | ..0x300... | ..7.0298 ms.. | ..6.6261 ms... | 1.061x faster | 6465.012 KiB | .6465.012 KiB. | ..same.. |
| ....100.... | .........65,536 B.......... | ..0xc00... | ..6.9050 ms.. | ..6.7708 ms... | 1.020x faster | 6465.012 KiB | .6465.012 KiB. | ..same.. |
| ...1,000... | ..........1,024 B.......... | ...0xf.... | ..4.9156 ms.. | ..4.6207 ms... | 1.064x faster | 1652.328 KiB | .1652.328 KiB. | ..same.. |
| ...1,000... | ..........1,024 B.......... | ...0xf0... | ..5.1891 ms.. | ..4.9479 ms... | 1.049x faster | 1652.328 KiB | .1652.328 KiB. | ..same.. |
| ...1,000... | ..........1,024 B.......... | ..0x300... | .11.8471 ms.. | ..11.5129 ms.. | 1.029x faster | 1652.328 KiB | .1652.328 KiB. | ..same.. |
| ...1,000... | ..........1,024 B.......... | ..0xc00... | .11.9090 ms.. | ..11.3502 ms.. | 1.049x faster | 1652.328 KiB | .1652.328 KiB. | ..same.. |

Change History (1)

comment:1 by harshit gupta, 2 hours ago

Description: modified (diff)
Note: See TracTickets for help on using tickets.
Back to Top