26#include <geometry/rtree.h>
32#include <nlohmann/json.hpp>
50std::vector<BBOX> generateRandomBoxes(
int aCount,
int aMaxCoord,
int aMaxSize,
53 std::uniform_int_distribution<int> coordDist( 0, aMaxCoord );
54 std::uniform_int_distribution<int> sizeDist( 1, aMaxSize );
56 std::vector<BBOX> boxes( aCount );
58 for(
int i = 0; i < aCount; ++i )
60 boxes[i].min[0] = coordDist( aRng );
61 boxes[i].min[1] = coordDist( aRng );
62 boxes[i].max[0] = boxes[i].min[0] + sizeDist( aRng );
63 boxes[i].max[1] = boxes[i].min[1] + sizeDist( aRng );
92 const std::vector<int> scales = { 1000, 10000, 100000 };
93 std::vector<BENCH_RESULT> results;
97 std::mt19937
rng( 42 );
98 auto boxes = generateRandomBoxes(
N, 1000000, 10000,
rng );
101 auto queries = generateRandomBoxes( 1000, 1000000, 50000,
rng );
107 result.implementation =
"OldTree";
113 RTree<intptr_t, int, 2, double, 8, 4> oldTree;
115 for(
int i = 0; i <
N; ++i )
116 oldTree.Insert( boxes[i].min, boxes[i].max,
static_cast<intptr_t
>( i ) );
124 for(
const BBOX& q : queries )
126 auto visitor = [&totalFound]( intptr_t )
132 oldTree.Search( q.min, q.max, visitor );
136 result.queryCount =
static_cast<int>( queries.size() );
138 ? ( queries.size() / (
result.queryMs / 1e3 ) )
141 results.push_back(
result );
148 result.implementation =
"PackedTree";
157 for(
int i = 0; i <
N; ++i )
158 builder.
Add( boxes[i].min, boxes[i].max,
static_cast<intptr_t
>( i ) );
160 auto packedTree = builder.
Build();
162 result.memoryBytes = packedTree.MemoryUsage();
168 for(
const BBOX& q : queries )
170 auto visitor = [&totalFound]( intptr_t )
176 packedTree.Search( q.min, q.max, visitor );
180 result.queryCount =
static_cast<int>( queries.size() );
182 ? ( queries.size() / (
result.queryMs / 1e3 ) )
185 results.push_back(
result );
192 result.implementation =
"DynTree";
200 for(
int i = 0; i <
N; ++i )
201 dynTree.
Insert( boxes[i].min, boxes[i].max,
static_cast<intptr_t
>( i ) );
210 for(
const BBOX& q : queries )
212 auto visitor = [&totalFound]( intptr_t )
218 dynTree.
Search( q.min, q.max, visitor );
222 result.queryCount =
static_cast<int>( queries.size() );
224 ? ( queries.size() / (
result.queryMs / 1e3 ) )
227 results.push_back(
result );
236 nlohmann::json entry;
237 entry[
"workload"] = r.workload;
238 entry[
"implementation"] = r.implementation;
239 entry[
"itemCount"] = r.itemCount;
240 entry[
"buildMs"] = r.buildMs;
241 entry[
"queryMs"] = r.queryMs;
242 entry[
"queryCount"] = r.queryCount;
243 entry[
"queriesPerSec"] = r.queriesPerSec;
244 entry[
"memoryBytes"] = r.memoryBytes;
245 j.push_back( entry );
249 std::filesystem::path outputDir( std::filesystem::temp_directory_path()
251 std::filesystem::create_directories( outputDir );
252 std::ofstream out( outputDir /
"spatial_index_drc.json" );
255 out << j.dump( 2 ) << std::endl;
258 for(
size_t i = 0; i + 2 < results.size(); i += 3 )
260 if( results[i].itemCount >= 10000 )
262 double oldQps = results[i].queriesPerSec;
263 double packedQps = results[i + 1].queriesPerSec;
264 double dynQps = results[i + 2].queriesPerSec;
266 BOOST_CHECK_GE( packedQps, oldQps );
267 BOOST_CHECK_GE( dynQps, oldQps );
276 const std::vector<int> scales = { 100, 1000, 5000 };
278 for(
int N : scales )
282 std::mt19937
rng( 123 );
283 auto boxes = generateRandomBoxes(
N, 100000, 5000,
rng );
285 RTree<intptr_t, int, 2, double, 8, 4> oldTree;
287 for(
int i = 0; i <
N; ++i )
288 oldTree.Insert( boxes[i].min, boxes[i].max,
static_cast<intptr_t
>( i ) );
290 std::uniform_int_distribution<int> opDist( 0, 9 );
291 std::uniform_int_distribution<int> coordDist( 0, 100000 );
292 std::uniform_int_distribution<int> sizeDist( 1, 5000 );
299 for(
int op = 0; op < ops; ++op )
301 int which = opDist(
rng );
305 int min[2] = { coordDist(
rng ), coordDist(
rng ) };
306 int max[2] = { min[0] + sizeDist(
rng ), min[1] + sizeDist(
rng ) };
307 oldTree.Insert( min, max,
static_cast<intptr_t
>(
N + op ) );
309 else if( which == 1 && !boxes.empty() )
311 std::uniform_int_distribution<int> idxDist( 0, boxes.size() - 1 );
312 int idx = idxDist(
rng );
314 oldTree.Remove( boxes[idx].min, boxes[idx].max,
315 static_cast<intptr_t
>( idx ) );
319 int min[2] = { coordDist(
rng ), coordDist(
rng ) };
320 int max[2] = { min[0] + sizeDist(
rng ) * 5,
321 min[1] + sizeDist(
rng ) * 5 };
324 auto visitor = [&found]( intptr_t )
330 oldTree.Search( min, max, visitor );
334 double oldMs = timer.
msecs();
337 <<
" ops in " << oldMs <<
"ms" );
342 std::mt19937
rng( 123 );
343 auto boxes = generateRandomBoxes(
N, 100000, 5000,
rng );
347 for(
int i = 0; i <
N; ++i )
348 dynTree.
Insert( boxes[i].min, boxes[i].max,
static_cast<intptr_t
>( i ) );
350 std::uniform_int_distribution<int> opDist( 0, 9 );
351 std::uniform_int_distribution<int> coordDist( 0, 100000 );
352 std::uniform_int_distribution<int> sizeDist( 1, 5000 );
362 for(
int op = 0; op < ops; ++op )
364 int which = opDist(
rng );
368 int min[2] = { coordDist(
rng ), coordDist(
rng ) };
369 int max[2] = { min[0] + sizeDist(
rng ), min[1] + sizeDist(
rng ) };
370 dynTree.
Insert( min, max,
static_cast<intptr_t
>(
N + insertCount ) );
373 else if( which == 1 && !boxes.empty() )
375 std::uniform_int_distribution<int> idxDist( 0, boxes.size() - 1 );
376 int idx = idxDist(
rng );
378 dynTree.
Remove( boxes[idx].min, boxes[idx].max,
379 static_cast<intptr_t
>( idx ) );
384 int min[2] = { coordDist(
rng ), coordDist(
rng ) };
385 int max[2] = { min[0] + sizeDist(
rng ) * 5,
386 min[1] + sizeDist(
rng ) * 5 };
389 auto visitor = [&found]( intptr_t )
395 dynTree.
Search( min, max, visitor );
400 double elapsedMs = timer.
msecs();
403 << elapsedMs <<
"ms ("
404 << insertCount <<
" ins, "
405 << removeCount <<
" rem, "
406 << queryCount <<
" qry)" );
416 const std::vector<int> scales = { 1000, 10000, 100000 };
417 std::vector<BENCH_RESULT> results;
419 for(
int N : scales )
421 std::mt19937
rng( 77 );
424 auto boxes = generateRandomBoxes(
N, 400000000, 2000000,
rng );
427 auto queries = generateRandomBoxes( 2000, 400000000, 50000000,
rng );
432 result.workload =
"Viewport";
433 result.implementation =
"OldTree";
436 RTree<intptr_t, int, 2, double, 8, 4> oldTree;
441 for(
int i = 0; i <
N; ++i )
442 oldTree.Insert( boxes[i].min, boxes[i].max,
static_cast<intptr_t
>( i ) );
449 for(
const BBOX& q : queries )
451 auto visitor = []( intptr_t ) {
return true; };
452 oldTree.Search( q.min, q.max, visitor );
456 result.queryCount =
static_cast<int>( queries.size() );
458 ? ( queries.size() / (
result.queryMs / 1e3 ) )
461 results.push_back(
result );
467 result.workload =
"Viewport";
468 result.implementation =
"DynTree";
476 for(
int i = 0; i <
N; ++i )
477 dynTree.
Insert( boxes[i].min, boxes[i].max,
static_cast<intptr_t
>( i ) );
485 for(
const BBOX& q : queries )
487 auto visitor = []( intptr_t ) {
return true; };
488 dynTree.
Search( q.min, q.max, visitor );
492 result.queryCount =
static_cast<int>( queries.size() );
494 ? ( queries.size() / (
result.queryMs / 1e3 ) )
497 results.push_back(
result );
506 nlohmann::json entry;
507 entry[
"workload"] = r.workload;
508 entry[
"implementation"] = r.implementation;
509 entry[
"itemCount"] = r.itemCount;
510 entry[
"buildMs"] = r.buildMs;
511 entry[
"queryMs"] = r.queryMs;
512 entry[
"queryCount"] = r.queryCount;
513 entry[
"queriesPerSec"] = r.queriesPerSec;
514 entry[
"memoryBytes"] = r.memoryBytes;
515 j.push_back( entry );
518 std::filesystem::path outputDir( std::filesystem::temp_directory_path() /
"kicad_bench" );
519 std::filesystem::create_directories( outputDir );
520 std::ofstream out( outputDir /
"spatial_index_viewport.json" );
523 out << j.dump( 2 ) << std::endl;
526 for(
size_t i = 0; i + 1 < results.size(); i += 2 )
528 if( results[i].itemCount >= 10000 )
530 double oldQps = results[i].queriesPerSec;
531 double dynQps = results[i + 1].queriesPerSec;
533 BOOST_CHECK_GE( dynQps, oldQps );
543 const std::vector<int> scales = { 1000, 5000, 15000 };
545 for(
int N : scales )
547 std::mt19937
rng( 555 );
548 auto boxes = generateRandomBoxes(
N, 100000, 5000,
rng );
552 RTree<intptr_t, int, 2, double, 8, 4> srcTree;
554 for(
int i = 0; i <
N; ++i )
555 srcTree.Insert( boxes[i].min, boxes[i].max,
static_cast<intptr_t
>( i ) );
560 RTree<intptr_t, int, 2, double, 8, 4> dstTree;
562 auto copyVisitor = [&dstTree, &boxes]( intptr_t val )
564 int idx =
static_cast<int>( val );
566 dstTree.Insert( boxes[idx].min, boxes[idx].max, val );
570 int allMin[2] = { INT_MIN, INT_MIN };
571 int allMax[2] = { INT_MAX, INT_MAX };
572 srcTree.Search( allMin, allMax, copyVisitor );
574 double oldCopyMs = timer.
msecs();
583 for(
int i = 0; i <
N; ++i )
584 srcTree.
Insert( boxes[i].min, boxes[i].max,
static_cast<intptr_t
>( i ) );
589 auto clone = srcTree.
Clone();
591 double cloneMs = timer.
msecs();
605 const std::vector<int> scales = { 1000, 5000 };
607 for(
int N : scales )
609 std::mt19937
rng( 456 );
610 auto boxes = generateRandomBoxes(
N, 100000, 5000,
rng );
614 for(
int i = 0; i <
N; ++i )
615 parent.
Insert( boxes[i].min, boxes[i].max,
static_cast<intptr_t
>( i ) );
621 auto clone = parent.
Clone();
622 double cloneMs = timer.
msecs();
625 std::uniform_int_distribution<int> coordDist( 0, 100000 );
626 std::uniform_int_distribution<int> sizeDist( 1, 5000 );
628 for(
int i = 0; i < 100; ++i )
630 int min[2] = { coordDist(
rng ), coordDist(
rng ) };
631 int max[2] = { min[0] + sizeDist(
rng ), min[1] + sizeDist(
rng ) };
632 clone.Insert( min, max,
static_cast<intptr_t
>(
N + i ) );
636 auto queries = generateRandomBoxes( 500, 100000, 20000,
rng );
641 for(
const BBOX& q : queries )
645 auto visitor = [&found]( intptr_t )
651 clone.Search( q.min, q.max, visitor );
654 double queryMs = queryTimer.
msecs();
657 <<
": clone=" << cloneMs <<
"ms"
658 <<
", 500 queries=" << queryMs <<
"ms"
659 <<
", parent size=" << parent.
size()
660 <<
", clone size=" << clone.size() );
673 std::mt19937
rng( 789 );
674 auto boxes = generateRandomBoxes(
N, 100000, 5000,
rng );
678 for(
int i = 0; i <
N; ++i )
679 root.
Insert( boxes[i].min, boxes[i].max,
static_cast<intptr_t
>( i ) );
681 const std::vector<int> depths = { 1, 2, 3, 5, 10 };
683 for(
int depth : depths )
688 std::vector<KIRTREE::COW_RTREE<intptr_t, int, 2>>
chain;
691 for(
int d = 1; d < depth; ++d )
694 double cloneMs = timer.
msecs();
697 auto queries = generateRandomBoxes( 200, 100000, 20000,
rng );
703 for(
const BBOX& q : queries )
705 auto visitor = [&totalFound]( intptr_t )
711 chain.back().Search( q.min, q.max, visitor );
714 double queryMs = queryTimer.
msecs();
717 <<
": clone chain=" << cloneMs <<
"ms"
718 <<
", 200 queries=" << queryMs <<
"ms"
719 <<
", 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")