Skip to content

Commit 04ddeee

Browse files
committed
perf(MountManager): use binary search to find mount in path
Signed-off-by: Carl Schwan <carlschwan@kde.org>
1 parent c6c11d4 commit 04ddeee

File tree

2 files changed

+76
-25
lines changed

2 files changed

+76
-25
lines changed

lib/private/Files/Mount/Manager.php

Lines changed: 58 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -18,8 +18,11 @@
1818
use OCP\Files\NotFoundException;
1919

2020
class Manager implements IMountManager {
21-
/** @var MountPoint[] */
21+
/** @var array<string, IMountPoint> */
2222
private array $mounts = [];
23+
private bool $areMountsSorted = false;
24+
/** @var list<string>|null $mountKeys */
25+
private ?array $mountKeys = null;
2326
/** @var CappedMemoryCache<IMountPoint> */
2427
private CappedMemoryCache $pathCache;
2528
/** @var CappedMemoryCache<IMountPoint[]> */
@@ -32,44 +35,34 @@ public function __construct(SetupManagerFactory $setupManagerFactory) {
3235
$this->setupManager = $setupManagerFactory->create($this);
3336
}
3437

35-
/**
36-
* @param IMountPoint $mount
37-
*/
38-
public function addMount(IMountPoint $mount) {
38+
public function addMount(IMountPoint $mount): void {
3939
$this->mounts[$mount->getMountPoint()] = $mount;
4040
$this->pathCache->clear();
4141
$this->inPathCache->clear();
42+
$this->areMountsSorted = false;
4243
}
4344

44-
/**
45-
* @param string $mountPoint
46-
*/
47-
public function removeMount(string $mountPoint) {
45+
public function removeMount(string $mountPoint): void {
4846
$mountPoint = Filesystem::normalizePath($mountPoint);
4947
if (\strlen($mountPoint) > 1) {
5048
$mountPoint .= '/';
5149
}
5250
unset($this->mounts[$mountPoint]);
5351
$this->pathCache->clear();
5452
$this->inPathCache->clear();
53+
$this->areMountsSorted = false;
5554
}
5655

57-
/**
58-
* @param string $mountPoint
59-
* @param string $target
60-
*/
61-
public function moveMount(string $mountPoint, string $target) {
56+
public function moveMount(string $mountPoint, string $target): void {
6257
$this->mounts[$target] = $this->mounts[$mountPoint];
6358
unset($this->mounts[$mountPoint]);
6459
$this->pathCache->clear();
6560
$this->inPathCache->clear();
61+
$this->areMountsSorted = false;
6662
}
6763

6864
/**
6965
* Find the mount for $path
70-
*
71-
* @param string $path
72-
* @return IMountPoint
7366
*/
7467
public function find(string $path): IMountPoint {
7568
$this->setupManager->setupForPath($path);
@@ -79,8 +72,6 @@ public function find(string $path): IMountPoint {
7972
return $this->pathCache[$path];
8073
}
8174

82-
83-
8475
if (count($this->mounts) === 0) {
8576
$this->setupManager->setupRoot();
8677
if (count($this->mounts) === 0) {
@@ -120,15 +111,57 @@ public function findIn(string $path): array {
120111
return $this->inPathCache[$path];
121112
}
122113

114+
if (!$this->areMountsSorted) {
115+
ksort($this->mounts, SORT_STRING);
116+
$this->mountKeys = array_keys($this->mounts);
117+
$this->areMountsSorted = true;
118+
}
119+
120+
$result = $this->binarySearch($this->mounts, $this->mountKeys, $path);
121+
122+
$this->inPathCache[$path] = $result;
123+
return $result;
124+
}
125+
126+
/**
127+
* Search for all entries in $sortedArray where $prefix is a prefix but not equal to their key.
128+
*
129+
* @template T
130+
* @param array<string, T> $sortedArray
131+
* @param list<string> $sortedKeys
132+
* @param string $prefix
133+
* @return list<T>
134+
*/
135+
private function binarySearch(array $sortedArray, array $sortedKeys, string $prefix): array {
136+
$low = 0;
137+
$high = count($sortedArray) - 1;
138+
$start = null;
139+
140+
// binary search
141+
while ($low <= $high) {
142+
$mid = ($low + $high) >> 1;
143+
if ($sortedKeys[$mid] < $prefix) {
144+
$low = $mid + 1;
145+
} else {
146+
$start = $mid;
147+
$high = $mid - 1;
148+
}
149+
}
150+
123151
$result = [];
124-
$pathLen = strlen($path);
125-
foreach ($this->mounts as $mountPoint => $mount) {
126-
if (strlen($mountPoint) > $pathLen && str_starts_with($mountPoint, $path)) {
127-
$result[] = $mount;
152+
if ($start !== null) {
153+
for ($i = $start, $n = count($sortedKeys); $i < $n; $i++) {
154+
$key = $sortedKeys[$i];
155+
if (!str_starts_with($key, $prefix)) {
156+
break;
157+
}
158+
159+
if ($key !== $prefix) {
160+
$result[] = $sortedArray[$key];
161+
}
128162
}
129163
}
130164

131-
$this->inPathCache[$path] = $result;
132165
return $result;
133166
}
134167

@@ -201,7 +234,7 @@ public function getSetupManager(): SetupManager {
201234
*
202235
* @param string $path
203236
* @param string[] $mountProviders
204-
* @return MountPoint[]
237+
* @return IMountPoint[]
205238
*/
206239
public function getMountsByMountProvider(string $path, array $mountProviders) {
207240
$this->getSetupManager()->setupForProvider($path, $mountProviders);

tests/lib/Files/Mount/ManagerTest.php

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,24 @@ public function testFind(): void {
5454
$this->assertEquals([$mount1, $mount3], $this->manager->findByStorageId($id));
5555
}
5656

57+
public function testBinarySearch(): void {
58+
$sortedArray = [
59+
'a' => false,
60+
'aa' => false,
61+
'b' => false,
62+
'bb' => false,
63+
'bba' => true,
64+
'bbb' => true,
65+
'bdc' => false,
66+
];
67+
$sortedKey = array_keys($sortedArray);
68+
$result = $this->invokePrivate($this->manager, 'binarySearch', [$sortedArray, $sortedKey, 'bb']);
69+
$this->assertEquals(2, count($result));
70+
foreach ($result as $entry) {
71+
$this->assertTrue($entry);
72+
}
73+
}
74+
5775
public function testLong(): void {
5876
$storage = new LongId([]);
5977
$mount = new MountPoint($storage, '/foo');

0 commit comments

Comments
 (0)