Squashed 'vendor/ruvector/' content from commit b64c2172
git-subtree-dir: vendor/ruvector git-subtree-split: b64c21726f2bb37286d9ee36a7869fef60cc6900
This commit is contained in:
216
npm/packages/graph-node/benchmark.js
Normal file
216
npm/packages/graph-node/benchmark.js
Normal file
@@ -0,0 +1,216 @@
|
||||
/**
|
||||
* RuVector Graph Node Benchmark
|
||||
*
|
||||
* Tests performance of graph operations including:
|
||||
* - Node creation
|
||||
* - Edge creation
|
||||
* - Hyperedge creation
|
||||
* - Batch inserts
|
||||
* - Vector similarity search
|
||||
* - k-hop neighbor traversal
|
||||
* - Cypher queries
|
||||
*/
|
||||
|
||||
const { GraphDatabase, version } = require('./index.js');
|
||||
|
||||
const DIMENSIONS = 384;
|
||||
const NUM_NODES = 10000;
|
||||
const NUM_EDGES = 50000;
|
||||
const NUM_HYPEREDGES = 5000;
|
||||
const SEARCH_K = 10;
|
||||
|
||||
function randomEmbedding(dims) {
|
||||
const arr = new Float32Array(dims);
|
||||
for (let i = 0; i < dims; i++) {
|
||||
arr[i] = Math.random();
|
||||
}
|
||||
return arr;
|
||||
}
|
||||
|
||||
function formatTime(ms) {
|
||||
if (ms < 1) return `${(ms * 1000).toFixed(2)}μs`;
|
||||
if (ms < 1000) return `${ms.toFixed(2)}ms`;
|
||||
return `${(ms / 1000).toFixed(2)}s`;
|
||||
}
|
||||
|
||||
function formatOps(count, ms) {
|
||||
const ops = (count / ms) * 1000;
|
||||
if (ops >= 1000000) return `${(ops / 1000000).toFixed(2)}M ops/sec`;
|
||||
if (ops >= 1000) return `${(ops / 1000).toFixed(2)}K ops/sec`;
|
||||
return `${ops.toFixed(2)} ops/sec`;
|
||||
}
|
||||
|
||||
async function benchmark() {
|
||||
console.log('╔════════════════════════════════════════════════════════════════╗');
|
||||
console.log('║ RuVector Graph Node Benchmark Suite ║');
|
||||
console.log('╠════════════════════════════════════════════════════════════════╣');
|
||||
console.log(`║ Version: ${version().padEnd(54)}║`);
|
||||
console.log(`║ Dimensions: ${DIMENSIONS.toString().padEnd(51)}║`);
|
||||
console.log(`║ Nodes: ${NUM_NODES.toLocaleString().padEnd(56)}║`);
|
||||
console.log(`║ Edges: ${NUM_EDGES.toLocaleString().padEnd(56)}║`);
|
||||
console.log(`║ Hyperedges: ${NUM_HYPEREDGES.toLocaleString().padEnd(51)}║`);
|
||||
console.log('╚════════════════════════════════════════════════════════════════╝\n');
|
||||
|
||||
const db = new GraphDatabase({
|
||||
distanceMetric: 'Cosine',
|
||||
dimensions: DIMENSIONS
|
||||
});
|
||||
|
||||
const results = [];
|
||||
|
||||
// Benchmark 1: Node Creation
|
||||
console.log('📌 Benchmark 1: Individual Node Creation');
|
||||
const nodeCount = 1000;
|
||||
const nodeStart = performance.now();
|
||||
for (let i = 0; i < nodeCount; i++) {
|
||||
await db.createNode({
|
||||
id: `node_${i}`,
|
||||
embedding: randomEmbedding(DIMENSIONS),
|
||||
labels: ['TestNode'],
|
||||
properties: { index: String(i) }
|
||||
});
|
||||
}
|
||||
const nodeEnd = performance.now();
|
||||
const nodeTime = nodeEnd - nodeStart;
|
||||
console.log(` Created ${nodeCount} nodes in ${formatTime(nodeTime)}`);
|
||||
console.log(` Throughput: ${formatOps(nodeCount, nodeTime)}\n`);
|
||||
results.push({ name: 'Node Creation', count: nodeCount, time: nodeTime });
|
||||
|
||||
// Benchmark 2: Batch Node Creation
|
||||
console.log('📌 Benchmark 2: Batch Node Creation');
|
||||
const batchSize = 1000;
|
||||
const batchNodes = [];
|
||||
for (let i = 0; i < batchSize; i++) {
|
||||
batchNodes.push({
|
||||
id: `batch_node_${i}`,
|
||||
embedding: randomEmbedding(DIMENSIONS),
|
||||
labels: ['BatchNode']
|
||||
});
|
||||
}
|
||||
const batchNodeStart = performance.now();
|
||||
await db.batchInsert({ nodes: batchNodes, edges: [] });
|
||||
const batchNodeEnd = performance.now();
|
||||
const batchNodeTime = batchNodeEnd - batchNodeStart;
|
||||
console.log(` Inserted ${batchSize} nodes in ${formatTime(batchNodeTime)}`);
|
||||
console.log(` Throughput: ${formatOps(batchSize, batchNodeTime)}\n`);
|
||||
results.push({ name: 'Batch Node Creation', count: batchSize, time: batchNodeTime });
|
||||
|
||||
// Benchmark 3: Edge Creation
|
||||
console.log('📌 Benchmark 3: Edge Creation');
|
||||
const edgeCount = 1000;
|
||||
const edgeStart = performance.now();
|
||||
for (let i = 0; i < edgeCount; i++) {
|
||||
const from = `node_${i % nodeCount}`;
|
||||
const to = `node_${(i + 1) % nodeCount}`;
|
||||
await db.createEdge({
|
||||
from,
|
||||
to,
|
||||
description: 'CONNECTED_TO',
|
||||
embedding: randomEmbedding(DIMENSIONS),
|
||||
confidence: Math.random()
|
||||
});
|
||||
}
|
||||
const edgeEnd = performance.now();
|
||||
const edgeTime = edgeEnd - edgeStart;
|
||||
console.log(` Created ${edgeCount} edges in ${formatTime(edgeTime)}`);
|
||||
console.log(` Throughput: ${formatOps(edgeCount, edgeTime)}\n`);
|
||||
results.push({ name: 'Edge Creation', count: edgeCount, time: edgeTime });
|
||||
|
||||
// Benchmark 4: Hyperedge Creation
|
||||
console.log('📌 Benchmark 4: Hyperedge Creation');
|
||||
const hyperedgeCount = 500;
|
||||
const hyperedgeStart = performance.now();
|
||||
for (let i = 0; i < hyperedgeCount; i++) {
|
||||
const nodes = [];
|
||||
const numNodes = 3 + Math.floor(Math.random() * 5); // 3-7 nodes per hyperedge
|
||||
for (let j = 0; j < numNodes; j++) {
|
||||
nodes.push(`node_${(i + j) % nodeCount}`);
|
||||
}
|
||||
await db.createHyperedge({
|
||||
nodes,
|
||||
description: `RELATIONSHIP_${i}`,
|
||||
embedding: randomEmbedding(DIMENSIONS),
|
||||
confidence: Math.random()
|
||||
});
|
||||
}
|
||||
const hyperedgeEnd = performance.now();
|
||||
const hyperedgeTime = hyperedgeEnd - hyperedgeStart;
|
||||
console.log(` Created ${hyperedgeCount} hyperedges in ${formatTime(hyperedgeTime)}`);
|
||||
console.log(` Throughput: ${formatOps(hyperedgeCount, hyperedgeTime)}\n`);
|
||||
results.push({ name: 'Hyperedge Creation', count: hyperedgeCount, time: hyperedgeTime });
|
||||
|
||||
// Benchmark 5: Vector Similarity Search
|
||||
console.log('📌 Benchmark 5: Vector Similarity Search');
|
||||
const searchCount = 100;
|
||||
const searchStart = performance.now();
|
||||
for (let i = 0; i < searchCount; i++) {
|
||||
await db.searchHyperedges({
|
||||
embedding: randomEmbedding(DIMENSIONS),
|
||||
k: SEARCH_K
|
||||
});
|
||||
}
|
||||
const searchEnd = performance.now();
|
||||
const searchTime = searchEnd - searchStart;
|
||||
console.log(` Performed ${searchCount} searches (k=${SEARCH_K}) in ${formatTime(searchTime)}`);
|
||||
console.log(` Throughput: ${formatOps(searchCount, searchTime)}\n`);
|
||||
results.push({ name: 'Vector Search', count: searchCount, time: searchTime });
|
||||
|
||||
// Benchmark 6: k-hop Neighbor Traversal
|
||||
console.log('📌 Benchmark 6: k-hop Neighbor Traversal');
|
||||
const traversalCount = 100;
|
||||
const traversalStart = performance.now();
|
||||
for (let i = 0; i < traversalCount; i++) {
|
||||
await db.kHopNeighbors(`node_${i % nodeCount}`, 2);
|
||||
}
|
||||
const traversalEnd = performance.now();
|
||||
const traversalTime = traversalEnd - traversalStart;
|
||||
console.log(` Performed ${traversalCount} 2-hop traversals in ${formatTime(traversalTime)}`);
|
||||
console.log(` Throughput: ${formatOps(traversalCount, traversalTime)}\n`);
|
||||
results.push({ name: 'k-hop Traversal', count: traversalCount, time: traversalTime });
|
||||
|
||||
// Benchmark 7: Statistics Query
|
||||
console.log('📌 Benchmark 7: Statistics Query');
|
||||
const statsCount = 1000;
|
||||
const statsStart = performance.now();
|
||||
for (let i = 0; i < statsCount; i++) {
|
||||
await db.stats();
|
||||
}
|
||||
const statsEnd = performance.now();
|
||||
const statsTime = statsEnd - statsStart;
|
||||
console.log(` Performed ${statsCount} stats queries in ${formatTime(statsTime)}`);
|
||||
console.log(` Throughput: ${formatOps(statsCount, statsTime)}\n`);
|
||||
results.push({ name: 'Stats Query', count: statsCount, time: statsTime });
|
||||
|
||||
// Benchmark 8: Transaction Overhead
|
||||
console.log('📌 Benchmark 8: Transaction Overhead');
|
||||
const txCount = 100;
|
||||
const txStart = performance.now();
|
||||
for (let i = 0; i < txCount; i++) {
|
||||
const txId = await db.begin();
|
||||
await db.commit(txId);
|
||||
}
|
||||
const txEnd = performance.now();
|
||||
const txTime = txEnd - txStart;
|
||||
console.log(` Performed ${txCount} transactions in ${formatTime(txTime)}`);
|
||||
console.log(` Throughput: ${formatOps(txCount, txTime)}\n`);
|
||||
results.push({ name: 'Transaction', count: txCount, time: txTime });
|
||||
|
||||
// Summary
|
||||
console.log('╔════════════════════════════════════════════════════════════════╗');
|
||||
console.log('║ BENCHMARK SUMMARY ║');
|
||||
console.log('╠════════════════════════════════════════════════════════════════╣');
|
||||
for (const r of results) {
|
||||
const ops = formatOps(r.count, r.time);
|
||||
console.log(`║ ${r.name.padEnd(25)} ${ops.padStart(20)} ${formatTime(r.time).padStart(12)} ║`);
|
||||
}
|
||||
console.log('╚════════════════════════════════════════════════════════════════╝');
|
||||
|
||||
// Final stats
|
||||
const finalStats = await db.stats();
|
||||
console.log(`\n📊 Final Database State:`);
|
||||
console.log(` Total Nodes: ${finalStats.totalNodes.toLocaleString()}`);
|
||||
console.log(` Total Edges: ${finalStats.totalEdges.toLocaleString()}`);
|
||||
console.log(` Avg Degree: ${finalStats.avgDegree.toFixed(4)}`);
|
||||
}
|
||||
|
||||
benchmark().catch(console.error);
|
||||
Reference in New Issue
Block a user