Skip to content

Commit

Permalink
Merge pull request #52 from Workiva/bulk-loading
Browse files Browse the repository at this point in the history
DT-23024 Bulk loading
  • Loading branch information
rm-astro-wf authored Aug 8, 2023
2 parents ceca7b3 + 2cb8077 commit 8877ecf
Show file tree
Hide file tree
Showing 14 changed files with 846 additions and 218 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/dart_ci.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ jobs:
- name: Validate dependencies
run: dart run dependency_validator
- name: Check formatting
run: dart format --output=none --set-exit-if-changed .
run: dart format -l 120 --output=none --set-exit-if-changed .
if: ${{ matrix.sdk == '2.18.7' }}
- name: Analyze project source
run: dart analyze
Expand Down
194 changes: 117 additions & 77 deletions benchmark/benchmarks.dart
Original file line number Diff line number Diff line change
Expand Up @@ -4,46 +4,103 @@ import 'package:benchmark_harness/benchmark_harness.dart';

import 'package:r_tree/r_tree.dart';

final int BRANCH_FACTOR = 16;

final int branchFactor = 16;
final int randomSeed = 3;
main() {
print('Running benchmark...');
print('Running benchmarks...');
var collector = ScoreCollector();
InsertBenchmark(collector).report();
InsertBenchmark(collector, totalItems: 100).report();
InsertBenchmark(collector, totalItems: 1000).report();
InsertBenchmark(collector, totalItems: 10000).report();

LoadBenchmark(collector, totalItems: 100).report();
LoadBenchmark(collector, totalItems: 1000).report();
LoadBenchmark(collector, totalItems: 10000).report();

RemoveBenchmark(collector).report();
SearchBenchmark1(collector).report();
SearchBenchmark2(collector).report();
SearchBenchmark1(collector, iterateAll: true).report();
SearchBenchmark2(collector, iterateAll: true).report();

var output = '\nName\tResult (microseconds)\n';
SearchBenchmark(collector, totalItems: 100, iterateAll: true).report();
SearchBenchmark(collector, totalItems: 1000, iterateAll: true).report();
SearchBenchmark(collector, totalItems: 10000, iterateAll: true).report();

SearchBenchmark(collector, totalItems: 100, iterateAll: true, useLoad: true).report();
SearchBenchmark(collector, totalItems: 1000, iterateAll: true, useLoad: true).report();
SearchBenchmark(collector, totalItems: 10000, iterateAll: true, useLoad: true).report();

var longestName =
collector.collected.keys.reduce((value, element) => value.length > element.length ? value : element).length;
var longestValue = collector.collected.values
.reduce((value, element) => value.toStringAsFixed(2).length > element.toStringAsFixed(2).length ? value : element)
.toStringAsFixed(2);
var nameHeading = 'Name';
var heading = '$nameHeading${' ' * (longestName - nameHeading.length)}\tResult (microseconds)';
var separator = '-' * (heading.length + 5);
var output = '\n$heading\n$separator\n';
collector.collected.forEach((String name, double value) {
output += '$name\t$value\n';
name += (' ' * (longestName - name.length));
var valueString = value.toStringAsFixed(2);
output += '$name\t${' ' * (longestValue.length - valueString.length)}${valueString}\n';
});

print(output);
}

