Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

GH-126795: Increase the JIT threshold from 16 to 4096 #126816

Open
wants to merge 7 commits into
base: main
Choose a base branch
from

Conversation

brandtbucher
Copy link
Member

@brandtbucher brandtbucher commented Nov 14, 2024

The core change itself is simple, and results in 2.1% speed improvement and a 3.6% memory improvement for JIT builds. The bulk of this PR is just modifying most of the tests in test_capi.test_opt to remove assumptions about the warmup threshold.

@brandtbucher brandtbucher added performance Performance or resource usage interpreter-core (Objects, Python, Grammar, and Parser dirs) topic-JIT labels Nov 14, 2024
@brandtbucher brandtbucher self-assigned this Nov 14, 2024
@@ -0,0 +1 @@
Increase the threshold for JIT code warmup.
Copy link
Member

Choose a reason for hiding this comment

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

Do we want to include some mention of the performance improvements we've seen with the new threshold?

Copy link
Contributor

Choose a reason for hiding this comment

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

I agree with Savannah.

@@ -75,20 +75,19 @@ def loop():
self.assertEqual(opt.get_count(), 0)
with clear_executors(loop):
loop()
# Subtract because optimizer doesn't kick in sooner
self.assertEqual(opt.get_count(), 1000 - TIER2_THRESHOLD)
self.assertEqual(opt.get_count(), 1001)
Copy link
Member

Choose a reason for hiding this comment

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

I might be missing something. Why did we remove the piece about the TIER2_THRESHOLD?

I guess, more generally, how did you decide when to add/remove the threshold from a test? It seems we're wholesale replacing hardcoded values in tests but also adding/removing the threshold when basic arithmetic is used. Was this just trial and error, or is there some convention I'm not picking up on?

Copy link
Member

Choose a reason for hiding this comment

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

IIUC, we were iterating 1000 times, 1000 - TIER2_THRESHOLD of which happened after reaching the threshold.

By that doesn't work if TIER2_THRESHOLD > 1000, so now we iterate 1000 + TIER2_THRESHOLD times, 1000 of which happen after reaching the threshold.

Copy link
Contributor

Choose a reason for hiding this comment

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

Earlier logic assumed a low threshold (< 1000). With the new threshold this logic needs to be adjusted to keep the test working. Now it's generic enough, that the threshold could be any (positive) number.

Copy link
Member

Choose a reason for hiding this comment

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

Ahhhh, that makes sense. Thanks!

Copy link
Member Author

Choose a reason for hiding this comment

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

Yeah, generally my approach was to change the number of loops to use the constant, then fix up any math to use the constant (for tests that assert some result). This one was a bit different, since it needed to run more than TIER2_THRESHOLD times. 1000 used to be a "big" number, but now it's not.

Copy link
Member

@markshannon markshannon left a comment

Choose a reason for hiding this comment

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

A few comments on how we name the consts, but looks good in principle.

x0 = x1 = x2 = x3 = x4 = x5 = x6 = x7 = x8 = x9 = 42
y0 = y1 = y2 = y3 = y4 = y5 = y6 = y7 = y8 = y9 = 42
z0 = z1 = z2 = z3 = z4 = z5 = z6 = z7 = z8 = z9 = 42
a0 = a1 = a2 = a3 = a4 = a5 = a6 = a7 = a8 = a9 = {TIER2_THRESHOLD}
Copy link
Member

Choose a reason for hiding this comment

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

I don't think those 42s are anything to do with thresholds, just a Douglas Adams reference.

Copy link
Contributor

Choose a reason for hiding this comment

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

42 is always the right answer :)

w0 = w1 = w2 = w3 = w4 = w5 = w6 = w7 = w8 = w9 = {TIER2_THRESHOLD}
x0 = x1 = x2 = x3 = x4 = x5 = x6 = x7 = x8 = x9 = {TIER2_THRESHOLD}
y0 = y1 = y2 = y3 = y4 = y5 = y6 = y7 = y8 = y9 = {TIER2_THRESHOLD}
z0 = z1 = z2 = z3 = z4 = z5 = z6 = z7 = z8 = z9 = {TIER2_THRESHOLD}
Copy link
Member

