Skip to content

Conversation

@colesbury
Copy link
Contributor

@colesbury colesbury commented Dec 23, 2025

Count the number of Element attribute accesses as a proxy for work done. With double the amount of work, a ratio of 2.0 indicates linear scaling and 4.0 quadratic scaling. Use 3.2 as an intermediate threshold.

Count the number of Element attribute accesses as a proxy for work done.
With double the amount of work, a ratio of 2.0 indicates linear scaling
and 4.0 quadratic scaling. Use 3.2 as an intermediate threshold.
@bedevere-app bedevere-app bot added the tests Tests in the Lib/test dir label Dec 23, 2025
@colesbury colesbury marked this pull request as ready for review December 23, 2025 16:20
@colesbury colesbury requested a review from gpshead December 23, 2025 16:20
@colesbury
Copy link
Contributor Author

The context for this is that I'm trying to get the full test suite in a state where we can run it under TSan. TSan tends to be much slower, so timing tests are even more flaky.

Let me know what you think about this appraoch.

For context, I used ChatGPT to generate the test and then edited it. (It was pretty good, but not perfect).

@sethmlarson
Copy link
Contributor

Thanks @colesbury, did you happen to run this test with the commit in question reverted to see that this test would indeed catch quadratic behavior in number of accesses?

@colesbury
Copy link
Contributor Author

Yes, if you revert the fix then it fails with:

======================================================================
FAIL: testAppendChildNoQuadraticComplexity (test.test_minidom.MinidomTest.testAppendChildNoQuadraticComplexity)
----------------------------------------------------------------------
Traceback (most recent call last):
  File "/home/sgross/cpython/Lib/test/test_minidom.py", line 207, in testAppendChildNoQuadraticComplexity
    self.assertLess(
    ~~~~~~~~~~~~~~~^
        max(r1, r2), 3.2,
        ^^^^^^^^^^^^^^^^^
        msg=f"Possible quadratic behavior: work={w1,w2,w3} ratios={r1,r2}"
        ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
    )
    ^
AssertionError: 3.9883495145631067 not less than 3.2 : Possible quadratic behavior: work=(1060864, 4218880, 16826368) ratios=(3.976833976833977, 3.9883495145631067)

Copy link
Contributor

@sethmlarson sethmlarson left a comment

Choose a reason for hiding this comment

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

LGTM, thanks @colesbury!

@gpshead gpshead added needs backport to 3.13 bugs and security fixes needs backport to 3.14 bugs and security fixes labels Dec 23, 2025

@support.requires_resource('cpu')
def testAppendChildNoQuadraticComplexity(self):
# Don't use wall-clock timing (too flaky). Instead count a proxy for the
Copy link
Member

Choose a reason for hiding this comment

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

Elide the model's explanatory comment about the old no longer present code.

self.assertEqual(dom.documentElement.childNodes[-1].data, "Hello")
dom.unlink()

@support.requires_resource('cpu')
Copy link
Member

Choose a reason for hiding this comment

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

why remove this? i realize the test shouldn't be "slow" now, but the intent of adding it was to mark it as a test that may not be suitable for slow builds and loaded systems. some of our buildbots run with the cpu resource disabled for that reason. it's more of a performance test no matter how it is implemented.

for tests where it isn't platform specific and we just need something in our support tiers to catch an unlikely regression the issue, resource tags save effort.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I'll add it back. I removed it because my understanding was that "cpu" was for tests that were CPU-heavy, and now this test is very fast (~30 ms in a debug build).

total_calls += 1
return object.__getattribute__(self, attr)

with support.swap_attr(Element, "__getattribute__", getattribute_counter):
Copy link
Member

Choose a reason for hiding this comment

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

doing this is... gross, but likely works. your assertGreater(w1, 0) below covers the case when it stops doing so. LGTM

the other thought i was having as i did the simple "raise the timeout to 4s" to unblock the earlier ones was that we don't need absolute times at all. measuring relative time taken as we increase the amount of work to ensure that it scales - similar to this scaling measurement of attribute accesses - would be sufficient and should produce similar results on super slow builds or heavily loaded systems. it would still be time based (so maybe use resource.getrusage(resource.RUSAGE_SELF).ru_utime instead of time. APIs... though i suspect rusage is low-res) but far less "works on my system"

we have a smattering of other timing based cpu performancy tests in the repo. i don't think we've codified a reusable best practice.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

measuring relative time taken as we increase the amount of work ... would be sufficient

It'd be great to have some sort of standard best practice for this. If resource.getrusage(resource.RUSAGE_SELF).ru_utime isn't too affected by other processes, that would be good.

Some other things I was thinking about:

  1. Count cycles (via perf_event_open?)
  2. Count bytecodes executed (by instrumenting ceval.c in debug builds)

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

Labels

awaiting core review needs backport to 3.13 bugs and security fixes needs backport to 3.14 bugs and security fixes skip news tests Tests in the Lib/test dir

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants