Skip to content

Conversation

@qqiu
Copy link
Contributor

@qqiu qqiu commented Jan 4, 2026

Immediately release temporary objects to prevent memory pressure and unpredictable GC pauses during high-frequency input (e.g. rapid mouse movement).

Add an @autoreleasepool to the Cocoa_PumpEventsUntilDate loop to immediately release NSEvent objects.

Immediately release temporary objects to prevent memory pressure
and unpredictable GC pauses during high-frequency input (e.g. rapid
mouse movement).
@slouken
Copy link
Collaborator

slouken commented Jan 4, 2026

Maybe the autorelease pool should be outside the for loop?

@slouken slouken added this to the 3.4.2 milestone Jan 4, 2026
@slouken slouken self-assigned this Jan 4, 2026
@qqiu
Copy link
Contributor Author

qqiu commented Jan 4, 2026

The callers of Cocoa_PumpEventsUntilDate() already wrap the invocation in an autorelease pool.

@slouken
Copy link
Collaborator

slouken commented Jan 4, 2026

The callers of Cocoa_PumpEventsUntilDate() already wrap the invocation in an autorelease pool.

How many events have to be processed at once in order to trigger the GC pause?

@qqiu
Copy link
Contributor Author

qqiu commented Jan 4, 2026

The GC pause happens each time execution leaves the autorelease pool. How long the pool takes to drain depends on how many events have built up in the loop, so the pause is not a fixed cost.

Adding an @autoreleasepool inside the loop releases each event’s temporary objects right after processing, avoiding unpredictable drain times for the outer pool.

@slouken
Copy link
Collaborator

slouken commented Jan 5, 2026

It seems that if you add up the total autorelease time for each event with this patch and compare it to the autorelease time for all events with the existing code, that they would total about the same, is that correct?

@qqiu
Copy link
Contributor Author

qqiu commented Jan 5, 2026

Yes, the total release time across all events should be roughly the same. The difference is that the cost is spread more evenly across events instead of happening in larger, less predictable chunks.

@slouken
Copy link
Collaborator

slouken commented Jan 5, 2026

But it all happens inside a single call to SDL_PollEvents(), right?

e.g. existing code

SDL_PollEvents()
    event
    event
    event
    GC

vs your change:

SDL_PollEvents()
    event
    GC
    event
    GC
    event
    GC

Your change may have more overhead because of starting GC after every event, and delay event delivery while previous event GC is happening.

What am I missing?

@qqiu
Copy link
Contributor Author

qqiu commented Jan 5, 2026

The difference really comes down to when temporary objects from nextEventMatchingMask() and sendEvent() are destroyed. The total overhead is the same either way.

With batched destruction, fast mouse movement on a high polling rate mouse can cause hundreds of objects to be released at once when the outer autorelease pool drains, at an unpredictable time.

@slouken
Copy link
Collaborator

slouken commented Jan 5, 2026

It's not an unpredictable time, it's at the end of SDL_PumpEvents(), right?

@slouken
Copy link
Collaborator

slouken commented Jan 5, 2026

BTW, I'm not objecting to your PR, I'm just trying to understand how it benefits applications.

@qqiu
Copy link
Contributor Author

qqiu commented Jan 5, 2026

Yes, the pool is drained at the end of SDL_PumpEvents(), but the timing is not deterministic. You do not know how many events each call ends up processing, and the gap between consecutive SDL_PumpEvents() calls can vary.

@slouken
Copy link
Collaborator

slouken commented Jan 5, 2026

If the time to drain at the end equals the sum of the time to drain after each event, doesn't that end up with the same behavior?

@qqiu
Copy link
Contributor Author

qqiu commented Jan 5, 2026

The difference is really just the timing jitter between one SDL_PumpEvents() call and the next.

@slouken
Copy link
Collaborator

slouken commented Jan 5, 2026

So let's say the work for one event is GC, with the old code, the timing would be:

SDL_PumpEvents()
    event
    event
    GC GC

SDL_PumpEvents()
    event
    event
    event
    GC GC GC

with the new code, the timing would be:

SDL_PumpEvents()
    event
    GC
    event
    GC

SDL_PumpEvents()
    event
    GC
    event
    GC
    event
    GC

The first pump events would take 2x GC and the second pump events would take 3x GC in both cases.

I feel like I must still be missing something.

Do you have timing logs that show the difference between the old code and your patch?

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants