Skip to content

Commit 903a56f

Browse files
committed
chore: benchmarks for Collection component
1 parent 14ad277 commit 903a56f

File tree

5 files changed

+448
-4
lines changed

5 files changed

+448
-4
lines changed

src/Psl/Collection/Map.php

+4-2
Original file line numberDiff line numberDiff line change
@@ -70,13 +70,15 @@ public static function fromArray(array $elements): Map
7070
* @template Tsk of array-key
7171
* @template Tsv
7272
*
73-
* @param array<Tsk, Tsv> $items
73+
* @param iterable<Tsk, Tsv> $items
7474
*
7575
* @return Map<Tsk, Tsv>
7676
*/
7777
public static function fromItems(iterable $items): Map
7878
{
79-
return self::fromArray(iterator_to_array($items));
79+
$items = $items instanceof \Traversable ? iterator_to_array($items) : $items;
80+
81+
return self::fromArray($items);
8082
}
8183

8284
/**

src/Psl/Collection/MutableMap.php

+4-2
Original file line numberDiff line numberDiff line change
@@ -70,13 +70,15 @@ public static function fromArray(array $elements): MutableMap
7070
* @template Tsk of array-key
7171
* @template Tsv
7272
*
73-
* @param array<Tsk, Tsv> $items
73+
* @param iterable<Tsk, Tsv> $items
7474
*
7575
* @return MutableMap<Tsk, Tsv>
7676
*/
7777
public static function fromItems(iterable $items): MutableMap
7878
{
79-
return self::fromArray(iterator_to_array($items));
79+
$items = $items instanceof \Traversable ? iterator_to_array($items) : $items;
80+
81+
return self::fromArray($items);
8082
}
8183

