@recaptime-dev's working patches + fork for Phorge, a community fork of Phabricator. (Upstream dev and stable branches are at upstream/main and upstream/stable respectively.) hq.recaptime.dev/wiki/Phorge
phorge phabricator
1
fork

Configure Feed

Select the types of activity you want to include in your feed.

Provide a cached class map query for making key-based class lookups more efficient

Summary:
Ref T11954. Depends on D16993. We have a couple of "look up the class for this key" queries which are costly enough to show up on a profile.

These aren't huge wins, but they're pretty easy. We currently do this like this:

```
$class_map = load_every_subclass();
return idx($class_map, $key);
```

However, we don't need to load EVERY subclass if we're only looking for, say, the Conduit method subclass which implements `user.whoami`. This allows us to cache that map and find the right class efficiently.

This cache is self-validating and completely safe even in development.

Test Plan:
- Used `curl` to make queries to `user.whoami`, verified that content was identical before and after the change.
- Used `ab -n100` to roughly measure 99th percentile time, which dropped from 74ms to 65ms. This is a small improvement (13% in the best case, here) but it benefits every Conduit method call.

Reviewers: chad

Reviewed By: chad

Maniphest Tasks: T11954

Differential Revision: https://secure.phabricator.com/D16994

+131
+2
src/__phutil_library_map__.php
··· 2031 2031 'PhabricatorCacheSetupCheck' => 'applications/config/check/PhabricatorCacheSetupCheck.php', 2032 2032 'PhabricatorCacheSpec' => 'applications/cache/spec/PhabricatorCacheSpec.php', 2033 2033 'PhabricatorCacheTTLGarbageCollector' => 'applications/cache/garbagecollector/PhabricatorCacheTTLGarbageCollector.php', 2034 + 'PhabricatorCachedClassMapQuery' => 'applications/cache/PhabricatorCachedClassMapQuery.php', 2034 2035 'PhabricatorCaches' => 'applications/cache/PhabricatorCaches.php', 2035 2036 'PhabricatorCachesTestCase' => 'applications/cache/__tests__/PhabricatorCachesTestCase.php', 2036 2037 'PhabricatorCalendarApplication' => 'applications/calendar/application/PhabricatorCalendarApplication.php', ··· 6889 6890 'PhabricatorCacheSetupCheck' => 'PhabricatorSetupCheck', 6890 6891 'PhabricatorCacheSpec' => 'Phobject', 6891 6892 'PhabricatorCacheTTLGarbageCollector' => 'PhabricatorGarbageCollector', 6893 + 'PhabricatorCachedClassMapQuery' => 'Phobject', 6892 6894 'PhabricatorCaches' => 'Phobject', 6893 6895 'PhabricatorCachesTestCase' => 'PhabricatorTestCase', 6894 6896 'PhabricatorCalendarApplication' => 'PhabricatorApplication',
+129
src/applications/cache/PhabricatorCachedClassMapQuery.php
··· 1 + <?php 2 + 3 + /** 4 + * Cached @{class:PhutilClassMapQuery} which can perform lookups for single 5 + * classes efficiently. 6 + * 7 + * Some class trees (like Conduit methods and PHID types) contain a huge number 8 + * of classes but are frequently accessed by looking for a specific class by 9 + * a known identifier (like a Conduit method name or a PHID type constant). 10 + * 11 + * Loading the entire class map for these cases has a small but measurable 12 + * performance cost. Instead, we can build a cache from each Conduit method 13 + * name to just the class required to serve that request. This means that we 14 + * load fewer classes and have less overhead to execute API calls. 15 + */ 16 + final class PhabricatorCachedClassMapQuery 17 + extends Phobject { 18 + 19 + private $query; 20 + private $queryCacheKey; 21 + private $mapKeyMethod; 22 + private $objectMap; 23 + 24 + public function setClassMapQuery(PhutilClassMapQuery $query) { 25 + $this->query = $query; 26 + return $this; 27 + } 28 + 29 + public function setMapKeyMethod($method) { 30 + $this->mapKeyMethod = $method; 31 + return $this; 32 + } 33 + 34 + public function loadClasses(array $values) { 35 + $cache = PhabricatorCaches::getRuntimeCache(); 36 + 37 + $cache_keys = $this->getCacheKeys($values); 38 + $cache_map = $cache->getKeys($cache_keys); 39 + 40 + $results = array(); 41 + $writes = array(); 42 + foreach ($cache_keys as $value => $cache_key) { 43 + if (isset($cache_map[$cache_key])) { 44 + $class_name = $cache_map[$cache_key]; 45 + try { 46 + $result = $this->newObject($class_name); 47 + if ($this->getObjectMapKey($result) === $value) { 48 + $results[$value] = $result; 49 + continue; 50 + } 51 + } catch (Exception $ex) { 52 + // Keep going, we'll handle this immediately below. 53 + } 54 + 55 + // If we didn't "continue;" above, there was either a direct issue with 56 + // the cache or the cached class did not generate the correct map key. 57 + // Wipe the cache and pretend we missed. 58 + $cache->deleteKey($cache_key); 59 + } 60 + 61 + if ($this->objectMap === null) { 62 + $this->objectMap = $this->newObjectMap(); 63 + } 64 + 65 + if (isset($this->objectMap[$value])) { 66 + $results[$value] = $this->objectMap[$value]; 67 + $writes[$cache_key] = get_class($results[$value]); 68 + } 69 + } 70 + 71 + if ($writes) { 72 + $cache->setKeys($writes); 73 + } 74 + 75 + return $results; 76 + } 77 + 78 + public function loadClass($value) { 79 + $result = $this->loadClasses(array($value)); 80 + return idx($result, $value); 81 + } 82 + 83 + private function getCacheKeys(array $values) { 84 + if ($this->queryCacheKey === null) { 85 + $this->queryCacheKey = $this->query->getCacheKey(); 86 + } 87 + 88 + $key = $this->queryCacheKey; 89 + $method = $this->mapKeyMethod; 90 + 91 + $keys = array(); 92 + foreach ($values as $value) { 93 + $keys[$value] = "classmap({$key}).{$method}({$value})"; 94 + } 95 + 96 + return $keys; 97 + } 98 + 99 + private function newObject($class_name) { 100 + return newv($class_name, array()); 101 + } 102 + 103 + private function newObjectMap() { 104 + $map = $this->query->execute(); 105 + 106 + $result = array(); 107 + foreach ($map as $object) { 108 + $value = $this->getObjectMapKey($object); 109 + if (isset($result[$value])) { 110 + $other = $result[$value]; 111 + throw new Exception( 112 + pht( 113 + 'Two objects (of classes "%s" and "%s") generate the same map '. 114 + 'value ("%s"). Each object must generate a unique map value.', 115 + get_class($object), 116 + get_class($other), 117 + $value)); 118 + } 119 + $result[$value] = $object; 120 + } 121 + 122 + return $result; 123 + } 124 + 125 + private function getObjectMapKey($object) { 126 + return call_user_func(array($object, $this->mapKeyMethod)); 127 + } 128 + 129 + }