Page MenuHomePhabricator

Improve performance of type checks in MapCacheLRU
Closed, ResolvedPublic

Description

MapCacheLRU performs various type checks, e.g. in has():

if ( !is_int( $key ) && !is_string( $key ) ) {
	throw new UnexpectedValueException(
		__METHOD__ . ': invalid key; must be string or integer.' );
}

While these are not slow on their own, they might become a problem if this method is called many times. Looking at a sample request, MapCacheLRU::has() is called 200k times, which clearly makes it hot. Also, although it might be possible to decrease the call count by refactoring the callers, MapCacheLRU is a cache, which means it should be as quick as possible, or it might be quicker to recompute whatever value is being cached.

It's also not 100% clear what the intent of this code is. Should it guard against types that are not valid array keys? This would be unnecessary, as PHP checks it upon reading the offset. Or, it might be guarding against the few edge cases with array keys:

  • Booleans, floats, and strings that are the canonical representation of an integer will be cast to integers
  • Null will be cast to the empty string

If this is the case, and we don't trust the caller to validate the key, the only BC solution is (I think) to use PHP 8's union types, which is obviously far far away.

The same is also true for setField() and hasField().

Event Timeline

It originates from change 325623. The commit message implies that PHP was already providing warnings here, but that we want exceptions because they offer a "full useful stacktrace", which is obsolete since T45086.

Maybe the warnings only happen upon getting or only upon setting, in which case a consistent warning on whichever happens first could certainly help.

https://3v4l.org/3Z0f7

This suggests that for all PHP 7.x versions, warnings are built-in for non-string/int keys to array_key_exists call (as done by "has" and "get"). And that for array assignments, it tolerates booleans as well. So that might be a place to keep our own checks?

Maybe we could just wait for PHP 8+ support and add typehints of int|string? So we don't have get() and set() perform different checks on the key.

Change 713557 had a related patch set uploaded (by Krinkle; author: Krinkle):

[mediawiki/core@master] MapCacheLRU: Add tests for getWithSet

https://gerrit.wikimedia.org/r/713557

Change 713558 had a related patch set uploaded (by Krinkle; author: Krinkle):

[mediawiki/core@master] MapCacheLRU: Remove redundant type checks in favour of native warnings

https://gerrit.wikimedia.org/r/713558

Maybe we could just wait for PHP 8+ support and add typehints of int|string? So we don't have get() and set() perform different checks on the key.

get() and set() as well as most other methods all would be the same in this patch. It's only one method (setField) that's the odd one out with an inline check. Seems like a net-win?

Change 713557 merged by jenkins-bot:

[mediawiki/core@master] MapCacheLRU: Add tests for getWithSet

https://gerrit.wikimedia.org/r/713557

Change 713558 merged by jenkins-bot:

[mediawiki/core@master] MapCacheLRU: Remove redundant type checks in favour of native warnings

https://gerrit.wikimedia.org/r/713558

Krinkle triaged this task as Medium priority.

Keeping open for a few days to confirm impact in flame graphs.

Change 719162 had a related patch set uploaded (by Krinkle; author: Krinkle):

[mediawiki/core@master] objectcache: Avoid getCurrentTime() call in MapCacheLRU::has()

https://gerrit.wikimedia.org/r/719162

Change 719162 merged by jenkins-bot:

[mediawiki/core@master] objectcache: Avoid getCurrentTime() call in MapCacheLRU::has()

https://gerrit.wikimedia.org/r/719162

Change 747982 had a related patch set uploaded (by Krinkle; author: Aaron Schulz):

[mediawiki/core@master] objectcache: split up MapCacheLRU::getAge() to avoid conditional overhead

https://gerrit.wikimedia.org/r/747982

Change 747982 merged by jenkins-bot:

[mediawiki/core@master] objectcache: split up MapCacheLRU::getAge() to avoid conditional overhead

https://gerrit.wikimedia.org/r/747982

Change 755941 had a related patch set uploaded (by Reedy; author: Krinkle):

[mediawiki/core@REL1_37] objectcache: Avoid getCurrentTime() call in MapCacheLRU::has()

https://gerrit.wikimedia.org/r/755941

Change 755942 had a related patch set uploaded (by Reedy; author: Aaron Schulz):

[mediawiki/core@REL1_37] objectcache: split up MapCacheLRU::getAge() to avoid conditional overhead

https://gerrit.wikimedia.org/r/755942

Change 755941 merged by jenkins-bot:

[mediawiki/core@REL1_37] objectcache: Avoid getCurrentTime() call in MapCacheLRU::has()

https://gerrit.wikimedia.org/r/755941

Change 755942 merged by jenkins-bot:

[mediawiki/core@REL1_37] objectcache: split up MapCacheLRU::getAge() to avoid conditional overhead

https://gerrit.wikimedia.org/r/755942