22#include <geometry/rtree.h>
28#include <nlohmann/json.hpp>
46std::vector<BBOX> generateRandomBoxes(
int aCount,
int aMaxCoord,
int aMaxSize,
49 std::uniform_int_distribution<int> coordDist( 0, aMaxCoord );
50 std::uniform_int_distribution<int> sizeDist( 1, aMaxSize );
52 std::vector<BBOX> boxes( aCount );
54 for(
int i = 0; i < aCount; ++i )
56 boxes[i].min[0] = coordDist( aRng );
57 boxes[i].min[1] = coordDist( aRng );
58 boxes[i].max[0] = boxes[i].min[0] + sizeDist( aRng );
59 boxes[i].max[1] = boxes[i].min[1] + sizeDist( aRng );
88 const std::vector<int> scales = { 1000, 10000, 100000 };
89 std::vector<BENCH_RESULT> results;
93 std::mt19937
rng( 42 );
94 auto boxes = generateRandomBoxes(
N, 1000000, 10000,
rng );
97 auto queries = generateRandomBoxes( 1000, 1000000, 50000,
rng );
103 result.implementation =
"OldTree";
109 RTree<intptr_t, int, 2, double, 8, 4> oldTree;
111 for(
int i = 0; i <
N; ++i )
112 oldTree.Insert( boxes[i].min, boxes[i].max,
static_cast<intptr_t
>( i ) );
120 for(
const BBOX& q : queries )
122 auto visitor = [&totalFound]( intptr_t )
128 oldTree.Search( q.min, q.max, visitor );
132 result.queryCount =
static_cast<int>( queries.size() );
134 ? ( queries.size() / (
result.queryMs / 1e3 ) )
137 results.push_back(
result );
144 result.implementation =
"PackedTree";
153 for(
int i = 0; i <
N; ++i )
154 builder.
Add( boxes[i].min, boxes[i].max,
static_cast<intptr_t
>( i ) );
156 auto packedTree = builder.
Build();
158 result.memoryBytes = packedTree.MemoryUsage();
164 for(
const BBOX& q : queries )
166 auto visitor = [&totalFound]( intptr_t )
172 packedTree.Search( q.min, q.max, visitor );
176 result.queryCount =
static_cast<int>( queries.size() );
178 ? ( queries.size() / (
result.queryMs / 1e3 ) )
181 results.push_back(
result );
188 result.implementation =
"DynTree";
196 for(
int i = 0; i <
N; ++i )
197 dynTree.
Insert( boxes[i].min, boxes[i].max,
static_cast<intptr_t
>( i ) );
206 for(
const BBOX& q : queries )
208 auto visitor = [&totalFound]( intptr_t )
214 dynTree.
Search( q.min, q.max, visitor );
218 result.queryCount =
static_cast<int>( queries.size() );
220 ? ( queries.size() / (
result.queryMs / 1e3 ) )
223 results.push_back(
result );
232 nlohmann::json entry;
233 entry[
"workload"] = r.workload;
234 entry[
"implementation"] = r.implementation;
235 entry[
"itemCount"] = r.itemCount;
236 entry[
"buildMs"] = r.buildMs;
237 entry[
"queryMs"] = r.queryMs;
238 entry[
"queryCount"] = r.queryCount;
239 entry[
"queriesPerSec"] = r.queriesPerSec;
240 entry[
"memoryBytes"] = r.memoryBytes;
241 j.push_back( entry );
245 std::filesystem::path outputDir( std::filesystem::temp_directory_path()
247 std::filesystem::create_directories( outputDir );
248 std::ofstream out( outputDir /
"spatial_index_drc.json" );
251 out << j.dump( 2 ) << std::endl;
254 for(
size_t i = 0; i + 2 < results.size(); i += 3 )
256 if( results[i].itemCount >= 10000 )
258 double oldQps = results[i].queriesPerSec;
259 double packedQps = results[i + 1].queriesPerSec;
260 double dynQps = results[i + 2].queriesPerSec;
262 BOOST_CHECK_GE( packedQps, oldQps );
263 BOOST_CHECK_GE( dynQps, oldQps );
272 const std::vector<int> scales = { 100, 1000, 5000 };
274 for(
int N : scales )
278 std::mt19937
rng( 123 );
279 auto boxes = generateRandomBoxes(
N, 100000, 5000,
rng );
281 RTree<intptr_t, int, 2, double, 8, 4> oldTree;
283 for(
int i = 0; i <
N; ++i )
284 oldTree.Insert( boxes[i].min, boxes[i].max,
static_cast<intptr_t
>( i ) );
286 std::uniform_int_distribution<int> opDist( 0, 9 );
287 std::uniform_int_distribution<int> coordDist( 0, 100000 );
288 std::uniform_int_distribution<int> sizeDist( 1, 5000 );
295 for(
int op = 0; op < ops; ++op )
297 int which = opDist(
rng );
301 int min[2] = { coordDist(
rng ), coordDist(
rng ) };
302 int max[2] = { min[0] + sizeDist(
rng ), min[1] + sizeDist(
rng ) };
303 oldTree.Insert( min, max,
static_cast<intptr_t
>(
N + op ) );
305 else if( which == 1 && !boxes.empty() )
307 std::uniform_int_distribution<int> idxDist( 0, boxes.size() - 1 );
308 int idx = idxDist(
rng );
310 oldTree.Remove( boxes[idx].min, boxes[idx].max,
311 static_cast<intptr_t
>( idx ) );
315 int min[2] = { coordDist(
rng ), coordDist(
rng ) };
316 int max[2] = { min[0] + sizeDist(
rng ) * 5,
317 min[1] + sizeDist(
rng ) * 5 };
320 auto visitor = [&found]( intptr_t )
326 oldTree.Search( min, max, visitor );
330 double oldMs = timer.
msecs();
333 <<
" ops in " << oldMs <<
"ms" );
338 std::mt19937
rng( 123 );
339 auto boxes = generateRandomBoxes(
N, 100000, 5000,
rng );
343 for(
int i = 0; i <
N; ++i )
344 dynTree.
Insert( boxes[i].min, boxes[i].max,
static_cast<intptr_t
>( i ) );
346 std::uniform_int_distribution<int> opDist( 0, 9 );
347 std::uniform_int_distribution<int> coordDist( 0, 100000 );
348 std::uniform_int_distribution<int> sizeDist( 1, 5000 );
358 for(
int op = 0; op < ops; ++op )
360 int which = opDist(
rng );
364 int min[2] = { coordDist(
rng ), coordDist(
rng ) };
365 int max[2] = { min[0] + sizeDist(
rng ), min[1] + sizeDist(
rng ) };
366 dynTree.
Insert( min, max,
static_cast<intptr_t
>(
N + insertCount ) );
369 else if( which == 1 && !boxes.empty() )
371 std::uniform_int_distribution<int> idxDist( 0, boxes.size() - 1 );
372 int idx = idxDist(
rng );
374 dynTree.
Remove( boxes[idx].min, boxes[idx].max,
375 static_cast<intptr_t
>( idx ) );
380 int min[2] = { coordDist(
rng ), coordDist(
rng ) };
381 int max[2] = { min[0] + sizeDist(
rng ) * 5,
382 min[1] + sizeDist(
rng ) * 5 };
385 auto visitor = [&found]( intptr_t )
391 dynTree.
Search( min, max, visitor );
396 double elapsedMs = timer.
msecs();
399 << elapsedMs <<
"ms ("
400 << insertCount <<
" ins, "
401 << removeCount <<
" rem, "
402 << queryCount <<
" qry)" );
412 const std::vector<int> scales = { 1000, 10000, 100000 };
413 std::vector<BENCH_RESULT> results;
415 for(
int N : scales )
417 std::mt19937
rng( 77 );
420 auto boxes = generateRandomBoxes(
N, 400000000, 2000000,
rng );
423 auto queries = generateRandomBoxes( 2000, 400000000, 50000000,
rng );
428 result.workload =
"Viewport";
429 result.implementation =
"OldTree";
432 RTree<intptr_t, int, 2, double, 8, 4> oldTree;
437 for(
int i = 0; i <
N; ++i )
438 oldTree.Insert( boxes[i].min, boxes[i].max,
static_cast<intptr_t
>( i ) );
445 for(
const BBOX& q : queries )
447 auto visitor = []( intptr_t ) {
return true; };
448 oldTree.Search( q.min, q.max, visitor );
452 result.queryCount =
static_cast<int>( queries.size() );
454 ? ( queries.size() / (
result.queryMs / 1e3 ) )
457 results.push_back(
result );
463 result.workload =
"Viewport";
464 result.implementation =
"DynTree";
472 for(
int i = 0; i <
N; ++i )
473 dynTree.
Insert( boxes[i].min, boxes[i].max,
static_cast<intptr_t
>( i ) );
481 for(
const BBOX& q : queries )
483 auto visitor = []( intptr_t ) {
return true; };
484 dynTree.
Search( q.min, q.max, visitor );
488 result.queryCount =
static_cast<int>( queries.size() );
490 ? ( queries.size() / (
result.queryMs / 1e3 ) )
493 results.push_back(
result );
502 nlohmann::json entry;
503 entry[
"workload"] = r.workload;
504 entry[
"implementation"] = r.implementation;
505 entry[
"itemCount"] = r.itemCount;
506 entry[
"buildMs"] = r.buildMs;
507 entry[
"queryMs"] = r.queryMs;
508 entry[
"queryCount"] = r.queryCount;
509 entry[
"queriesPerSec"] = r.queriesPerSec;
510 entry[
"memoryBytes"] = r.memoryBytes;
511 j.push_back( entry );
514 std::filesystem::path outputDir( std::filesystem::temp_directory_path() /
"kicad_bench" );
515 std::filesystem::create_directories( outputDir );
516 std::ofstream out( outputDir /
"spatial_index_viewport.json" );
519 out << j.dump( 2 ) << std::endl;
522 for(
size_t i = 0; i + 1 < results.size(); i += 2 )
524 if( results[i].itemCount >= 10000 )
526 double oldQps = results[i].queriesPerSec;
527 double dynQps = results[i + 1].queriesPerSec;
529 BOOST_CHECK_GE( dynQps, oldQps );
539 const std::vector<int> scales = { 1000, 5000, 15000 };
541 for(
int N : scales )
543 std::mt19937
rng( 555 );
544 auto boxes = generateRandomBoxes(
N, 100000, 5000,
rng );
548 RTree<intptr_t, int, 2, double, 8, 4> srcTree;
550 for(
int i = 0; i <
N; ++i )
551 srcTree.Insert( boxes[i].min, boxes[i].max,
static_cast<intptr_t
>( i ) );
556 RTree<intptr_t, int, 2, double, 8, 4> dstTree;
558 auto copyVisitor = [&dstTree, &boxes]( intptr_t val )
560 int idx =
static_cast<int>( val );
562 dstTree.Insert( boxes[idx].min, boxes[idx].max, val );
566 int allMin[2] = { INT_MIN, INT_MIN };
567 int allMax[2] = { INT_MAX, INT_MAX };
568 srcTree.Search( allMin, allMax, copyVisitor );
570 double oldCopyMs = timer.
msecs();
579 for(
int i = 0; i <
N; ++i )
580 srcTree.
Insert( boxes[i].min, boxes[i].max,
static_cast<intptr_t
>( i ) );
585 auto clone = srcTree.
Clone();
587 double cloneMs = timer.
msecs();
601 const std::vector<int> scales = { 1000, 5000 };
603 for(
int N : scales )
605 std::mt19937
rng( 456 );
606 auto boxes = generateRandomBoxes(
N, 100000, 5000,
rng );
610 for(
int i = 0; i <
N; ++i )
611 parent.
Insert( boxes[i].min, boxes[i].max,
static_cast<intptr_t
>( i ) );
617 auto clone = parent.
Clone();
618 double cloneMs = timer.
msecs();
621 std::uniform_int_distribution<int> coordDist( 0, 100000 );
622 std::uniform_int_distribution<int> sizeDist( 1, 5000 );
624 for(
int i = 0; i < 100; ++i )
626 int min[2] = { coordDist(
rng ), coordDist(
rng ) };
627 int max[2] = { min[0] + sizeDist(
rng ), min[1] + sizeDist(
rng ) };
628 clone.Insert( min, max,
static_cast<intptr_t
>(
N + i ) );
632 auto queries = generateRandomBoxes( 500, 100000, 20000,
rng );
637 for(
const BBOX& q : queries )
641 auto visitor = [&found]( intptr_t )
647 clone.Search( q.min, q.max, visitor );
650 double queryMs = queryTimer.
msecs();
653 <<
": clone=" << cloneMs <<
"ms"
654 <<
", 500 queries=" << queryMs <<
"ms"
655 <<
", parent size=" << parent.
size()
656 <<
", clone size=" << clone.size() );
669 std::mt19937
rng( 789 );
670 auto boxes = generateRandomBoxes(
N, 100000, 5000,
rng );
674 for(
int i = 0; i <
N; ++i )
675 root.
Insert( boxes[i].min, boxes[i].max,
static_cast<intptr_t
>( i ) );
677 const std::vector<int> depths = { 1, 2, 3, 5, 10 };
679 for(
int depth : depths )
684 std::vector<KIRTREE::COW_RTREE<intptr_t, int, 2>>
chain;
687 for(
int d = 1; d < depth; ++d )
690 double cloneMs = timer.
msecs();
693 auto queries = generateRandomBoxes( 200, 100000, 20000,
rng );
699 for(
const BBOX& q : queries )
701 auto visitor = [&totalFound]( intptr_t )
707 chain.back().Search( q.min, q.max, visitor );
710 double queryMs = queryTimer.
msecs();
713 <<
": clone chain=" << cloneMs <<
"ms"
714 <<
", 200 queries=" << queryMs <<
"ms"
715 <<
", found=" << totalFound );
BOOST_AUTO_TEST_CASE(DRCWorkload)
Copy-on-Write wrapper for DYNAMIC_RTREE.
void Insert(const ELEMTYPE aMin[NUMDIMS], const ELEMTYPE aMax[NUMDIMS], const DATATYPE &aData)
Insert an item.
COW_RTREE Clone() const
Create a clone that shares the tree structure with this tree.
Dynamic R*-tree with SoA node layout and stored insertion bounding boxes.
int Search(const ELEMTYPE aMin[NUMDIMS], const ELEMTYPE aMax[NUMDIMS], VISITOR &aVisitor) const
Search for items whose bounding boxes overlap the query rectangle.
size_t MemoryUsage() const
Return approximate memory usage in bytes.
bool Remove(const ELEMTYPE aMin[NUMDIMS], const ELEMTYPE aMax[NUMDIMS], const DATATYPE &aData)
Remove an item using its stored insertion bounding box.
void Insert(const ELEMTYPE aMin[NUMDIMS], const ELEMTYPE aMax[NUMDIMS], const DATATYPE &aData)
Insert an item with the given bounding box.
Builder for constructing a PACKED_RTREE from a set of items.
void Add(const ELEMTYPE aMin[NUMDIMS], const ELEMTYPE aMax[NUMDIMS], const DATATYPE &aData)
void Reserve(size_t aCount)
A small class to help profiling.
void Start()
Start or restart the counter.
double msecs(bool aSinceLast=false)
static thread_local boost::mt19937 rng
std::string implementation
BOOST_AUTO_TEST_CASE(HorizontalAlignment)
BOOST_AUTO_TEST_SUITE(CadstarPartParser)
BOOST_AUTO_TEST_SUITE_END()
BOOST_TEST_MESSAGE("\n=== Real-World Polygon PIP Benchmark ===\n"<< formatTable(table))
const SHAPE_LINE_CHAIN chain
wxString result
Test unit parsing edge cases and error handling.
BOOST_CHECK_EQUAL(result, "25.4")