class InsertBenchmark extends RTreeBenchmarkBase {
InsertBenchmark(ScoreCollector collector) : super("Insert 5k", collector);
final int totalItems;

InsertBenchmark(ScoreCollector collector, {this.totalItems = 500}) : super("Insert $totalItems", collector);

late RTree<String> tree;
late List<RTreeDatum<String>> datum;

void run() {
final rand = Random();
for (int i = 0; i < 5000; i++) {
int x = rand.nextInt(100000);
int y = rand.nextInt(100000);
tree = RTree<String>(branchFactor);
for (var data in datum) {
tree.insert(data);
}
}

void setup() {
Random rand = Random(randomSeed);
datum = <RTreeDatum<String>>[];
for (int i = 0; i < totalItems; i++) {
int x = rand.nextInt(1000);
int y = rand.nextInt(1000);
int height = rand.nextInt(100);
int width = rand.nextInt(100);
final item =
RTreeDatum<String>(Rectangle(x, y, width, height), 'item $i');
tree.insert(item);
final item = RTreeDatum<String>(Rectangle(x, y, width, height), 'item $i');
datum.add(item);
}
}

void teardown() {}
}

class LoadBenchmark extends RTreeBenchmarkBase {
final int totalItems;

LoadBenchmark(ScoreCollector collector, {required this.totalItems}) : super("Load $totalItems ", collector);

late RTree<String> tree;
late List<RTreeDatum<String>> datum;

void run() {
tree = RTree<String>(branchFactor);
tree.load(datum);
}

void setup() {
tree = RTree<String>(BRANCH_FACTOR);
Random rand = Random(randomSeed);
datum = <RTreeDatum<String>>[];
for (int i = 0; i < totalItems; i++) {
int x = rand.nextInt(1000);
int y = rand.nextInt(1000);
int height = rand.nextInt(100);
int width = rand.nextInt(100);
final item = RTreeDatum<String>(Rectangle(x, y, width, height), 'item $i');
datum.add(item);
}
datum.shuffle();
}

void teardown() {}
Expand All @@ -64,7 +121,7 @@ class RemoveBenchmark extends RTreeBenchmarkBase {
}

void setup() {
tree = RTree<String>(BRANCH_FACTOR);
tree = RTree<String>(branchFactor);

for (int i = 0; i < 100; i++) {
for (int j = 0; j < 100; j++) {
Expand All @@ -73,25 +130,40 @@ class RemoveBenchmark extends RTreeBenchmarkBase {
}

Rectangle rect = Rectangle(i, j, 1, 1);
items[i].add(RTreeDatum<String>(rect, 'item $i:$j'));
final datum = RTreeDatum<String>(rect, 'item $i:$j');
items[i].add(datum);
tree.insert(datum);
}
}
}

void teardown() {}
}

class SearchBenchmark1 extends RTreeBenchmarkBase {
class SearchBenchmark extends RTreeBenchmarkBase {
final int totalItems;

/// Allows comparing search performance if the results are iterated or not.
final bool iterateAll;
SearchBenchmark1(ScoreCollector collector, {this.iterateAll = false})
: super("Search${iterateAll ? '/Iterate' : ''} 5k", collector);

/// Allows comparing search performance between trees built out via insert or load
final bool useLoad;

SearchBenchmark(
ScoreCollector collector, {
required this.totalItems,
this.iterateAll = false,
this.useLoad = false,
}) : super("Search${iterateAll ? '/Iterate' : ''} ${useLoad ? '(using Load)' : '(using Insert)'} ${totalItems}",
collector);

late RTree<String> tree;
late int size;

void run() {
for (int i = 0; i < 10; i++) {
for (int j = 0; j < 50; j++) {
var results = tree.search(Rectangle(i, j, 1, 1));
for (int x = 0; x < size; x++) {
for (int y = 0; y < size; y++) {
var results = tree.search(Rectangle(x, y, 1, 1));
if (iterateAll) {
// ignore: unused_local_variable
for (var result in results) {
Expand All @@ -103,60 +175,30 @@ class SearchBenchmark1 extends RTreeBenchmarkBase {
}

void setup() {
tree = RTree(BRANCH_FACTOR);
size = sqrt(totalItems).ceil();
tree = RTree(branchFactor);

var datum = <RTreeDatum<String>>[];
for (int i = 0; i < 10; i++) {
for (int j = 0; j < 50; j++) {
Rectangle rect = Rectangle(i, j, 1, 1);
tree.insert(RTreeDatum<String>(rect, 'item1'));
tree.insert(RTreeDatum<String>(rect, 'item2'));
tree.insert(RTreeDatum<String>(rect, 'item3'));
tree.insert(RTreeDatum<String>(rect, 'item4'));
tree.insert(RTreeDatum<String>(rect, 'item5'));
tree.insert(RTreeDatum<String>(rect, 'item6'));
tree.insert(RTreeDatum<String>(rect, 'item7'));
tree.insert(RTreeDatum<String>(rect, 'item8'));
tree.insert(RTreeDatum<String>(rect, 'item9'));
tree.insert(RTreeDatum<String>(rect, 'item10'));
}
}
}

void teardown() {}
}

class SearchBenchmark2 extends RTreeBenchmarkBase {
final bool iterateAll;

SearchBenchmark2(ScoreCollector collector, {this.iterateAll = false})
: super("Search${iterateAll ? '/Iterate' : ''} 30k", collector);

late RTree<String> tree;

void run() {
for (int i = 0; i < 100; i++) {
for (int j = 0; j < 50; j++) {
var results = tree.search(Rectangle(i, j, 1, 1));
if (iterateAll) {
// ignore: unused_local_variable
for (var result in results) {
// nothing to do here, just iterating over every result once
}
}
datum.add(RTreeDatum<String>(rect, 'item1'));
datum.add(RTreeDatum<String>(rect, 'item2'));
datum.add(RTreeDatum<String>(rect, 'item3'));
datum.add(RTreeDatum<String>(rect, 'item4'));
datum.add(RTreeDatum<String>(rect, 'item5'));
datum.add(RTreeDatum<String>(rect, 'item6'));
datum.add(RTreeDatum<String>(rect, 'item7'));
datum.add(RTreeDatum<String>(rect, 'item8'));
datum.add(RTreeDatum<String>(rect, 'item9'));
datum.add(RTreeDatum<String>(rect, 'item10'));
}
}
}

void setup() {
tree = RTree<String>(BRANCH_FACTOR);

for (int i = 0; i < 100; i++) {
for (int j = 0; j < 100; j++) {
Rectangle rect = Rectangle(i, j, 1, 1);
tree.insert(RTreeDatum<String>(rect, 'item1 $i:$j'));
tree.insert(RTreeDatum<String>(rect, 'item2 $i:$j'));
tree.insert(RTreeDatum<String>(rect, 'item3 $i:$j'));
}
if (useLoad) {
tree.load(datum);
} else {
datum.forEach(tree.insert);
}
}

Expand All @@ -166,9 +208,7 @@ class SearchBenchmark2 extends RTreeBenchmarkBase {
class RTreeBenchmarkBase extends BenchmarkBase {
final int iterations;

RTreeBenchmarkBase(String name, ScoreCollector collector,
{this.iterations = 100})
: super(name, emitter: collector);
RTreeBenchmarkBase(String name, ScoreCollector collector, {this.iterations = 100}) : super(name, emitter: collector);

@override
void exercise() {
Expand Down
8 changes: 7 additions & 1 deletion example/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,11 @@
<title>r_tree example</title>
</head>
<body>
<div id="app"></div>
<div id="app" style="border: 1px solid #000000; width: min-content;"></div>
<button id="insert">Generate and insert (one-by-one)</button>
<button id="load">Generate and load (bulk)</button>
<button id="clear">Clear</button>

<div>
<p>Click a button to switch paint brush, then click & drag to paint a rectangle, which is inserted into the RTree. Use search to query the RTree.</p>
<button id="red">Red</button>
Expand All @@ -16,6 +20,8 @@
<ol id="results">
<li>Add rectangles and then use search to populate the results list</li>
</ol>
<button id="graphviz">Generate GraphViz</button> <button id="copy">Copy Results</button>
<pre id="output"></pre>
<script type="application/javascript" src="/main.dart.js"></script>
</body>
</html>
Loading

0 comments on commit 8877ecf

Please sign in to comment.