Choose a reason for hiding this comment

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

z9 does need to exceed the threshold though.

Copy link
Contributor

Choose a reason for hiding this comment

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

shouldn't we add an assert somewhere to check this condition? if z9 is bigger than the threshold the test fails.

Copy link
Member Author

Choose a reason for hiding this comment

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

The test passes if z9 meets or exceeds the threshold. It fails if it doesn't. I think it's fine.

@@ -1390,13 +1392,13 @@ def test_guard_type_version_not_removed(self):

def thing(a):
x = 0
for i in range(100):
for i in range(TIER2_THRESHOLD + 100):
x += a.attr
# for the first 90 iterations we set the attribute on this dummy function which shouldn't
Copy link
Member

Choose a reason for hiding this comment

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

This comment needs updating

@@ -2222,7 +2222,7 @@ module_exec(PyObject *module)
}

if (PyModule_Add(module, "TIER2_THRESHOLD",
PyLong_FromLong(JUMP_BACKWARD_INITIAL_VALUE)) < 0) {
PyLong_FromLong(JUMP_BACKWARD_INITIAL_VALUE + 1)) < 0) {
Copy link
Member

Choose a reason for hiding this comment

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

I'd rather not have the +1 here.
Could you add another const EXCEEDS_TIER2_THRESHOLD = TIER2_THRESHOLD + 1?
In the tests above, use EXCEEDS_TIER2_THRESHOLD where we want to exceed the threshold, and in the places where we need the actual threshold, and you currently have TIER2_THRESHOLD - 1, use TIER2_THRESHOLD

In theory, the tests should continue to work if EXCEEDS_TIER2_THRESHOLD = TIER2_THRESHOLD + 5.

Copy link
Member Author

Choose a reason for hiding this comment

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

The thing is, the threshold to tier up is 4096 hits. We just have JUMP_BACKWARD_INITIAL_VALUE set to 4095 because we "count" the final zero.

This makes sense for me when looking at the tests, personally. It's intuitive that something that loops TIER2_THRESHOLD times would tier up, and something that loops TIER2_THRESHOLD - 1 wouldn't.

@bedevere-app
Copy link

bedevere-app bot commented Nov 14, 2024

When you're done making the requested changes, leave the comment: I have made the requested changes; please review again.

@brandtbucher
Copy link
Member Author

Results across platforms. Looks like the memory savings are more pronounced on AArch64 macOS and performance impact is more pronounced on AArch64 Linux. Windows seems to benefit less overall (we don't measure memory on Windows, though):

  • aarch64-apple-darwin: 2% faster, 5% less memory
  • aarch64-unknown-linux-gnu: 9% faster, 4% less memory
  • x86_64-unknown-linux-gnu: 1-2% faster, 3-4% less memory
  • x86_64-pc-windows-msvc: 1% faster
  • i686-pc-windows-msvc: 1% faster

Copy link
Contributor

@diegorusso diegorusso left a comment

Choose a reason for hiding this comment

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

This is more a question: are we going to make this threshold changeable at runtime?

@@ -75,20 +75,19 @@ def loop():
self.assertEqual(opt.get_count(), 0)
with clear_executors(loop):
loop()
# Subtract because optimizer doesn't kick in sooner
self.assertEqual(opt.get_count(), 1000 - TIER2_THRESHOLD)
self.assertEqual(opt.get_count(), 1001)
Copy link
Contributor

Choose a reason for hiding this comment

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

Earlier logic assumed a low threshold (< 1000). With the new threshold this logic needs to be adjusted to keep the test working. Now it's generic enough, that the threshold could be any (positive) number.

x0 = x1 = x2 = x3 = x4 = x5 = x6 = x7 = x8 = x9 = 42
y0 = y1 = y2 = y3 = y4 = y5 = y6 = y7 = y8 = y9 = 42
z0 = z1 = z2 = z3 = z4 = z5 = z6 = z7 = z8 = z9 = 42
a0 = a1 = a2 = a3 = a4 = a5 = a6 = a7 = a8 = a9 = {TIER2_THRESHOLD}
Copy link
Contributor

Choose a reason for hiding this comment

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

42 is always the right answer :)

w0 = w1 = w2 = w3 = w4 = w5 = w6 = w7 = w8 = w9 = {TIER2_THRESHOLD}
x0 = x1 = x2 = x3 = x4 = x5 = x6 = x7 = x8 = x9 = {TIER2_THRESHOLD}
y0 = y1 = y2 = y3 = y4 = y5 = y6 = y7 = y8 = y9 = {TIER2_THRESHOLD}
z0 = z1 = z2 = z3 = z4 = z5 = z6 = z7 = z8 = z9 = {TIER2_THRESHOLD}
Copy link
Contributor

Choose a reason for hiding this comment

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

shouldn't we add an assert somewhere to check this condition? if z9 is bigger than the threshold the test fails.

@@ -0,0 +1 @@
Increase the threshold for JIT code warmup.
Copy link
Contributor

Choose a reason for hiding this comment

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

I agree with Savannah.

@diegorusso
Copy link
Contributor

Results across platforms. Looks like the memory savings are more pronounced on AArch64 macOS and performance impact is more pronounced on AArch64 Linux. Windows seems to benefit less overall (we don't measure memory on Windows, though):

  • aarch64-apple-darwin: 2% faster, 5% less memory
  • aarch64-unknown-linux-gnu: 9% faster, 4% less memory
  • x86_64-unknown-linux-gnu: 1-2% faster, 3-4% less memory
  • x86_64-pc-windows-msvc: 1% faster
  • i686-pc-windows-msvc: 1% faster

Anyway, great stuff! These results are incredible!

@alonme
Copy link
Contributor

alonme commented Nov 15, 2024

Results across platforms. Looks like the memory savings are more pronounced on AArch64 macOS and performance impact is more pronounced on AArch64 Linux. Windows seems to benefit less overall (we don't measure memory on Windows, though):

  • aarch64-apple-darwin: 2% faster, 5% less memory
  • aarch64-unknown-linux-gnu: 9% faster, 4% less memory
  • x86_64-unknown-linux-gnu: 1-2% faster, 3-4% less memory
  • x86_64-pc-windows-msvc: 1% faster
  • i686-pc-windows-msvc: 1% faster

Any idea why aarch64-unknown-linux-gnu would be affected ~4X more?

@brandtbucher
Copy link
Member Author

This is more a question: are we going to make this threshold changeable at runtime?

Maybe eventually. I'll open an issue about exactly what interfaces people would like to control the lifecycle of JIT code.

The thing is that changing the thresholds of our counters at runtime is sort of tricky, since they are initialized to some constant and count down from there towards zero. There's also a lot of them.

That means that we need to re-initialize counters whenever the threshold is changed in order to make this work. It's doable, but not quite as simple as e.g. updating GC thresholds at runtime. An env var set at startup could be another option.

@brandtbucher
Copy link
Member Author

Any idea why aarch64-unknown-linux-gnu would be affected ~4X more?

Not 100% sure.

Anecdotally, AArch64 Linux code is the largest and least efficient to compile (lots of tricky instruction patching, emitting range-extending trampolines, etc.), so it would make sense that compiling less of it helps us.

@brandtbucher
Copy link
Member Author

Okay, I think I've addressed everyone's comments.

I have made the requested changes; please review again (or don't, either way is fine).

@bedevere-app
Copy link

bedevere-app bot commented Nov 15, 2024

Thanks for making the requested changes!

@markshannon: please review the changes made to this pull request.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
awaiting change review interpreter-core (Objects, Python, Grammar, and Parser dirs) performance Performance or resource usage topic-JIT
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants