Skip to content

Commit ec6c431

Browse files
authored
PHPLIB-1236 Implement Multi-Doc and GridFS Benchmarks (#1165)
Extracted GridFS benchmarks into a specific class because they are very different that other collection benchmarks. They need some properties. > The dataset, designated GRIDFS_LARGE (disk file 'gridfs_large.bin'), consists of a single file containing about 50 MB of random data. I don't need to commit a 50MB file full of NULL characters from the spec. I can generate it on-demand in-memory.
1 parent a187452 commit ec6c431

File tree

6 files changed

+198
-11
lines changed

6 files changed

+198
-11
lines changed

benchmark/DriverBench/GridFSBench.php

Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
<?php
2+
3+
namespace MongoDB\Benchmark\DriverBench;
4+
5+
use MongoDB\Benchmark\Fixtures\Data;
6+
use MongoDB\Benchmark\Utils;
7+
use MongoDB\GridFS\Bucket;
8+
use PhpBench\Attributes\AfterMethods;
9+
use PhpBench\Attributes\BeforeMethods;
10+
11+
/**
12+
* For accurate results, run benchmarks on a standalone server.
13+
*
14+
* @see https://github.com/mongodb/specifications/blob/ddfc8b583d49aaf8c4c19fa01255afb66b36b92e/source/benchmarking/benchmarking.rst#multi-doc-benchmarks
15+
*/
16+
#[AfterMethods('afterAll')]
17+
final class GridFSBench
18+
{
19+
/** @var resource */
20+
private $stream;
21+
private Bucket $bucket;
22+
private mixed $id;
23+
24+
/** @see https://github.com/mongodb/specifications/blob/ddfc8b583d49aaf8c4c19fa01255afb66b36b92e/source/benchmarking/benchmarking.rst#gridfs-upload */
25+
#[BeforeMethods('beforeUpload')]
26+
public function benchUpload(): void
27+
{
28+
$this->bucket->uploadFromStream('test', $this->stream);
29+
}
30+
31+
public function beforeUpload(): void
32+
{
33+
$database = Utils::getDatabase();
34+
$database->drop();
35+
36+
$this->bucket = $database->selectGridFSBucket();
37+
// Init the GridFS bucket
38+
$this->bucket->uploadFromStream('init', Data::getStream(1));
39+
// Prepare the 50MB stream to upload
40+
$this->stream = Data::getStream(50 * 1024 * 1024);
41+
}
42+
43+
/** @see https://github.com/mongodb/specifications/blob/ddfc8b583d49aaf8c4c19fa01255afb66b36b92e/source/benchmarking/benchmarking.rst#gridfs-download */
44+
#[BeforeMethods('beforeDownload')]
45+
public function benchDownload(): void
46+
{
47+
$this->bucket->downloadToStream($this->id, $this->stream);
48+
}
49+
50+
public function beforeDownload(): void
51+
{
52+
$database = Utils::getDatabase();
53+
$database->drop();
54+
55+
$this->bucket = $database->selectGridFSBucket();
56+
// Upload a 50MB file
57+
$this->id = $this->bucket->uploadFromStream('init', Data::getStream(50 * 1024 * 1024));
58+
// Prepare the stream to receive the download
59+
$this->stream = Data::getStream(0);
60+
}
61+
62+
public function afterAll(): void
63+
{
64+
unset($this->bucket, $this->stream, $this->id);
65+
Utils::getDatabase()->drop();
66+
}
67+
}
Lines changed: 99 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,99 @@
1+
<?php
2+
3+
namespace MongoDB\Benchmark\DriverBench;
4+
5+
use Generator;
6+
use MongoDB\Benchmark\Fixtures\Data;
7+
use MongoDB\Benchmark\Utils;
8+
use MongoDB\BSON\Document;
9+
use PhpBench\Attributes\BeforeMethods;
10+
use PhpBench\Attributes\ParamProviders;
11+
12+
use function array_fill;
13+
use function file_get_contents;
14+
15+
/**
16+
* For accurate results, run benchmarks on a standalone server.
17+
*
18+
* @see https://github.com/mongodb/specifications/blob/ddfc8b583d49aaf8c4c19fa01255afb66b36b92e/source/benchmarking/benchmarking.rst#multi-doc-benchmarks
19+
*/
20+
final class MultiDocBench
21+
{
22+
/**
23+
* @see https://github.com/mongodb/specifications/blob/master/source/benchmarking/benchmarking.rst#find-many-and-empty-the-cursor
24+
* @param array{options: array} $params
25+
*/
26+
#[BeforeMethods('beforeFindMany')]
27+
#[ParamProviders('provideFindManyParams')]
28+
public function benchFindMany(array $params): void
29+
{
30+
$collection = Utils::getCollection();
31+
32+
// phpcs:ignore Generic.CodeAnalysis.EmptyStatement.DetectedForeach
33+
// phpcs:ignore Generic.ControlStructures.InlineControlStructure.NotAllowed
34+
foreach ($collection->find([], $params['options']) as $document);
35+
}
36+
37+
public function beforeFindMany(): void
38+
{
39+
$collection = Utils::getCollection();
40+
$collection->drop();
41+
42+
$tweet = Data::readJsonFile(Data::TWEET_FILE_PATH);
43+
$documents = array_fill(0, 9_999, $tweet);
44+
$collection->insertMany($documents);
45+
}
46+
47+
public static function provideFindManyParams(): Generator
48+
{
49+
yield 'Driver default typemap' => [
50+
'options' => [],
51+
];
52+
53+
yield 'Raw BSON' => [
54+
'options' => ['typeMap' => ['root' => 'bson']],
55+
];
56+
}
57+
58+
/**
59+
* @see https://github.com/mongodb/specifications/blob/ddfc8b583d49aaf8c4c19fa01255afb66b36b92e/source/benchmarking/benchmarking.rst#small-doc-bulk-insert
60+
* @see https://github.com/mongodb/specifications/blob/ddfc8b583d49aaf8c4c19fa01255afb66b36b92e/source/benchmarking/benchmarking.rst#large-doc-bulk-insert
61+
* @param array{documents: array} $params
62+
*/
63+
#[BeforeMethods('beforeBulkInsert')]
64+
#[ParamProviders('provideBulkInsertParams')]
65+
public function benchBulkInsert(array $params): void
66+
{
67+
$collection = Utils::getCollection();
68+
69+
// phpcs:ignore Generic.CodeAnalysis.EmptyStatement.DetectedForeach
70+
// phpcs:ignore Generic.ControlStructures.InlineControlStructure.NotAllowed
71+
$collection->insertMany($params['documents']);
72+
}
73+
74+
public function beforeBulkInsert(): void
75+
{
76+
$database = Utils::getDatabase();
77+
$database->dropCollection(Utils::getCollectionName());
78+
$database->createCollection(Utils::getCollectionName());
79+
}
80+
81+
public static function provideBulkInsertParams(): Generator
82+
{
83+
yield 'Small doc' => [
84+
'documents' => array_fill(0, 9_999, Data::readJsonFile(Data::SMALL_FILE_PATH)),
85+
];
86+
87+
yield 'Small BSON doc' => [
88+
'documents' => array_fill(0, 9_999, Document::fromJSON(file_get_contents(Data::SMALL_FILE_PATH))),
89+
];
90+
91+
yield 'Large doc' => [
92+
'documents' => array_fill(0, 9, Data::readJsonFile(Data::LARGE_FILE_PATH)),
93+
];
94+
95+
yield 'Large BSON doc' => [
96+
'documents' => array_fill(0, 9, Document::fromJSON(file_get_contents(Data::LARGE_FILE_PATH))),
97+
];
98+
}
99+
}

benchmark/Extension/EnvironmentProvider.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,7 @@ private function getServerInfo(Manager $manager): array
6060
private function getBuildInfo(Manager $manager): array
6161
{
6262
$buildInfo = $manager->executeCommand(
63-
Utils::getDatabase(),
63+
Utils::getDatabaseName(),
6464
new Command(['buildInfo' => 1]),
6565
new ReadPreference(ReadPreference::PRIMARY),
6666
)->toArray()[0];

benchmark/Fixtures/Data.php

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,11 @@
33
namespace MongoDB\Benchmark\Fixtures;
44

55
use function file_get_contents;
6+
use function fopen;
7+
use function fwrite;
68
use function json_decode;
9+
use function rewind;
10+
use function str_repeat;
711

812
use const JSON_THROW_ON_ERROR;
913

@@ -17,4 +21,18 @@ public static function readJsonFile(string $path): array
1721
{
1822
return json_decode(file_get_contents($path), true, 512, JSON_THROW_ON_ERROR);
1923
}
24+
25+
/**
26+
* Generates an in-memory stream of the given size.
27+
*
28+
* @return resource
29+
*/
30+
public static function getStream(int $size)
31+
{
32+
$stream = fopen('php://memory', 'w+');
33+
fwrite($stream, str_repeat("\0", $size));
34+
rewind($stream);
35+
36+
return $stream;
37+
}
2038
}

benchmark/Utils.php

Lines changed: 12 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -4,41 +4,43 @@
44

55
use MongoDB\Client;
66
use MongoDB\Collection;
7+
use MongoDB\Database;
78

89
use function getenv;
910

1011
final class Utils
1112
{
1213
private static ?Client $client;
14+
private static ?Database $database;
1315
private static ?Collection $collection;
1416

1517
public static function getClient(): Client
1618
{
17-
return self::$client ??= self::createClient();
19+
return self::$client ??= new Client(self::getUri());
20+
}
21+
22+
public static function getDatabase(): Database
23+
{
24+
return self::$database ??= self::getClient()->selectDatabase(self::getDatabaseName());
1825
}
1926

2027
public static function getCollection(): Collection
2128
{
22-
return self::$collection ??= self::createCollection();
29+
return self::$collection ??= self::getDatabase()->selectCollection(self::getCollectionName());
2330
}
2431

2532
public static function getUri(): string
2633
{
2734
return getenv('MONGODB_URI') ?: 'mongodb://localhost:27017/';
2835
}
2936

30-
public static function getDatabase(): string
37+
public static function getDatabaseName(): string
3138
{
3239
return getenv('MONGODB_DATABASE') ?: 'phplib_test';
3340
}
3441

35-
private static function createClient(): Client
36-
{
37-
return new Client(self::getUri());
38-
}
39-
40-
private static function createCollection(): Collection
42+
public static function getCollectionName(): string
4143
{
42-
return self::getClient()->selectCollection(self::getDatabase(), 'perftest');
44+
return 'perftest';
4345
}
4446
}

phpbench.json.dist

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
"runner.bootstrap": "vendor/autoload.php",
66
"runner.file_pattern": "*Bench.php",
77
"runner.path": "benchmark",
8+
"runner.php_config": { "memory_limit": "1G" },
89
"runner.iterations": 3,
910
"runner.revs": 10
1011
}

0 commit comments

Comments
 (0)