8284
/**
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,315 @@
1+
<?php
2+
3+
namespace Psl\Tests\Benchmark\Collection;
4+
5+
use PhpBench\Attributes\Groups;
6+
use PhpBench\Attributes\ParamProviders;
7+
use Psl\Collection\MapInterface;
8+
use Psl\Collection\VectorInterface;
9+
use function Psl\Vec\map;
10+
11+
#[Groups(["collection"])]
12+
abstract class AbstractMapBench
13+
{
14+
public function benchFromArray(): void
15+
{
16+
map($this->getDataProviders(), fn ($value) => $this->createFromArray(iterator_to_array($value)));
17+
}
18+
19+
public function benchFromIterable(): void
20+
{
21+
map($this->getDataProviders(), fn ($value) => $this->createFromIterable($value));
22+
}
23+
24+
public function benchmarkValues(): VectorInterface
25+
{
26+
return $this
27+
->createFromIterable($this->createAssociativeIterable())
28+
->values();
29+
}
30+
31+
public function benchmarkKeys(): VectorInterface
32+
{
33+
return $this
34+
->createFromIterable($this->createAssociativeIterable())
35+
->keys();
36+
}
37+
38+
public function benchmarkFilter(): void
39+
{
40+
$map = $this->createFromIterable($this->createAssociativeIterable());
41+
42+
$map->filter(fn ($v) => $v === 'a');
43+
$map->filter(fn ($v) => $v === 'k');
44+
$map->filter(fn ($v) => $v === 'foo');
45+
}
46+
47+
public function benchmarkMap(): MapInterface
48+
{
49+
return $this
50+
->createFromIterable($this->createAssociativeIterable())
51+
->map(fn ($v) => $v);
52+
}
53+
54+
public function benchmarkMapWithKey(): MapInterface
55+
{
56+
return $this
57+
->createFromIterable($this->createAssociativeIterable())
58+
->mapWithKey(fn ($k, $v) => [$k, $v]);
59+
}
60+
61+
public function benchmarkFirst(): string|null
62+
{
63+
return $this
64+
->createFromIterable($this->createAssociativeIterable())
65+
->first();
66+
}
67+
68+
public function benchmarkFirstKey(): string|null
69+
{
70+
return $this
71+
->createFromIterable($this->createAssociativeIterable())
72+
->firstKey();
73+
}
74+
75+
public function benchmarkLast(): string|null
76+
{
77+
return $this
78+
->createFromIterable($this->createAssociativeIterable())
79+
->last();
80+
}
81+
82+
public function benchmarkLastKey(): string|null
83+
{
84+
return $this
85+
->createFromIterable($this->createAssociativeIterable())
86+
->lastKey();
87+
}
88+
89+
/**
90+
* @param array{value: string} $params
91+
*/
92+
#[ParamProviders('provideLinearSearch')]
93+
public function benchmarkLinearSearch(array $params): string|null
94+
{
95+
return $this
96+
->createFromIterable($this->createAssociativeIterable())
97+
->linearSearch($params['value']);
98+
}
99+
100+
public function provideLinearSearch(): array
101+
{
102+
return [
103+
['value' => 'a'],
104+
['value' => 'k'],
105+
['value' => 'lol'],
106+
];
107+
}
108+
109+
#[ParamProviders('provideZip')]
110+
public function benchmarkZip(array $data): MapInterface
111+
{
112+
return $this
113+
->createFromIterable($this->createAssociativeIterable())
114+
->zip($data);
115+
}
116+
117+
public function provideZip(): array
118+
{
119+
return [
120+
['value' => []],
121+
['value' => ['z', 'y', 'x']],
122+
];
123+
}
124+
125+
/**
126+
* @param array{value: int<0, max>} $data
127+
*/
128+
#[ParamProviders('provideTake')]
129+
public function benchmarkTake(array $data): MapInterface
130+
{
131+
return $this
132+
->createFromIterable($this->createAssociativeIterable())
133+
->take($data['value']);
134+
}
135+
136+
/**
137+
* @return list<array{value: int<0, max>}>
138+
*/
139+
public function provideTake(): array
140+
{
141+
return [
142+
['value' => 0],
143+
['value' => 1],
144+
['value' => 5],
145+
['value' => 1000],
146+
];
147+
}
148+
149+
public function benchmarkTakeWhile(): void
150+
{
151+
$map = $this->createFromIterable($this->createAssociativeIterable());
152+
153+
$map->takeWhile(fn ($v) => $v === 'a');
154+
$map->takeWhile(fn ($v) => $v === 'k');
155+
$map->takeWhile(fn ($v) => $v === 'rolf');
156+
$map->takeWhile(fn () => true);
157+
$map->takeWhile(fn () => false);
158+
}
159+
160+
/**
161+
* @param array{value: int<0, max>} $data
162+
*/
163+
#[ParamProviders('provideDrop')]
164+
public function benchmarkDrop(array $data): MapInterface
165+
{
166+
return $this
167+
->createFromIterable($this->createAssociativeIterable())
168+
->drop($data['value']);
169+
}
170+
171+
/**
172+
* @return list<array{value: int<0, max>}>
173+
*/
174+
public function provideDrop(): array
175+
176+
{
177+
return [
178+
['value' => 0],
179+
['value' => 1],
180+
['value' => 8],
181+
['value' => 1000],
182+
];
183+
}
184+
185+
public function benchmarkDropWhile(): void
186+
{
187+
$map = $this->createFromIterable($this->createAssociativeIterable());
188+
189+
$map->dropWhile(fn ($v) => $v === 'a');
190+
$map->dropWhile(fn ($v) => $v === 'k');
191+
$map->dropWhile(fn ($v) => $v === 'rolf');
192+
$map->dropWhile(fn () => true);
193+
$map->dropWhile(fn () => false);
194+
}
195+
196+
/**
197+
* @param array{start: int<0, max>, length: int<0, max>} $data
198+
*/
199+
#[ParamProviders('provideSlice')]
200+
public function benchmarkSlice(array $data): MapInterface
201+
{
202+
return $this
203+
->createFromIterable($this->createAssociativeIterable())
204+
->slice($data['start'], $data['length']);
205+
}
206+
207+
/**
208+
* @return list<array{start: int<0, max>, length: int<0, max>}>
209+
*/
210+
public function provideSlice(): array
211+
{
212+
return [
213+
['start' => 0, 'length' => 10],
214+
['start' => 5, 'length' => 10],
215+
['start' => 15, 'length' => 10],
216+
['start' => 1000, 'length' => 100],
217+
];
218+
}
219+
220+
/**
221+
* @param array{value: positive-int} $data
222+
*/
223+
#[ParamProviders('provideChunk')]
224+
public function benchmarkChunk(array $data): VectorInterface
225+
{
226+
return $this
227+
->createFromIterable($this->createAssociativeIterable())
228+
->chunk($data['value']);
229+
}
230+
231+
/**
232+
* @return list<array{value: positive-int}>
233+
*/
234+
public function provideChunk(): array
235+
{
236+
return [
237+
['value' => 1],
238+
['value' => 2],
239+
['value' => 3],
240+
['value' => 10],
241+
['value' => 1000],
242+
];
243+
}
244+
245+
/**
246+
* @return array<string, array<array-key, mixed>>
247+
*/
248+
public function getDataProviders(): array
249+
{
250+
return [
251+
'empty array' => [$this->createEmptyIterable()],
252+
'list' => [$this->createNumericIterable()],
253+
'associative' => [$this->createAssociativeIterable()],
254+
];
255+
}
256+
257+
public function createEmptyIterable(): iterable
258+
{
259+
yield from [];
260+
}
261+
262+
/**
263+
* @template Tk of array-key
264+
* @template Tv
265+
*
266+
* @param iterable<Tk, Tv> $items *
267+
* @return MapInterface<Tk, Tv>
268+
*/
269+
abstract protected function createFromIterable(iterable $items): MapInterface;
270+
/**
271+
* @template Tk of array-key
272+
* @template Tv
273+
*
274+
* @param array<Tk, Tv> $items
275+
*
276+
* @return MapInterface<Tk, Tv>
277+
*/
278+
abstract protected function createFromArray(array $items): MapInterface;
279+
280+
/**
281+
* @return \Generator<int, string, void, void>
282+
*/
283+
function createNumericIterable(): \Generator
284+
{
285+
yield 'a';
286+
yield 'b';
287+
yield 'c';
288+
yield 'd';
289+
yield 'e';
290+
yield 'f';
291+
yield 'g';
292+
yield 'h';
293+
yield 'i';
294+
yield 'j';
295+
yield 'k';
296+
}
297+
298+
/**
299+
* @return \Generator<string, string, void, void>
300+
*/
301+
public function createAssociativeIterable(): \Generator
302+
{
303+
yield 'a' => 'a';
304+
yield 'b' => 'b';
305+
yield 'c' => 'c';
306+
yield 'd' => 'd';
307+
yield 'e' => 'e';
308+
yield 'f' => 'f';
309+
yield 'g' => 'g';
310+
yield 'h' => 'h';
311+
yield 'i' => 'i';
312+
yield 'j' => 'j';
313+
yield 'k' => 'k';
314+
}
315+
}
+36
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
<?php
2+
3+
namespace Psl\Tests\Benchmark\Collection;
4+
5+
use PhpBench\Attributes\Groups;
6+
use Psl\Collection\MapInterface;
7+
use Psl\Collection\Map;
8+
9+
class MapBench extends AbstractMapBench
10+
{
11+
/**
12+
* @template TKey of array-key
13+
* @template TValue
14+
*
15+
* @param iterable<TKey, TValue> $items
16+
*
17+
* @return MapInterface<TKey, TValue>
18+
*/
19+
protected function createFromIterable(iterable $items): MapInterface
20+
{
21+
return Map::fromItems($items);
22+
}
23+
24+
/**
25+
* @template TKey of array-key
26+
* @template TValue
27+
*
28+
* @param array<TKey, TValue> $items
29+
*
30+
* @return MapInterface<TKey, TValue>
31+
*/
32+
protected function createFromArray(array $items): MapInterface
33+
{
34+
return Map::fromArray($items);
35+
}
36+
}

0 commit comments

Comments
 (0)