Merge branch 'T1191-MG-implement-create-expand-with-multiframe' into T1189-MG-implement-create-node-cursor-mf
This commit is contained in:
commit
8c2b2f4be2
@ -171,7 +171,7 @@ benchmark_tag="v1.6.0"
|
||||
repo_clone_try_double "${primary_urls[gbenchmark]}" "${secondary_urls[gbenchmark]}" "benchmark" "$benchmark_tag" true
|
||||
|
||||
# google test
|
||||
googletest_tag="release-1.8.0"
|
||||
googletest_tag="release-1.12.1"
|
||||
repo_clone_try_double "${primary_urls[gtest]}" "${secondary_urls[gtest]}" "googletest" "$googletest_tag" true
|
||||
|
||||
# libbcrypt
|
||||
|
@ -283,7 +283,7 @@ std::vector<ShardToInitialize> ShardMap::AssignShards(Address storage_manager,
|
||||
// TODO(tyler) avoid these triple-nested loops by having the heartbeat include better info
|
||||
bool machine_contains_shard = false;
|
||||
|
||||
for (auto &aas : shard) {
|
||||
for (auto &aas : shard.peers) {
|
||||
if (initialized.contains(aas.address.unique_id)) {
|
||||
machine_contains_shard = true;
|
||||
if (aas.status != Status::CONSENSUS_PARTICIPANT) {
|
||||
@ -311,7 +311,7 @@ std::vector<ShardToInitialize> ShardMap::AssignShards(Address storage_manager,
|
||||
}
|
||||
}
|
||||
|
||||
if (!machine_contains_shard && shard.size() < label_space.replication_factor) {
|
||||
if (!machine_contains_shard && shard.peers.size() < label_space.replication_factor) {
|
||||
// increment version for each new uuid for deterministic creation
|
||||
IncrementShardMapVersion();
|
||||
|
||||
@ -337,7 +337,7 @@ std::vector<ShardToInitialize> ShardMap::AssignShards(Address storage_manager,
|
||||
.status = Status::INITIALIZING,
|
||||
};
|
||||
|
||||
shard.emplace_back(aas);
|
||||
shard.peers.emplace_back(aas);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -360,9 +360,9 @@ bool ShardMap::SplitShard(Hlc previous_shard_map_version, LabelId label_id, cons
|
||||
MG_ASSERT(!shards_in_map.contains(key));
|
||||
MG_ASSERT(label_spaces.contains(label_id));
|
||||
|
||||
// Finding the Shard that the new PrimaryKey should map to.
|
||||
// Finding the ShardMetadata that the new PrimaryKey should map to.
|
||||
auto prev = std::prev(shards_in_map.upper_bound(key));
|
||||
Shard duplicated_shard = prev->second;
|
||||
ShardMetadata duplicated_shard = prev->second;
|
||||
|
||||
// Apply the split
|
||||
shards_in_map[key] = duplicated_shard;
|
||||
@ -383,7 +383,7 @@ std::optional<LabelId> ShardMap::InitializeNewLabel(std::string label_name, std:
|
||||
labels.emplace(std::move(label_name), label_id);
|
||||
|
||||
PrimaryKey initial_key = SchemaToMinKey(schema);
|
||||
Shard empty_shard = {};
|
||||
ShardMetadata empty_shard = {};
|
||||
|
||||
Shards shards = {
|
||||
{initial_key, empty_shard},
|
||||
@ -479,7 +479,7 @@ Shards ShardMap::GetShardsForRange(const LabelName &label_name, const PrimaryKey
|
||||
return shards;
|
||||
}
|
||||
|
||||
Shard ShardMap::GetShardForKey(const LabelName &label_name, const PrimaryKey &key) const {
|
||||
ShardMetadata ShardMap::GetShardForKey(const LabelName &label_name, const PrimaryKey &key) const {
|
||||
MG_ASSERT(labels.contains(label_name));
|
||||
|
||||
LabelId label_id = labels.at(label_name);
|
||||
@ -492,7 +492,7 @@ Shard ShardMap::GetShardForKey(const LabelName &label_name, const PrimaryKey &ke
|
||||
return std::prev(label_space.shards.upper_bound(key))->second;
|
||||
}
|
||||
|
||||
Shard ShardMap::GetShardForKey(const LabelId &label_id, const PrimaryKey &key) const {
|
||||
ShardMetadata ShardMap::GetShardForKey(const LabelId &label_id, const PrimaryKey &key) const {
|
||||
MG_ASSERT(label_spaces.contains(label_id));
|
||||
|
||||
const auto &label_space = label_spaces.at(label_id);
|
||||
@ -556,12 +556,12 @@ EdgeTypeIdMap ShardMap::AllocateEdgeTypeIds(const std::vector<EdgeTypeName> &new
|
||||
bool ShardMap::ClusterInitialized() const {
|
||||
for (const auto &[label_id, label_space] : label_spaces) {
|
||||
for (const auto &[low_key, shard] : label_space.shards) {
|
||||
if (shard.size() < label_space.replication_factor) {
|
||||
if (shard.peers.size() < label_space.replication_factor) {
|
||||
spdlog::info("label_space below desired replication factor");
|
||||
return false;
|
||||
}
|
||||
|
||||
for (const auto &aas : shard) {
|
||||
for (const auto &aas : shard.peers) {
|
||||
if (aas.status != Status::CONSENSUS_PARTICIPANT) {
|
||||
spdlog::info("shard member not yet a CONSENSUS_PARTICIPANT");
|
||||
return false;
|
||||
|
@ -76,8 +76,35 @@ struct AddressAndStatus {
|
||||
};
|
||||
|
||||
using PrimaryKey = std::vector<PropertyValue>;
|
||||
using Shard = std::vector<AddressAndStatus>;
|
||||
using Shards = std::map<PrimaryKey, Shard>;
|
||||
|
||||
struct ShardMetadata {
|
||||
std::vector<AddressAndStatus> peers;
|
||||
uint64_t version;
|
||||
|
||||
friend std::ostream &operator<<(std::ostream &in, const ShardMetadata &shard) {
|
||||
using utils::print_helpers::operator<<;
|
||||
|
||||
in << "ShardMetadata { peers: ";
|
||||
in << shard.peers;
|
||||
in << " version: ";
|
||||
in << shard.version;
|
||||
in << " }";
|
||||
|
||||
return in;
|
||||
}
|
||||
|
||||
friend bool operator==(const ShardMetadata &lhs, const ShardMetadata &rhs) = default;
|
||||
|
||||
friend bool operator<(const ShardMetadata &lhs, const ShardMetadata &rhs) {
|
||||
if (lhs.peers != rhs.peers) {
|
||||
return lhs.peers < rhs.peers;
|
||||
}
|
||||
|
||||
return lhs.version < rhs.version;
|
||||
}
|
||||
};
|
||||
|
||||
using Shards = std::map<PrimaryKey, ShardMetadata>;
|
||||
using LabelName = std::string;
|
||||
using PropertyName = std::string;
|
||||
using EdgeTypeName = std::string;
|
||||
@ -99,7 +126,7 @@ PrimaryKey SchemaToMinKey(const std::vector<SchemaProperty> &schema);
|
||||
struct LabelSpace {
|
||||
std::vector<SchemaProperty> schema;
|
||||
// Maps between the smallest primary key stored in the shard and the shard
|
||||
std::map<PrimaryKey, Shard> shards;
|
||||
std::map<PrimaryKey, ShardMetadata> shards;
|
||||
size_t replication_factor;
|
||||
|
||||
friend std::ostream &operator<<(std::ostream &in, const LabelSpace &label_space) {
|
||||
@ -160,9 +187,9 @@ struct ShardMap {
|
||||
|
||||
Shards GetShardsForRange(const LabelName &label_name, const PrimaryKey &start_key, const PrimaryKey &end_key) const;
|
||||
|
||||
Shard GetShardForKey(const LabelName &label_name, const PrimaryKey &key) const;
|
||||
ShardMetadata GetShardForKey(const LabelName &label_name, const PrimaryKey &key) const;
|
||||
|
||||
Shard GetShardForKey(const LabelId &label_id, const PrimaryKey &key) const;
|
||||
ShardMetadata GetShardForKey(const LabelId &label_id, const PrimaryKey &key) const;
|
||||
|
||||
PropertyMap AllocatePropertyIds(const std::vector<PropertyName> &new_properties);
|
||||
|
||||
|
@ -69,7 +69,7 @@ ValidFramesConsumer MultiFrame::GetValidFramesConsumer() { return ValidFramesCon
|
||||
|
||||
InvalidFramesPopulator MultiFrame::GetInvalidFramesPopulator() { return InvalidFramesPopulator{*this}; }
|
||||
|
||||
ValidFramesReader::ValidFramesReader(MultiFrame &multiframe) : multiframe_(multiframe) {
|
||||
ValidFramesReader::ValidFramesReader(MultiFrame &multiframe) : multiframe_(&multiframe) {
|
||||
/*
|
||||
From: https://en.cppreference.com/w/cpp/algorithm/find
|
||||
Returns an iterator to the first element in the range [first, last) that satisfies specific criteria:
|
||||
@ -81,38 +81,55 @@ ValidFramesReader::ValidFramesReader(MultiFrame &multiframe) : multiframe_(multi
|
||||
*/
|
||||
auto it = std::find_if(multiframe.frames_.begin(), multiframe.frames_.end(),
|
||||
[](const auto &frame) { return !frame.IsValid(); });
|
||||
after_last_valid_frame_ = multiframe_.frames_.data() + std::distance(multiframe.frames_.begin(), it);
|
||||
after_last_valid_frame_ = multiframe_->frames_.data() + std::distance(multiframe.frames_.begin(), it);
|
||||
}
|
||||
|
||||
ValidFramesReader::Iterator ValidFramesReader::begin() {
|
||||
if (multiframe_->frames_[0].IsValid()) {
|
||||
return Iterator{&multiframe_->frames_[0]};
|
||||
}
|
||||
return end();
|
||||
}
|
||||
|
||||
ValidFramesReader::Iterator ValidFramesReader::begin() { return Iterator{&multiframe_.frames_[0]}; }
|
||||
ValidFramesReader::Iterator ValidFramesReader::end() { return Iterator{after_last_valid_frame_}; }
|
||||
|
||||
ValidFramesModifier::ValidFramesModifier(MultiFrame &multiframe) : multiframe_(multiframe) {}
|
||||
ValidFramesModifier::ValidFramesModifier(MultiFrame &multiframe) : multiframe_(&multiframe) {}
|
||||
|
||||
ValidFramesModifier::Iterator ValidFramesModifier::begin() { return Iterator{&multiframe_.frames_[0], *this}; }
|
||||
ValidFramesModifier::Iterator ValidFramesModifier::end() {
|
||||
return Iterator{multiframe_.frames_.data() + multiframe_.frames_.size(), *this};
|
||||
ValidFramesModifier::Iterator ValidFramesModifier::begin() {
|
||||
if (multiframe_->frames_[0].IsValid()) {
|
||||
return Iterator{&multiframe_->frames_[0], *this};
|
||||
}
|
||||
return end();
|
||||
}
|
||||
|
||||
ValidFramesConsumer::ValidFramesConsumer(MultiFrame &multiframe) : multiframe_(multiframe) {}
|
||||
ValidFramesModifier::Iterator ValidFramesModifier::end() {
|
||||
return Iterator{multiframe_->frames_.data() + multiframe_->frames_.size(), *this};
|
||||
}
|
||||
|
||||
ValidFramesConsumer::ValidFramesConsumer(MultiFrame &multiframe) : multiframe_(&multiframe) {}
|
||||
|
||||
// NOLINTNEXTLINE (bugprone-exception-escape)
|
||||
ValidFramesConsumer::~ValidFramesConsumer() noexcept {
|
||||
// TODO Possible optimisation: only DefragmentValidFrames if one frame has been invalidated? Only if does not
|
||||
// cost too much to store it
|
||||
multiframe_.DefragmentValidFrames();
|
||||
multiframe_->DefragmentValidFrames();
|
||||
}
|
||||
|
||||
ValidFramesConsumer::Iterator ValidFramesConsumer::begin() { return Iterator{&multiframe_.frames_[0], *this}; }
|
||||
ValidFramesConsumer::Iterator ValidFramesConsumer::begin() {
|
||||
if (multiframe_->frames_[0].IsValid()) {
|
||||
return Iterator{&multiframe_->frames_[0], *this};
|
||||
}
|
||||
return end();
|
||||
}
|
||||
|
||||
ValidFramesConsumer::Iterator ValidFramesConsumer::end() {
|
||||
return Iterator{multiframe_.frames_.data() + multiframe_.frames_.size(), *this};
|
||||
return Iterator{multiframe_->frames_.data() + multiframe_->frames_.size(), *this};
|
||||
}
|
||||
|
||||
InvalidFramesPopulator::InvalidFramesPopulator(MultiFrame &multiframe) : multiframe_(multiframe) {}
|
||||
InvalidFramesPopulator::InvalidFramesPopulator(MultiFrame &multiframe) : multiframe_(&multiframe) {}
|
||||
|
||||
InvalidFramesPopulator::Iterator InvalidFramesPopulator::begin() {
|
||||
for (auto &frame : multiframe_.frames_) {
|
||||
for (auto &frame : multiframe_->frames_) {
|
||||
if (!frame.IsValid()) {
|
||||
return Iterator{&frame};
|
||||
}
|
||||
@ -121,7 +138,7 @@ InvalidFramesPopulator::Iterator InvalidFramesPopulator::begin() {
|
||||
}
|
||||
|
||||
InvalidFramesPopulator::Iterator InvalidFramesPopulator::end() {
|
||||
return Iterator{multiframe_.frames_.data() + multiframe_.frames_.size()};
|
||||
return Iterator{multiframe_->frames_.data() + multiframe_->frames_.size()};
|
||||
}
|
||||
|
||||
} // namespace memgraph::query::v2
|
||||
|
@ -137,7 +137,7 @@ class ValidFramesReader {
|
||||
|
||||
private:
|
||||
FrameWithValidity *after_last_valid_frame_;
|
||||
MultiFrame &multiframe_;
|
||||
MultiFrame *multiframe_;
|
||||
};
|
||||
|
||||
class ValidFramesModifier {
|
||||
@ -192,7 +192,7 @@ class ValidFramesModifier {
|
||||
Iterator end();
|
||||
|
||||
private:
|
||||
MultiFrame &multiframe_;
|
||||
MultiFrame *multiframe_;
|
||||
};
|
||||
|
||||
class ValidFramesConsumer {
|
||||
@ -246,7 +246,7 @@ class ValidFramesConsumer {
|
||||
Iterator end();
|
||||
|
||||
private:
|
||||
MultiFrame &multiframe_;
|
||||
MultiFrame *multiframe_;
|
||||
};
|
||||
|
||||
class InvalidFramesPopulator {
|
||||
@ -296,7 +296,7 @@ class InvalidFramesPopulator {
|
||||
Iterator end();
|
||||
|
||||
private:
|
||||
MultiFrame &multiframe_;
|
||||
MultiFrame *multiframe_;
|
||||
};
|
||||
|
||||
} // namespace memgraph::query::v2
|
||||
|
@ -2538,7 +2538,7 @@ class DistributedCreateExpandCursor : public Cursor {
|
||||
|
||||
std::vector<msgs::NewExpand> ExpandCreationInfoToRequests(MultiFrame &multi_frame, ExecutionContext &context) const {
|
||||
std::vector<msgs::NewExpand> edge_requests;
|
||||
auto reader = multi_frame.GetValidFramesConsumer();
|
||||
auto reader = multi_frame.GetValidFramesModifier();
|
||||
|
||||
for (auto &frame : reader) {
|
||||
const auto &edge_info = self_.edge_info_;
|
||||
|
@ -50,7 +50,7 @@ template <typename TStorageClient>
|
||||
class RsmStorageClientManager {
|
||||
public:
|
||||
using CompoundKey = io::rsm::ShardRsmKey;
|
||||
using Shard = coordinator::Shard;
|
||||
using ShardMetadata = coordinator::ShardMetadata;
|
||||
RsmStorageClientManager() = default;
|
||||
RsmStorageClientManager(const RsmStorageClientManager &) = delete;
|
||||
RsmStorageClientManager(RsmStorageClientManager &&) = delete;
|
||||
@ -58,25 +58,25 @@ class RsmStorageClientManager {
|
||||
RsmStorageClientManager &operator=(RsmStorageClientManager &&) = delete;
|
||||
~RsmStorageClientManager() = default;
|
||||
|
||||
void AddClient(Shard key, TStorageClient client) { cli_cache_.emplace(std::move(key), std::move(client)); }
|
||||
void AddClient(ShardMetadata key, TStorageClient client) { cli_cache_.emplace(std::move(key), std::move(client)); }
|
||||
|
||||
bool Exists(const Shard &key) { return cli_cache_.contains(key); }
|
||||
bool Exists(const ShardMetadata &key) { return cli_cache_.contains(key); }
|
||||
|
||||
void PurgeCache() { cli_cache_.clear(); }
|
||||
|
||||
TStorageClient &GetClient(const Shard &key) {
|
||||
TStorageClient &GetClient(const ShardMetadata &key) {
|
||||
auto it = cli_cache_.find(key);
|
||||
MG_ASSERT(it != cli_cache_.end(), "Non-existing shard client");
|
||||
return it->second;
|
||||
}
|
||||
|
||||
private:
|
||||
std::map<Shard, TStorageClient> cli_cache_;
|
||||
std::map<ShardMetadata, TStorageClient> cli_cache_;
|
||||
};
|
||||
|
||||
template <typename TRequest>
|
||||
struct ShardRequestState {
|
||||
memgraph::coordinator::Shard shard;
|
||||
memgraph::coordinator::ShardMetadata shard;
|
||||
TRequest request;
|
||||
};
|
||||
|
||||
@ -125,7 +125,7 @@ class RequestRouter : public RequestRouterInterface {
|
||||
using CoordinatorWriteRequests = coordinator::CoordinatorWriteRequests;
|
||||
using CoordinatorClient = coordinator::CoordinatorClient<TTransport>;
|
||||
using Address = io::Address;
|
||||
using Shard = coordinator::Shard;
|
||||
using ShardMetadata = coordinator::ShardMetadata;
|
||||
using ShardMap = coordinator::ShardMap;
|
||||
using CompoundKey = coordinator::PrimaryKey;
|
||||
using VertexAccessor = query::v2::accessors::VertexAccessor;
|
||||
@ -403,7 +403,7 @@ class RequestRouter : public RequestRouterInterface {
|
||||
private:
|
||||
std::vector<ShardRequestState<msgs::CreateVerticesRequest>> RequestsForCreateVertices(
|
||||
const std::vector<msgs::NewVertex> &new_vertices) {
|
||||
std::map<Shard, msgs::CreateVerticesRequest> per_shard_request_table;
|
||||
std::map<ShardMetadata, msgs::CreateVerticesRequest> per_shard_request_table;
|
||||
|
||||
for (auto &new_vertex : new_vertices) {
|
||||
MG_ASSERT(!new_vertex.label_ids.empty(), "No label_ids provided for new vertex in RequestRouter::CreateVertices");
|
||||
@ -431,9 +431,9 @@ class RequestRouter : public RequestRouterInterface {
|
||||
|
||||
std::vector<ShardRequestState<msgs::CreateExpandRequest>> RequestsForCreateExpand(
|
||||
const std::vector<msgs::NewExpand> &new_expands) {
|
||||
std::map<Shard, msgs::CreateExpandRequest> per_shard_request_table;
|
||||
std::map<ShardMetadata, msgs::CreateExpandRequest> per_shard_request_table;
|
||||
auto ensure_shard_exists_in_table = [&per_shard_request_table,
|
||||
transaction_id = transaction_id_](const Shard &shard) {
|
||||
transaction_id = transaction_id_](const ShardMetadata &shard) {
|
||||
if (!per_shard_request_table.contains(shard)) {
|
||||
msgs::CreateExpandRequest create_expand_request{.transaction_id = transaction_id};
|
||||
per_shard_request_table.insert({shard, std::move(create_expand_request)});
|
||||
@ -484,7 +484,7 @@ class RequestRouter : public RequestRouterInterface {
|
||||
|
||||
for (auto &shards : multi_shards) {
|
||||
for (auto &[key, shard] : shards) {
|
||||
MG_ASSERT(!shard.empty());
|
||||
MG_ASSERT(!shard.peers.empty());
|
||||
|
||||
msgs::ScanVerticesRequest request;
|
||||
request.transaction_id = transaction_id_;
|
||||
@ -503,7 +503,7 @@ class RequestRouter : public RequestRouterInterface {
|
||||
}
|
||||
|
||||
std::vector<ShardRequestState<msgs::ExpandOneRequest>> RequestsForExpandOne(const msgs::ExpandOneRequest &request) {
|
||||
std::map<Shard, msgs::ExpandOneRequest> per_shard_request_table;
|
||||
std::map<ShardMetadata, msgs::ExpandOneRequest> per_shard_request_table;
|
||||
msgs::ExpandOneRequest top_level_rqst_template = request;
|
||||
top_level_rqst_template.transaction_id = transaction_id_;
|
||||
top_level_rqst_template.src_vertices.clear();
|
||||
@ -533,7 +533,7 @@ class RequestRouter : public RequestRouterInterface {
|
||||
|
||||
std::vector<ShardRequestState<msgs::GetPropertiesRequest>> RequestsForGetProperties(
|
||||
msgs::GetPropertiesRequest &&request) {
|
||||
std::map<Shard, msgs::GetPropertiesRequest> per_shard_request_table;
|
||||
std::map<ShardMetadata, msgs::GetPropertiesRequest> per_shard_request_table;
|
||||
auto top_level_rqst_template = request;
|
||||
top_level_rqst_template.transaction_id = transaction_id_;
|
||||
top_level_rqst_template.vertex_ids.clear();
|
||||
@ -571,7 +571,7 @@ class RequestRouter : public RequestRouterInterface {
|
||||
return requests;
|
||||
}
|
||||
|
||||
StorageClient &GetStorageClientForShard(Shard shard) {
|
||||
StorageClient &GetStorageClientForShard(ShardMetadata shard) {
|
||||
if (!storage_cli_manager_.Exists(shard)) {
|
||||
AddStorageClientToManager(shard);
|
||||
}
|
||||
@ -583,12 +583,12 @@ class RequestRouter : public RequestRouterInterface {
|
||||
return GetStorageClientForShard(std::move(shard));
|
||||
}
|
||||
|
||||
void AddStorageClientToManager(Shard target_shard) {
|
||||
MG_ASSERT(!target_shard.empty());
|
||||
auto leader_addr = target_shard.front();
|
||||
void AddStorageClientToManager(ShardMetadata target_shard) {
|
||||
MG_ASSERT(!target_shard.peers.empty());
|
||||
auto leader_addr = target_shard.peers.front();
|
||||
std::vector<Address> addresses;
|
||||
addresses.reserve(target_shard.size());
|
||||
for (auto &address : target_shard) {
|
||||
addresses.reserve(target_shard.peers.size());
|
||||
for (auto &address : target_shard.peers) {
|
||||
addresses.push_back(std::move(address.address));
|
||||
}
|
||||
auto cli = StorageClient(io_, std::move(leader_addr.address), std::move(addresses));
|
||||
|
@ -9,11 +9,13 @@ function(add_benchmark test_cpp)
|
||||
get_filename_component(exec_name ${test_cpp} NAME_WE)
|
||||
set(target_name ${test_prefix}${exec_name})
|
||||
add_executable(${target_name} ${test_cpp} ${ARGN})
|
||||
|
||||
# OUTPUT_NAME sets the real name of a target when it is built and can be
|
||||
# used to help create two targets of the same name even though CMake
|
||||
# requires unique logical target names
|
||||
set_target_properties(${target_name} PROPERTIES OUTPUT_NAME ${exec_name})
|
||||
target_link_libraries(${target_name} benchmark gflags)
|
||||
|
||||
# register test
|
||||
add_test(${target_name} ${exec_name})
|
||||
add_dependencies(memgraph__benchmark ${target_name})
|
||||
@ -37,9 +39,9 @@ target_link_libraries(${test_prefix}profile mg-query)
|
||||
add_benchmark(query/stripped.cpp)
|
||||
target_link_libraries(${test_prefix}stripped mg-query)
|
||||
|
||||
if (MG_ENTERPRISE)
|
||||
add_benchmark(rpc.cpp)
|
||||
target_link_libraries(${test_prefix}rpc mg-rpc)
|
||||
if(MG_ENTERPRISE)
|
||||
add_benchmark(rpc.cpp)
|
||||
target_link_libraries(${test_prefix}rpc mg-rpc)
|
||||
endif()
|
||||
|
||||
add_benchmark(skip_list_random.cpp)
|
||||
@ -65,3 +67,15 @@ target_link_libraries(${test_prefix}storage_v2_property_store mg-storage-v2)
|
||||
|
||||
add_benchmark(future.cpp)
|
||||
target_link_libraries(${test_prefix}future mg-io)
|
||||
|
||||
add_benchmark(data_structures_insert.cpp)
|
||||
target_link_libraries(${test_prefix}data_structures_insert mg-utils mg-storage-v3)
|
||||
|
||||
add_benchmark(data_structures_find.cpp)
|
||||
target_link_libraries(${test_prefix}data_structures_find mg-utils mg-storage-v3)
|
||||
|
||||
add_benchmark(data_structures_contains.cpp)
|
||||
target_link_libraries(${test_prefix}data_structures_contains mg-utils mg-storage-v3)
|
||||
|
||||
add_benchmark(data_structures_remove.cpp)
|
||||
target_link_libraries(${test_prefix}data_structures_remove mg-utils mg-storage-v3)
|
||||
|
58
tests/benchmark/data_structures_common.hpp
Normal file
58
tests/benchmark/data_structures_common.hpp
Normal file
@ -0,0 +1,58 @@
|
||||
// Copyright 2022 Memgraph Ltd.
|
||||
//
|
||||
// Use of this software is governed by the Business Source License
|
||||
// included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source
|
||||
// License, and you may not use this file except in compliance with the Business Source License.
|
||||
//
|
||||
// As of the Change Date specified in that file, in accordance with
|
||||
// the Business Source License, use of this software will be governed
|
||||
// by the Apache License, Version 2.0, included in the file
|
||||
// licenses/APL.txt.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <map>
|
||||
#include <set>
|
||||
#include <vector>
|
||||
|
||||
#include "coordinator/hybrid_logical_clock.hpp"
|
||||
#include "storage/v3/key_store.hpp"
|
||||
#include "storage/v3/lexicographically_ordered_vertex.hpp"
|
||||
#include "storage/v3/mvcc.hpp"
|
||||
#include "storage/v3/transaction.hpp"
|
||||
#include "utils/skip_list.hpp"
|
||||
|
||||
namespace memgraph::benchmark {
|
||||
|
||||
template <typename T>
|
||||
inline void PrepareData(utils::SkipList<T> &skip_list, const int64_t num_elements) {
|
||||
coordinator::Hlc start_timestamp;
|
||||
storage::v3::Transaction transaction{start_timestamp, storage::v3::IsolationLevel::SNAPSHOT_ISOLATION};
|
||||
for (auto i{0}; i < num_elements; ++i) {
|
||||
auto acc = skip_list.access();
|
||||
acc.insert({storage::v3::PrimaryKey{storage::v3::PropertyValue{true}}});
|
||||
}
|
||||
}
|
||||
|
||||
template <typename TKey, typename TValue>
|
||||
inline void PrepareData(std::map<TKey, TValue> &std_map, const int64_t num_elements) {
|
||||
coordinator::Hlc start_timestamp;
|
||||
storage::v3::Transaction transaction{start_timestamp, storage::v3::IsolationLevel::SNAPSHOT_ISOLATION};
|
||||
auto *delta = storage::v3::CreateDeleteObjectDelta(&transaction);
|
||||
for (auto i{0}; i < num_elements; ++i) {
|
||||
std_map.insert({storage::v3::PrimaryKey{storage::v3::PropertyValue{i}},
|
||||
storage::v3::LexicographicallyOrderedVertex{storage::v3::Vertex{
|
||||
delta, std::vector<storage::v3::PropertyValue>{storage::v3::PropertyValue{true}}}}});
|
||||
}
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
inline void PrepareData(std::set<T> &std_set, const int64_t num_elements) {
|
||||
coordinator::Hlc start_timestamp;
|
||||
storage::v3::Transaction transaction{start_timestamp, storage::v3::IsolationLevel::SNAPSHOT_ISOLATION};
|
||||
for (auto i{0}; i < num_elements; ++i) {
|
||||
std_set.insert(std::vector<storage::v3::PropertyValue>{storage::v3::PropertyValue{true}});
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace memgraph::benchmark
|
105
tests/benchmark/data_structures_contains.cpp
Normal file
105
tests/benchmark/data_structures_contains.cpp
Normal file
@ -0,0 +1,105 @@
|
||||
// Copyright 2022 Memgraph Ltd.
|
||||
//
|
||||
// Use of this software is governed by the Business Source License
|
||||
// included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source
|
||||
// License, and you may not use this file except in compliance with the Business Source License.
|
||||
//
|
||||
// As of the Change Date specified in that file, in accordance with
|
||||
// the Business Source License, use of this software will be governed
|
||||
// by the Apache License, Version 2.0, included in the file
|
||||
// licenses/APL.txt.
|
||||
|
||||
#include <atomic>
|
||||
#include <concepts>
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
#include <exception>
|
||||
#include <map>
|
||||
#include <set>
|
||||
#include <stdexcept>
|
||||
#include <type_traits>
|
||||
#include <vector>
|
||||
|
||||
#include <benchmark/benchmark.h>
|
||||
#include <gflags/gflags.h>
|
||||
|
||||
#include "data_structures_common.hpp"
|
||||
#include "storage/v3/key_store.hpp"
|
||||
#include "storage/v3/lexicographically_ordered_vertex.hpp"
|
||||
#include "storage/v3/mvcc.hpp"
|
||||
#include "storage/v3/property_value.hpp"
|
||||
#include "storage/v3/transaction.hpp"
|
||||
#include "storage/v3/vertex.hpp"
|
||||
#include "utils/skip_list.hpp"
|
||||
|
||||
namespace memgraph::benchmark {
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
// Testing Contains Operation
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
static void BM_BenchmarkContainsSkipList(::benchmark::State &state) {
|
||||
utils::SkipList<storage::v3::PrimaryKey> skip_list;
|
||||
PrepareData(skip_list, state.range(0));
|
||||
// So we can also have elements that does don't exist
|
||||
std::mt19937 i_generator(std::random_device{}());
|
||||
std::uniform_int_distribution<int64_t> i_distribution(0, state.range(0) * 2);
|
||||
int64_t found_elems{0};
|
||||
for (auto _ : state) {
|
||||
for (auto i{0}; i < state.range(0); ++i) {
|
||||
int64_t value = i_distribution(i_generator);
|
||||
auto acc = skip_list.access();
|
||||
if (acc.contains(storage::v3::PrimaryKey{{storage::v3::PropertyValue(value)}})) {
|
||||
found_elems++;
|
||||
}
|
||||
}
|
||||
}
|
||||
state.SetItemsProcessed(found_elems);
|
||||
}
|
||||
|
||||
static void BM_BenchmarkContainsStdMap(::benchmark::State &state) {
|
||||
std::map<storage::v3::PrimaryKey, storage::v3::LexicographicallyOrderedVertex> std_map;
|
||||
PrepareData(std_map, state.range(0));
|
||||
|
||||
// So we can also have elements that does don't exist
|
||||
std::mt19937 i_generator(std::random_device{}());
|
||||
std::uniform_int_distribution<int64_t> i_distribution(0, state.range(0) * 2);
|
||||
int64_t found_elems{0};
|
||||
for (auto _ : state) {
|
||||
for (auto i{0}; i < state.range(0); ++i) {
|
||||
int64_t value = i_distribution(i_generator);
|
||||
if (std_map.contains(storage::v3::PrimaryKey{{storage::v3::PropertyValue(value)}})) {
|
||||
found_elems++;
|
||||
}
|
||||
}
|
||||
}
|
||||
state.SetItemsProcessed(found_elems);
|
||||
}
|
||||
|
||||
static void BM_BenchmarkContainsStdSet(::benchmark::State &state) {
|
||||
std::set<storage::v3::PrimaryKey> std_set;
|
||||
PrepareData(std_set, state.range(0));
|
||||
|
||||
// So we can also have elements that does don't exist
|
||||
std::mt19937 i_generator(std::random_device{}());
|
||||
std::uniform_int_distribution<int64_t> i_distribution(0, state.range(0) * 2);
|
||||
int64_t found_elems{0};
|
||||
for (auto _ : state) {
|
||||
for (auto i{0}; i < state.range(0); ++i) {
|
||||
int64_t value = i_distribution(i_generator);
|
||||
if (std_set.contains(storage::v3::PrimaryKey{storage::v3::PropertyValue{value}})) {
|
||||
found_elems++;
|
||||
}
|
||||
}
|
||||
}
|
||||
state.SetItemsProcessed(found_elems);
|
||||
}
|
||||
|
||||
BENCHMARK(BM_BenchmarkContainsSkipList)->RangeMultiplier(10)->Range(1000, 10000000)->Unit(::benchmark::kMillisecond);
|
||||
|
||||
BENCHMARK(BM_BenchmarkContainsStdMap)->RangeMultiplier(10)->Range(1000, 10000000)->Unit(::benchmark::kMillisecond);
|
||||
|
||||
BENCHMARK(BM_BenchmarkContainsStdSet)->RangeMultiplier(10)->Range(1000, 10000000)->Unit(::benchmark::kMillisecond);
|
||||
|
||||
} // namespace memgraph::benchmark
|
||||
|
||||
BENCHMARK_MAIN();
|
104
tests/benchmark/data_structures_find.cpp
Normal file
104
tests/benchmark/data_structures_find.cpp
Normal file
@ -0,0 +1,104 @@
|
||||
// Copyright 2022 Memgraph Ltd.
|
||||
//
|
||||
// Use of this software is governed by the Business Source License
|
||||
// included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source
|
||||
// License, and you may not use this file except in compliance with the Business Source License.
|
||||
//
|
||||
// As of the Change Date specified in that file, in accordance with
|
||||
// the Business Source License, use of this software will be governed
|
||||
// by the Apache License, Version 2.0, included in the file
|
||||
// licenses/APL.txt.
|
||||
|
||||
#include <atomic>
|
||||
#include <concepts>
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
#include <exception>
|
||||
#include <map>
|
||||
#include <set>
|
||||
#include <stdexcept>
|
||||
#include <type_traits>
|
||||
#include <vector>
|
||||
|
||||
#include <benchmark/benchmark.h>
|
||||
#include <gflags/gflags.h>
|
||||
|
||||
#include "data_structures_common.hpp"
|
||||
#include "storage/v3/key_store.hpp"
|
||||
#include "storage/v3/lexicographically_ordered_vertex.hpp"
|
||||
#include "storage/v3/mvcc.hpp"
|
||||
#include "storage/v3/property_value.hpp"
|
||||
#include "storage/v3/transaction.hpp"
|
||||
#include "storage/v3/vertex.hpp"
|
||||
#include "utils/skip_list.hpp"
|
||||
|
||||
namespace memgraph::benchmark {
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
// Testing Find Operation
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
static void BM_BenchmarkFindSkipList(::benchmark::State &state) {
|
||||
utils::SkipList<storage::v3::PrimaryKey> skip_list;
|
||||
PrepareData(skip_list, state.range(0));
|
||||
// So we can also have elements that does don't exist
|
||||
std::mt19937 i_generator(std::random_device{}());
|
||||
std::uniform_int_distribution<int64_t> i_distribution(0, state.range(0) * 2);
|
||||
int64_t found_elems{0};
|
||||
for (auto _ : state) {
|
||||
for (auto i{0}; i < state.range(0); ++i) {
|
||||
int64_t value = i_distribution(i_generator);
|
||||
auto acc = skip_list.access();
|
||||
if (acc.find(storage::v3::PrimaryKey{{storage::v3::PropertyValue(value)}}) != acc.end()) {
|
||||
found_elems++;
|
||||
}
|
||||
}
|
||||
}
|
||||
state.SetItemsProcessed(found_elems);
|
||||
}
|
||||
|
||||
static void BM_BenchmarkFindStdMap(::benchmark::State &state) {
|
||||
std::map<storage::v3::PrimaryKey, storage::v3::LexicographicallyOrderedVertex> std_map;
|
||||
PrepareData(std_map, state.range(0));
|
||||
// So we can also have elements that does don't exist
|
||||
std::mt19937 i_generator(std::random_device{}());
|
||||
std::uniform_int_distribution<int64_t> i_distribution(0, state.range(0) * 2);
|
||||
int64_t found_elems{0};
|
||||
for (auto _ : state) {
|
||||
for (auto i{0}; i < state.range(0); ++i) {
|
||||
int64_t value = i_distribution(i_generator);
|
||||
if (std_map.find(storage::v3::PrimaryKey{{storage::v3::PropertyValue(value)}}) != std_map.end()) {
|
||||
found_elems++;
|
||||
}
|
||||
}
|
||||
}
|
||||
state.SetItemsProcessed(found_elems);
|
||||
}
|
||||
|
||||
static void BM_BenchmarkFindStdSet(::benchmark::State &state) {
|
||||
std::set<storage::v3::PrimaryKey> std_set;
|
||||
PrepareData(std_set, state.range(0));
|
||||
|
||||
// So we can also have elements that does don't exist
|
||||
std::mt19937 i_generator(std::random_device{}());
|
||||
std::uniform_int_distribution<int64_t> i_distribution(0, state.range(0) * 2);
|
||||
int64_t found_elems{0};
|
||||
for (auto _ : state) {
|
||||
for (auto i{0}; i < state.range(0); ++i) {
|
||||
int64_t value = i_distribution(i_generator);
|
||||
if (std_set.find(storage::v3::PrimaryKey{storage::v3::PropertyValue{value}}) != std_set.end()) {
|
||||
found_elems++;
|
||||
}
|
||||
}
|
||||
}
|
||||
state.SetItemsProcessed(found_elems);
|
||||
}
|
||||
|
||||
BENCHMARK(BM_BenchmarkFindSkipList)->RangeMultiplier(10)->Range(1000, 10000000)->Unit(::benchmark::kMillisecond);
|
||||
|
||||
BENCHMARK(BM_BenchmarkFindStdMap)->RangeMultiplier(10)->Range(1000, 10000000)->Unit(::benchmark::kMillisecond);
|
||||
|
||||
BENCHMARK(BM_BenchmarkFindStdSet)->RangeMultiplier(10)->Range(1000, 10000000)->Unit(::benchmark::kMillisecond);
|
||||
|
||||
} // namespace memgraph::benchmark
|
||||
|
||||
BENCHMARK_MAIN();
|
85
tests/benchmark/data_structures_insert.cpp
Normal file
85
tests/benchmark/data_structures_insert.cpp
Normal file
@ -0,0 +1,85 @@
|
||||
// Copyright 2022 Memgraph Ltd.
|
||||
//
|
||||
// Use of this software is governed by the Business Source License
|
||||
// included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source
|
||||
// License, and you may not use this file except in compliance with the Business Source License.
|
||||
//
|
||||
// As of the Change Date specified in that file, in accordance with
|
||||
// the Business Source License, use of this software will be governed
|
||||
// by the Apache License, Version 2.0, included in the file
|
||||
// licenses/APL.txt.
|
||||
|
||||
#include <atomic>
|
||||
#include <concepts>
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
#include <exception>
|
||||
#include <map>
|
||||
#include <set>
|
||||
#include <stdexcept>
|
||||
#include <type_traits>
|
||||
#include <vector>
|
||||
|
||||
#include <benchmark/benchmark.h>
|
||||
#include <gflags/gflags.h>
|
||||
|
||||
#include "storage/v3/key_store.hpp"
|
||||
#include "storage/v3/lexicographically_ordered_vertex.hpp"
|
||||
#include "storage/v3/mvcc.hpp"
|
||||
#include "storage/v3/property_value.hpp"
|
||||
#include "storage/v3/transaction.hpp"
|
||||
#include "storage/v3/vertex.hpp"
|
||||
#include "utils/skip_list.hpp"
|
||||
|
||||
namespace memgraph::benchmark {
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
// Testing Insert Operation
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
static void BM_BenchmarkInsertSkipList(::benchmark::State &state) {
|
||||
utils::SkipList<storage::v3::PrimaryKey> skip_list;
|
||||
coordinator::Hlc start_timestamp;
|
||||
storage::v3::Transaction transaction{start_timestamp, storage::v3::IsolationLevel::SNAPSHOT_ISOLATION};
|
||||
|
||||
for (auto _ : state) {
|
||||
for (auto i{0}; i < state.range(0); ++i) {
|
||||
auto acc = skip_list.access();
|
||||
acc.insert({storage::v3::PrimaryKey{storage::v3::PropertyValue{true}}});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void BM_BenchmarkInsertStdMap(::benchmark::State &state) {
|
||||
std::map<storage::v3::PrimaryKey, storage::v3::LexicographicallyOrderedVertex> std_map;
|
||||
coordinator::Hlc start_timestamp;
|
||||
storage::v3::Transaction transaction{start_timestamp, storage::v3::IsolationLevel::SNAPSHOT_ISOLATION};
|
||||
auto *delta = storage::v3::CreateDeleteObjectDelta(&transaction);
|
||||
|
||||
for (auto _ : state) {
|
||||
for (auto i{0}; i < state.range(0); ++i) {
|
||||
std_map.insert({storage::v3::PrimaryKey{storage::v3::PropertyValue{i}},
|
||||
storage::v3::LexicographicallyOrderedVertex{storage::v3::Vertex{
|
||||
delta, std::vector<storage::v3::PropertyValue>{storage::v3::PropertyValue{true}}}}});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void BM_BenchmarkInsertStdSet(::benchmark::State &state) {
|
||||
std::set<storage::v3::PrimaryKey> std_set;
|
||||
for (auto _ : state) {
|
||||
for (auto i{0}; i < state.range(0); ++i) {
|
||||
std_set.insert(
|
||||
storage::v3::PrimaryKey{std::vector<storage::v3::PropertyValue>{storage::v3::PropertyValue{true}}});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
BENCHMARK(BM_BenchmarkInsertSkipList)->RangeMultiplier(10)->Range(1000, 10000000)->Unit(::benchmark::kMillisecond);
|
||||
|
||||
BENCHMARK(BM_BenchmarkInsertStdMap)->RangeMultiplier(10)->Range(1000, 10000000)->Unit(::benchmark::kMillisecond);
|
||||
|
||||
BENCHMARK(BM_BenchmarkInsertStdSet)->RangeMultiplier(10)->Range(1000, 10000000)->Unit(::benchmark::kMillisecond);
|
||||
|
||||
} // namespace memgraph::benchmark
|
||||
|
||||
BENCHMARK_MAIN();
|
106
tests/benchmark/data_structures_remove.cpp
Normal file
106
tests/benchmark/data_structures_remove.cpp
Normal file
@ -0,0 +1,106 @@
|
||||
// Copyright 2022 Memgraph Ltd.
|
||||
//
|
||||
// Use of this software is governed by the Business Source License
|
||||
// included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source
|
||||
// License, and you may not use this file except in compliance with the Business Source License.
|
||||
//
|
||||
// As of the Change Date specified in that file, in accordance with
|
||||
// the Business Source License, use of this software will be governed
|
||||
// by the Apache License, Version 2.0, included in the file
|
||||
// licenses/APL.txt.
|
||||
|
||||
#include <atomic>
|
||||
#include <concepts>
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
#include <exception>
|
||||
#include <map>
|
||||
#include <set>
|
||||
#include <stdexcept>
|
||||
#include <type_traits>
|
||||
#include <vector>
|
||||
|
||||
#include <benchmark/benchmark.h>
|
||||
#include <gflags/gflags.h>
|
||||
|
||||
#include "data_structures_common.hpp"
|
||||
#include "storage/v3/key_store.hpp"
|
||||
#include "storage/v3/lexicographically_ordered_vertex.hpp"
|
||||
#include "storage/v3/mvcc.hpp"
|
||||
#include "storage/v3/property_value.hpp"
|
||||
#include "storage/v3/transaction.hpp"
|
||||
#include "storage/v3/vertex.hpp"
|
||||
#include "utils/skip_list.hpp"
|
||||
|
||||
namespace memgraph::benchmark {
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
// Testing Remove Operation
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
static void BM_BenchmarkRemoveSkipList(::benchmark::State &state) {
|
||||
utils::SkipList<storage::v3::PrimaryKey> skip_list;
|
||||
PrepareData(skip_list, state.range(0));
|
||||
|
||||
// So we can also have elements that don't exist
|
||||
std::mt19937 i_generator(std::random_device{}());
|
||||
std::uniform_int_distribution<int64_t> i_distribution(0, state.range(0) * 2);
|
||||
int64_t removed_elems{0};
|
||||
for (auto _ : state) {
|
||||
for (auto i{0}; i < state.range(0); ++i) {
|
||||
int64_t value = i_distribution(i_generator);
|
||||
auto acc = skip_list.access();
|
||||
if (acc.remove(storage::v3::PrimaryKey{storage::v3::PropertyValue(value)})) {
|
||||
removed_elems++;
|
||||
}
|
||||
}
|
||||
}
|
||||
state.SetItemsProcessed(removed_elems);
|
||||
}
|
||||
|
||||
static void BM_BenchmarkRemoveStdMap(::benchmark::State &state) {
|
||||
std::map<storage::v3::PrimaryKey, storage::v3::LexicographicallyOrderedVertex> std_map;
|
||||
PrepareData(std_map, state.range(0));
|
||||
|
||||
// So we can also have elements that does don't exist
|
||||
std::mt19937 i_generator(std::random_device{}());
|
||||
std::uniform_int_distribution<int64_t> i_distribution(0, state.range(0) * 2);
|
||||
int64_t removed_elems{0};
|
||||
for (auto _ : state) {
|
||||
for (auto i{0}; i < state.range(0); ++i) {
|
||||
int64_t value = i_distribution(i_generator);
|
||||
if (std_map.erase(storage::v3::PrimaryKey{storage::v3::PropertyValue{value}}) > 0) {
|
||||
removed_elems++;
|
||||
}
|
||||
}
|
||||
}
|
||||
state.SetItemsProcessed(removed_elems);
|
||||
}
|
||||
|
||||
static void BM_BenchmarkRemoveStdSet(::benchmark::State &state) {
|
||||
std::set<storage::v3::PrimaryKey> std_set;
|
||||
PrepareData(std_set, state.range(0));
|
||||
|
||||
// So we can also have elements that does don't exist
|
||||
std::mt19937 i_generator(std::random_device{}());
|
||||
std::uniform_int_distribution<int64_t> i_distribution(0, state.range(0) * 2);
|
||||
int64_t removed_elems{0};
|
||||
for (auto _ : state) {
|
||||
for (auto i{0}; i < state.range(0); ++i) {
|
||||
int64_t value = i_distribution(i_generator);
|
||||
if (std_set.erase(storage::v3::PrimaryKey{storage::v3::PropertyValue{value}}) > 0) {
|
||||
removed_elems++;
|
||||
}
|
||||
}
|
||||
}
|
||||
state.SetItemsProcessed(removed_elems);
|
||||
}
|
||||
|
||||
BENCHMARK(BM_BenchmarkRemoveSkipList)->RangeMultiplier(10)->Range(1000, 10000000)->Unit(::benchmark::kMillisecond);
|
||||
|
||||
BENCHMARK(BM_BenchmarkRemoveStdMap)->RangeMultiplier(10)->Range(1000, 10000000)->Unit(::benchmark::kMillisecond);
|
||||
|
||||
BENCHMARK(BM_BenchmarkRemoveStdSet)->RangeMultiplier(10)->Range(1000, 10000000)->Unit(::benchmark::kMillisecond);
|
||||
|
||||
} // namespace memgraph::benchmark
|
||||
|
||||
BENCHMARK_MAIN();
|
@ -1,4 +1,4 @@
|
||||
// Copyright 2021 Memgraph Ltd.
|
||||
// Copyright 2022 Memgraph Ltd.
|
||||
//
|
||||
// Use of this software is governed by the Business Source License
|
||||
// included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source
|
||||
@ -11,11 +11,14 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <array>
|
||||
#include <atomic>
|
||||
#include <chrono>
|
||||
#include <cstdint>
|
||||
#include <functional>
|
||||
#include <iostream>
|
||||
#include <memory>
|
||||
#include <numeric>
|
||||
#include <thread>
|
||||
#include <vector>
|
||||
|
||||
@ -26,7 +29,7 @@ DEFINE_int32(duration, 10, "Duration of test (in seconds)");
|
||||
|
||||
struct Stats {
|
||||
uint64_t total{0};
|
||||
uint64_t succ[4] = {0, 0, 0, 0};
|
||||
std::array<uint64_t, 4> succ = {0, 0, 0, 0};
|
||||
};
|
||||
|
||||
const int OP_INSERT = 0;
|
||||
@ -81,3 +84,27 @@ inline void RunConcurrentTest(std::function<void(std::atomic<bool> *, Stats *)>
|
||||
std::cout << "Total successful: " << tot << " (" << tot / FLAGS_duration << " calls/s)" << std::endl;
|
||||
std::cout << "Total ops: " << tops << " (" << tops / FLAGS_duration << " calls/s)" << std::endl;
|
||||
}
|
||||
|
||||
inline void RunTest(std::function<void(const std::atomic<bool> &, Stats &)> test_func) {
|
||||
Stats stats;
|
||||
std::atomic<bool> run{true};
|
||||
|
||||
{
|
||||
std::jthread bg_thread(test_func, std::cref(run), std::ref(stats));
|
||||
std::this_thread::sleep_for(std::chrono::seconds(FLAGS_duration));
|
||||
run.store(false, std::memory_order_relaxed);
|
||||
}
|
||||
|
||||
std::cout << " Operations: " << stats.total << std::endl;
|
||||
std::cout << " Successful insert: " << stats.succ[0] << std::endl;
|
||||
std::cout << " Successful contains: " << stats.succ[1] << std::endl;
|
||||
std::cout << " Successful remove: " << stats.succ[2] << std::endl;
|
||||
std::cout << " Successful find: " << stats.succ[3] << std::endl;
|
||||
std::cout << std::endl;
|
||||
|
||||
const auto tot = std::accumulate(stats.succ.begin(), +stats.succ.begin() + 3, 0);
|
||||
const auto tops = stats.total;
|
||||
|
||||
std::cout << "Total successful: " << tot << " (" << tot / FLAGS_duration << " calls/s)" << std::endl;
|
||||
std::cout << "Total ops: " << tops << " (" << tops / FLAGS_duration << " calls/s)" << std::endl;
|
||||
}
|
||||
|
@ -46,8 +46,8 @@ using coordinator::CoordinatorClient;
|
||||
using coordinator::CoordinatorRsm;
|
||||
using coordinator::HlcRequest;
|
||||
using coordinator::HlcResponse;
|
||||
using coordinator::Shard;
|
||||
using coordinator::ShardMap;
|
||||
using coordinator::ShardMetadata;
|
||||
using coordinator::Shards;
|
||||
using coordinator::Status;
|
||||
using io::Address;
|
||||
@ -113,7 +113,7 @@ ShardMap CreateDummyShardmap(coordinator::Address a_io_1, coordinator::Address a
|
||||
AddressAndStatus aas1_2{.address = a_io_2, .status = Status::CONSENSUS_PARTICIPANT};
|
||||
AddressAndStatus aas1_3{.address = a_io_3, .status = Status::CONSENSUS_PARTICIPANT};
|
||||
|
||||
Shard shard1 = {aas1_1, aas1_2, aas1_3};
|
||||
ShardMetadata shard1 = ShardMetadata{.peers = {aas1_1, aas1_2, aas1_3}, .version = 1};
|
||||
|
||||
auto key1 = storage::v3::PropertyValue(0);
|
||||
auto key2 = storage::v3::PropertyValue(0);
|
||||
@ -125,7 +125,7 @@ ShardMap CreateDummyShardmap(coordinator::Address a_io_1, coordinator::Address a
|
||||
AddressAndStatus aas2_2{.address = b_io_2, .status = Status::CONSENSUS_PARTICIPANT};
|
||||
AddressAndStatus aas2_3{.address = b_io_3, .status = Status::CONSENSUS_PARTICIPANT};
|
||||
|
||||
Shard shard2 = {aas2_1, aas2_2, aas2_3};
|
||||
ShardMetadata shard2 = ShardMetadata{.peers = {aas2_1, aas2_2, aas2_3}, .version = 1};
|
||||
|
||||
auto key3 = storage::v3::PropertyValue(12);
|
||||
auto key4 = storage::v3::PropertyValue(13);
|
||||
|
@ -40,8 +40,8 @@ using memgraph::coordinator::CoordinatorRsm;
|
||||
using memgraph::coordinator::HlcRequest;
|
||||
using memgraph::coordinator::HlcResponse;
|
||||
using memgraph::coordinator::PrimaryKey;
|
||||
using memgraph::coordinator::Shard;
|
||||
using memgraph::coordinator::ShardMap;
|
||||
using memgraph::coordinator::ShardMetadata;
|
||||
using memgraph::coordinator::Shards;
|
||||
using memgraph::coordinator::Status;
|
||||
using memgraph::io::Address;
|
||||
@ -109,7 +109,7 @@ ShardMap CreateDummyShardmap(Address a_io_1, Address a_io_2, Address a_io_3, Add
|
||||
AddressAndStatus aas1_2{.address = a_io_2, .status = Status::CONSENSUS_PARTICIPANT};
|
||||
AddressAndStatus aas1_3{.address = a_io_3, .status = Status::CONSENSUS_PARTICIPANT};
|
||||
|
||||
Shard shard1 = {aas1_1, aas1_2, aas1_3};
|
||||
ShardMetadata shard1 = ShardMetadata{.peers = {aas1_1, aas1_2, aas1_3}, .version = 1};
|
||||
|
||||
const auto key1 = PropertyValue(0);
|
||||
const auto key2 = PropertyValue(0);
|
||||
@ -121,7 +121,7 @@ ShardMap CreateDummyShardmap(Address a_io_1, Address a_io_2, Address a_io_3, Add
|
||||
AddressAndStatus aas2_2{.address = b_io_2, .status = Status::CONSENSUS_PARTICIPANT};
|
||||
AddressAndStatus aas2_3{.address = b_io_3, .status = Status::CONSENSUS_PARTICIPANT};
|
||||
|
||||
Shard shard2 = {aas2_1, aas2_2, aas2_3};
|
||||
ShardMetadata shard2 = ShardMetadata{.peers = {aas2_1, aas2_2, aas2_3}, .version = 1};
|
||||
|
||||
auto key3 = PropertyValue(12);
|
||||
auto key4 = PropertyValue(13);
|
||||
@ -131,10 +131,10 @@ ShardMap CreateDummyShardmap(Address a_io_1, Address a_io_2, Address a_io_3, Add
|
||||
return sm;
|
||||
}
|
||||
|
||||
std::optional<ShardClient *> DetermineShardLocation(const Shard &target_shard, const std::vector<Address> &a_addrs,
|
||||
ShardClient &a_client, const std::vector<Address> &b_addrs,
|
||||
ShardClient &b_client) {
|
||||
for (const auto &addr : target_shard) {
|
||||
std::optional<ShardClient *> DetermineShardLocation(const ShardMetadata &target_shard,
|
||||
const std::vector<Address> &a_addrs, ShardClient &a_client,
|
||||
const std::vector<Address> &b_addrs, ShardClient &b_client) {
|
||||
for (const auto &addr : target_shard.peers) {
|
||||
if (addr.address == b_addrs[0]) {
|
||||
return &b_client;
|
||||
}
|
||||
@ -275,7 +275,7 @@ int main() {
|
||||
|
||||
const PrimaryKey compound_key = {cm_key_1, cm_key_2};
|
||||
|
||||
// Look for Shard
|
||||
// Look for ShardMetadata
|
||||
BasicResult<TimedOut, memgraph::coordinator::CoordinatorWriteResponses> read_res =
|
||||
coordinator_client.SendWriteRequest(req);
|
||||
|
||||
|
@ -47,8 +47,8 @@ using coordinator::GetShardMapRequest;
|
||||
using coordinator::GetShardMapResponse;
|
||||
using coordinator::Hlc;
|
||||
using coordinator::HlcResponse;
|
||||
using coordinator::Shard;
|
||||
using coordinator::ShardMap;
|
||||
using coordinator::ShardMetadata;
|
||||
using io::Address;
|
||||
using io::Io;
|
||||
using io::rsm::RsmClient;
|
||||
|
@ -44,8 +44,8 @@ using coordinator::GetShardMapRequest;
|
||||
using coordinator::GetShardMapResponse;
|
||||
using coordinator::Hlc;
|
||||
using coordinator::HlcResponse;
|
||||
using coordinator::Shard;
|
||||
using coordinator::ShardMap;
|
||||
using coordinator::ShardMetadata;
|
||||
using io::Address;
|
||||
using io::Io;
|
||||
using io::local_transport::LocalSystem;
|
||||
|
@ -45,8 +45,8 @@ using memgraph::coordinator::CoordinatorWriteRequests;
|
||||
using memgraph::coordinator::CoordinatorWriteResponses;
|
||||
using memgraph::coordinator::Hlc;
|
||||
using memgraph::coordinator::HlcResponse;
|
||||
using memgraph::coordinator::Shard;
|
||||
using memgraph::coordinator::ShardMap;
|
||||
using memgraph::coordinator::ShardMetadata;
|
||||
using memgraph::io::Io;
|
||||
using memgraph::io::local_transport::LocalSystem;
|
||||
using memgraph::io::local_transport::LocalTransport;
|
||||
|
@ -13,51 +13,70 @@
|
||||
|
||||
#include <gmock/gmock.h>
|
||||
#include <gtest/gtest.h>
|
||||
#include "query/v2/common.hpp"
|
||||
#include "query/v2/context.hpp"
|
||||
#include "query/v2/plan/operator.hpp"
|
||||
#include "query/v2/request_router.hpp"
|
||||
|
||||
namespace memgraph {
|
||||
class MockedRequestRouter : public query::v2::RequestRouterInterface {
|
||||
namespace memgraph::query::v2 {
|
||||
class MockedRequestRouter : public RequestRouterInterface {
|
||||
public:
|
||||
MOCK_METHOD1(ScanVertices, std::vector<VertexAccessor>(std::optional<std::string> label));
|
||||
MOCK_METHOD1(CreateVertices, std::vector<msgs::CreateVerticesResponse>(std::vector<msgs::NewVertex>));
|
||||
MOCK_METHOD1(ExpandOne, std::vector<msgs::ExpandOneResultRow>(msgs::ExpandOneRequest));
|
||||
MOCK_METHOD1(CreateExpand, std::vector<msgs::CreateExpandResponse>(std::vector<msgs::NewExpand>));
|
||||
MOCK_METHOD1(GetProperties, std::vector<msgs::GetPropertiesResultRow>(msgs::GetPropertiesRequest));
|
||||
MOCK_METHOD0(StartTransaction, void());
|
||||
MOCK_METHOD0(Commit, void());
|
||||
MOCK_METHOD(std::vector<VertexAccessor>, ScanVertices, (std::optional<std::string> label));
|
||||
MOCK_METHOD(std::vector<msgs::CreateVerticesResponse>, CreateVertices, (std::vector<msgs::NewVertex>));
|
||||
MOCK_METHOD(std::vector<msgs::ExpandOneResultRow>, ExpandOne, (msgs::ExpandOneRequest));
|
||||
MOCK_METHOD(std::vector<msgs::CreateExpandResponse>, CreateExpand, (std::vector<msgs::NewExpand>));
|
||||
MOCK_METHOD(std::vector<msgs::GetPropertiesResultRow>, GetProperties, (msgs::GetPropertiesRequest));
|
||||
MOCK_METHOD(void, StartTransaction, ());
|
||||
MOCK_METHOD(void, Commit, ());
|
||||
|
||||
MOCK_CONST_METHOD1(NameToEdgeType, storage::v3::EdgeTypeId(const std::string &));
|
||||
MOCK_CONST_METHOD1(NameToProperty, storage::v3::PropertyId(const std::string &));
|
||||
MOCK_CONST_METHOD1(NameToLabel, storage::v3::LabelId(const std::string &));
|
||||
MOCK_CONST_METHOD1(LabelToName, storage::v3::LabelId(const std::string &));
|
||||
MOCK_CONST_METHOD1(PropertyToName, const std::string &(storage::v3::PropertyId));
|
||||
MOCK_CONST_METHOD1(LabelToName, const std::string &(storage::v3::LabelId label));
|
||||
MOCK_CONST_METHOD1(EdgeTypeToName, const std::string &(storage::v3::EdgeTypeId type));
|
||||
MOCK_CONST_METHOD1(MaybeNameToProperty, std::optional<storage::v3::PropertyId>(const std::string &));
|
||||
MOCK_CONST_METHOD1(MaybeNameToEdgeType, std::optional<storage::v3::EdgeTypeId>(const std::string &));
|
||||
MOCK_CONST_METHOD1(MaybeNameToLabel, std::optional<storage::v3::LabelId>(const std::string &));
|
||||
MOCK_CONST_METHOD1(IsPrimaryLabel, bool(storage::v3::LabelId));
|
||||
MOCK_CONST_METHOD2(IsPrimaryKey, bool(storage::v3::LabelId, storage::v3::PropertyId));
|
||||
MOCK_METHOD(storage::v3::EdgeTypeId, NameToEdgeType, (const std::string &), (const));
|
||||
MOCK_METHOD(storage::v3::PropertyId, NameToProperty, (const std::string &), (const));
|
||||
MOCK_METHOD(storage::v3::LabelId, NameToLabel, (const std::string &), (const));
|
||||
MOCK_METHOD(storage::v3::LabelId, LabelToName, (const std::string &), (const));
|
||||
MOCK_METHOD(const std::string &, PropertyToName, (storage::v3::PropertyId), (const));
|
||||
MOCK_METHOD(const std::string &, LabelToName, (storage::v3::LabelId label), (const));
|
||||
MOCK_METHOD(const std::string &, EdgeTypeToName, (storage::v3::EdgeTypeId type), (const));
|
||||
MOCK_METHOD(std::optional<storage::v3::PropertyId>, MaybeNameToProperty, (const std::string &), (const));
|
||||
MOCK_METHOD(std::optional<storage::v3::EdgeTypeId>, MaybeNameToEdgeType, (const std::string &), (const));
|
||||
MOCK_METHOD(std::optional<storage::v3::LabelId>, MaybeNameToLabel, (const std::string &), (const));
|
||||
MOCK_METHOD(bool, IsPrimaryLabel, (storage::v3::LabelId), (const));
|
||||
MOCK_METHOD(bool, IsPrimaryKey, (storage::v3::LabelId, storage::v3::PropertyId), (const));
|
||||
};
|
||||
|
||||
class MockedLogicalOperator : query::v2::plan::LogicalOperator {
|
||||
class MockedLogicalOperator : public plan::LogicalOperator {
|
||||
public:
|
||||
MOCK_CONST_METHOD1(MakeCursor, query::v2::plan::UniqueCursorPtr(utils::MemoryResource *));
|
||||
MOCK_CONST_METHOD1(OutputSymbols, std::vector<expr::Symbol>(const expr::SymbolTable &));
|
||||
MOCK_CONST_METHOD1(ModifiedSymbols, std::vector<expr::Symbol>(const expr::SymbolTable &));
|
||||
MOCK_CONST_METHOD0(HasSingleInput, bool());
|
||||
MOCK_CONST_METHOD0(input, std::shared_ptr<LogicalOperator>());
|
||||
MOCK_METHOD1(set_input, void(std::shared_ptr<LogicalOperator>));
|
||||
MOCK_CONST_METHOD1(Clone, std::unique_ptr<LogicalOperator>(query::v2::AstStorage *storage));
|
||||
MOCK_METHOD(plan::UniqueCursorPtr, MakeCursor, (utils::MemoryResource *), (const));
|
||||
MOCK_METHOD(std::vector<expr::Symbol>, ModifiedSymbols, (const expr::SymbolTable &), (const));
|
||||
MOCK_METHOD(bool, HasSingleInput, (), (const));
|
||||
MOCK_METHOD(std::shared_ptr<LogicalOperator>, input, (), (const));
|
||||
MOCK_METHOD(void, set_input, (std::shared_ptr<LogicalOperator>));
|
||||
MOCK_METHOD(std::unique_ptr<LogicalOperator>, Clone, (AstStorage * storage), (const));
|
||||
MOCK_METHOD(bool, Accept, (plan::HierarchicalLogicalOperatorVisitor & visitor));
|
||||
};
|
||||
|
||||
class MockedCursor : memgraph::query::v2::plan::Cursor {
|
||||
class MockedCursor : public plan::Cursor {
|
||||
public:
|
||||
MOCK_METHOD2(Pull, bool(query::v2::Frame &, expr::ExecutionContext &));
|
||||
MOCK_METHOD2(PullMultiple, void(query::v2::MultiFrame &, expr::ExecutionContext &));
|
||||
MOCK_METHOD0(Reset, void());
|
||||
MOCK_METHOD0(Shutdown, void());
|
||||
MOCK_METHOD(bool, Pull, (Frame &, expr::ExecutionContext &));
|
||||
MOCK_METHOD(void, PullMultiple, (MultiFrame &, expr::ExecutionContext &));
|
||||
MOCK_METHOD(void, Reset, ());
|
||||
MOCK_METHOD(void, Shutdown, ());
|
||||
};
|
||||
|
||||
} // namespace memgraph
|
||||
inline expr::ExecutionContext MakeContext(const expr::AstStorage &storage, const expr::SymbolTable &symbol_table,
|
||||
RequestRouterInterface *router, IdAllocator *id_alloc) {
|
||||
expr::ExecutionContext context;
|
||||
context.symbol_table = symbol_table;
|
||||
context.evaluation_context.properties = NamesToProperties(storage.properties_, router);
|
||||
context.evaluation_context.labels = NamesToLabels(storage.labels_, router);
|
||||
context.edge_ids_alloc = id_alloc;
|
||||
context.request_router = router;
|
||||
return context;
|
||||
}
|
||||
|
||||
inline MockedLogicalOperator &BaseToMock(plan::LogicalOperator *op) {
|
||||
return *static_cast<MockedLogicalOperator *>(op);
|
||||
}
|
||||
|
||||
inline MockedCursor &BaseToMock(plan::Cursor *cursor) { return *static_cast<MockedCursor *>(cursor); }
|
||||
|
||||
} // namespace memgraph::query::v2
|
||||
|
@ -9,6 +9,7 @@
|
||||
// by the Apache License, Version 2.0, included in the file
|
||||
// licenses/APL.txt.
|
||||
|
||||
#include <memory>
|
||||
#include "mock_helpers.hpp"
|
||||
|
||||
#include "query/v2/bindings/frame.hpp"
|
||||
@ -22,24 +23,7 @@
|
||||
#include "utils/logging.hpp"
|
||||
#include "utils/memory.hpp"
|
||||
|
||||
using namespace memgraph::query::v2;
|
||||
using namespace memgraph::query::v2::plan;
|
||||
namespace memgraph {
|
||||
class TestTemplate : public testing::Test {
|
||||
protected:
|
||||
void SetUp() override {}
|
||||
};
|
||||
|
||||
ExecutionContext MakeContext(const AstStorage &storage, const SymbolTable &symbol_table, RequestRouterInterface *router,
|
||||
IdAllocator *id_alloc) {
|
||||
ExecutionContext context;
|
||||
context.symbol_table = symbol_table;
|
||||
context.evaluation_context.properties = NamesToProperties(storage.properties_, router);
|
||||
context.evaluation_context.labels = NamesToLabels(storage.labels_, router);
|
||||
context.edge_ids_alloc = id_alloc;
|
||||
context.request_router = router;
|
||||
return context;
|
||||
}
|
||||
namespace memgraph::query::v2 {
|
||||
|
||||
MultiFrame CreateMultiFrame(const size_t max_pos, const Symbol &src, const Symbol &dst, MockedRequestRouter *router) {
|
||||
static constexpr size_t frame_size = 100;
|
||||
@ -60,14 +44,15 @@ MultiFrame CreateMultiFrame(const size_t max_pos, const Symbol &src, const Symbo
|
||||
return multi_frame;
|
||||
}
|
||||
|
||||
TEST_F(TestTemplate, CreateExpand) {
|
||||
MockedRequestRouter router;
|
||||
TEST(CreateExpandTest, Cursor) {
|
||||
using testing::_;
|
||||
using testing::Return;
|
||||
|
||||
AstStorage ast;
|
||||
SymbolTable symbol_table;
|
||||
|
||||
query::v2::plan::NodeCreationInfo node;
|
||||
query::v2::plan::EdgeCreationInfo edge;
|
||||
plan::NodeCreationInfo node;
|
||||
plan::EdgeCreationInfo edge;
|
||||
edge.edge_type = msgs::EdgeTypeId::FromUint(1);
|
||||
edge.direction = EdgeAtom::Direction::IN;
|
||||
auto id_alloc = IdAllocator(0, 100);
|
||||
@ -75,14 +60,20 @@ TEST_F(TestTemplate, CreateExpand) {
|
||||
const auto &src = symbol_table.CreateSymbol("n", true);
|
||||
node.symbol = symbol_table.CreateSymbol("u", true);
|
||||
|
||||
auto create_expand = query::v2::plan::CreateExpand(node, edge, nullptr, src, true);
|
||||
auto once_cur = plan::MakeUniqueCursorPtr<MockedCursor>(utils::NewDeleteResource());
|
||||
EXPECT_CALL(BaseToMock(once_cur.get()), PullMultiple(_, _)).Times(1);
|
||||
|
||||
std::shared_ptr<plan::LogicalOperator> once_op = std::make_shared<MockedLogicalOperator>();
|
||||
EXPECT_CALL(BaseToMock(once_op.get()), MakeCursor(_)).Times(1).WillOnce(Return(std::move(once_cur)));
|
||||
|
||||
auto create_expand = plan::CreateExpand(node, edge, once_op, src, true);
|
||||
auto cursor = create_expand.MakeCursor(utils::NewDeleteResource());
|
||||
|
||||
EXPECT_CALL(router, CreateExpand(testing::_))
|
||||
.Times(1)
|
||||
.WillOnce(::testing::Return(std::vector<msgs::CreateExpandResponse>{}));
|
||||
MockedRequestRouter router;
|
||||
EXPECT_CALL(router, CreateExpand(_)).Times(1).WillOnce(Return(std::vector<msgs::CreateExpandResponse>{}));
|
||||
auto context = MakeContext(ast, symbol_table, &router, &id_alloc);
|
||||
auto multi_frame = CreateMultiFrame(context.symbol_table.max_position(), src, node.symbol, &router);
|
||||
cursor->PullMultiple(multi_frame, context);
|
||||
}
|
||||
} // namespace memgraph
|
||||
|
||||
} // namespace memgraph::query::v2
|
||||
|
@ -11,7 +11,7 @@
|
||||
|
||||
#include <filesystem>
|
||||
|
||||
#include <gmock/gmock-generated-matchers.h>
|
||||
#include <gmock/gmock-matchers.h>
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
#include "utils/settings.hpp"
|
||||
|
185
tools/plot/benchmark_datastructures.py
Normal file
185
tools/plot/benchmark_datastructures.py
Normal file
@ -0,0 +1,185 @@
|
||||
# Copyright 2022 Memgraph Ltd.
|
||||
#
|
||||
# Use of this software is governed by the Business Source License
|
||||
# included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source
|
||||
# License, and you may not use this file except in compliance with the Business Source License.
|
||||
#
|
||||
# As of the Change Date specified in that file, in accordance with
|
||||
# the Business Source License, use of this software will be governed
|
||||
# by the Apache License, Version 2.0, included in the file
|
||||
# licenses/APL.txt.
|
||||
|
||||
####################################
|
||||
# Benchmark datastructures analyzer
|
||||
####################################
|
||||
# This scripts uses the output from dataset benchmark tests to plot charts
|
||||
# comparing the results of different datastructures on the same operation.
|
||||
#
|
||||
# Note: Naming the tests is very important in order for this script to recognize
|
||||
# which operation is being performed and on which DS, so it should come in this
|
||||
# form: BM_Benchmark<Operation><Datastructure>/<RunArgument>
|
||||
# where run_argument will be added automatically by google benchmark framework
|
||||
|
||||
import argparse
|
||||
import json
|
||||
import sys
|
||||
from dataclasses import dataclass
|
||||
from enum import Enum
|
||||
from pathlib import Path
|
||||
from typing import Any, Dict, List, Optional
|
||||
|
||||
import matplotlib.pyplot as plt
|
||||
|
||||
|
||||
class Operation(Enum):
|
||||
CONTAINS = "contains"
|
||||
FIND = "find"
|
||||
INSERT = "insert"
|
||||
RANDOM = "random"
|
||||
REMOVE = "remove"
|
||||
|
||||
@classmethod
|
||||
def to_list(cls) -> List[str]:
|
||||
return list(map(lambda c: c.value, cls))
|
||||
|
||||
@staticmethod
|
||||
def get(s: str) -> Optional["Operation"]:
|
||||
try:
|
||||
return Operation[s.upper()]
|
||||
except ValueError:
|
||||
return None
|
||||
|
||||
def __str__(self):
|
||||
return str(self.value)
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class BenchmarkRow:
|
||||
name: str
|
||||
datastructure: str
|
||||
operation: Operation
|
||||
real_time: int
|
||||
cpu_time: int
|
||||
iterations: int
|
||||
time_unit: str
|
||||
run_arg: Optional[Any]
|
||||
|
||||
|
||||
class GoogleBenchmarkResult:
|
||||
def __init__(self):
|
||||
self._operation = None
|
||||
self._datastructures: Dict[str, List[BenchmarkRow]] = dict()
|
||||
|
||||
def add_result(self, row: BenchmarkRow) -> None:
|
||||
if self._operation is None:
|
||||
self._operation = row.operation
|
||||
assert self._operation is row.operation
|
||||
if row.datastructure not in self._datastructures:
|
||||
self._datastructures[row.datastructure] = [row]
|
||||
else:
|
||||
self._datastructures[row.datastructure].append(row)
|
||||
|
||||
@property
|
||||
def operation(self) -> Optional[Operation]:
|
||||
return self._operation
|
||||
|
||||
@property
|
||||
def datastructures(self) -> Dict[str, List[BenchmarkRow]]:
|
||||
return self._datastructures
|
||||
|
||||
|
||||
def get_operation(s: str) -> Operation:
|
||||
for op in Operation.to_list():
|
||||
if op.lower() in s.lower():
|
||||
operation_enum = Operation.get(op)
|
||||
if operation_enum is not None:
|
||||
return operation_enum
|
||||
else:
|
||||
print("Operation not found!")
|
||||
sys.exit(1)
|
||||
print("Operation not found!")
|
||||
sys.exit(1)
|
||||
|
||||
|
||||
def get_row_data(line: Dict[str, Any]) -> BenchmarkRow:
|
||||
"""
|
||||
Naming is very important, first must come an Operation name, and then a data
|
||||
structure to test.
|
||||
"""
|
||||
full_name = line["name"].split("BM_Benchmark")[1]
|
||||
name_with_run_arg = full_name.split("/")
|
||||
operation = get_operation(name_with_run_arg[0])
|
||||
datastructure = name_with_run_arg[0].split(operation.value.capitalize())[1]
|
||||
|
||||
run_arg = None
|
||||
if len(name_with_run_arg) > 1:
|
||||
run_arg = name_with_run_arg[1]
|
||||
|
||||
return BenchmarkRow(
|
||||
name_with_run_arg[0],
|
||||
datastructure,
|
||||
operation,
|
||||
line["real_time"],
|
||||
line["cpu_time"],
|
||||
line["iterations"],
|
||||
line["time_unit"],
|
||||
run_arg,
|
||||
)
|
||||
|
||||
|
||||
def get_benchmark_res(args) -> Optional[GoogleBenchmarkResult]:
|
||||
file_path = Path(args.log_file)
|
||||
if not file_path.exists():
|
||||
print("Error file {file_path} not found!")
|
||||
return None
|
||||
with file_path.open("r") as file:
|
||||
data = json.load(file)
|
||||
res = GoogleBenchmarkResult()
|
||||
assert "benchmarks" in data, "There must be a benchmark list inside"
|
||||
for benchmark in data["benchmarks"]:
|
||||
res.add_result(get_row_data(benchmark))
|
||||
return res
|
||||
|
||||
|
||||
def plot_operation(results: GoogleBenchmarkResult, save: bool) -> None:
|
||||
colors = ["red", "green", "blue", "yellow", "purple", "brown"]
|
||||
assert results.operation is not None
|
||||
fig = plt.figure()
|
||||
for ds, benchmarks in results.datastructures.items():
|
||||
if benchmarks:
|
||||
# Print line chart
|
||||
x_axis = [elem.real_time for elem in benchmarks]
|
||||
y_axis = [elem.run_arg for elem in benchmarks]
|
||||
plt.plot(x_axis, y_axis, marker="", color=colors.pop(0), linewidth="2", label=f"{ds}")
|
||||
plt.title(f"Benchmark results for operation {results.operation.value}")
|
||||
plt.xlabel(f"Time [{benchmarks[0].time_unit}]")
|
||||
plt.grid(True)
|
||||
plt.legend()
|
||||
plt.draw()
|
||||
else:
|
||||
print(f"Nothing to do for {ds}...")
|
||||
if save:
|
||||
plt.savefig(f"{results.operation.value}.png")
|
||||
plt.close(fig)
|
||||
else:
|
||||
plt.show()
|
||||
|
||||
|
||||
def parse_args() -> argparse.Namespace:
|
||||
parser = argparse.ArgumentParser(description="Process benchmark results.")
|
||||
parser.add_argument("--log_file", type=str)
|
||||
parser.add_argument("--save", type=bool, default=True)
|
||||
return parser.parse_args()
|
||||
|
||||
|
||||
def main():
|
||||
args = parse_args()
|
||||
res = get_benchmark_res(args)
|
||||
if res is None:
|
||||
print("Failed to get results from log file!")
|
||||
sys.exit(1)
|
||||
plot_operation(res, args.save)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
52
tools/plot/benchmark_datastructures.sh
Executable file
52
tools/plot/benchmark_datastructures.sh
Executable file
@ -0,0 +1,52 @@
|
||||
#!/bin/bash
|
||||
|
||||
set -euox pipefail
|
||||
|
||||
SCRIPT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"
|
||||
WORKSPACE_DIR=${SCRIPT_DIR}/../../
|
||||
CPUS=$(grep -c processor < /proc/cpuinfo)
|
||||
|
||||
# Get all benchmark files
|
||||
BENCHMARK_FILES=$(find ${WORKSPACE_DIR}/tests/benchmark -type f -iname "data_structures_*")
|
||||
|
||||
function test_all() {
|
||||
for BENCH_FILE in ${BENCHMARK_FILES[@]}; do
|
||||
local BASE_NAME=$(basename $BENCH_FILE)
|
||||
local NAME=${BASE_NAME%%.*}
|
||||
echo "Running $NAME"
|
||||
local TEST_FILE=${WORKSPACE_DIR}/build/tests/benchmark/${NAME}
|
||||
if [[ -f "${TEST_FILE}" ]]; then
|
||||
pushd ${WORKSPACE_DIR}/build
|
||||
make -j${CPUS} memgraph__benchmark__${NAME}
|
||||
popd
|
||||
local JSON_OUTPUT=${NAME}_output.json
|
||||
# Run benchmakr test
|
||||
${WORKSPACE_DIR}/build/tests/benchmark/${NAME} --benchmark_format=json --benchmark_out=${JSON_OUTPUT}
|
||||
# Run analyze script for benchmark test
|
||||
python3 ${WORKSPACE_DIR}/tools/plot/benchmark_datastructures.py --log_file=${JSON_OUTPUT}
|
||||
else
|
||||
echo "File ${TEST_FILE} does not exist!"
|
||||
fi
|
||||
done
|
||||
}
|
||||
|
||||
function test_memory() {
|
||||
## We are testing only insert
|
||||
local DATA_STRUCTURES=(SkipList StdMap StdSet BppTree)
|
||||
for DATA_STRUCTURE in ${DATA_STRUCTURES[@]}; do
|
||||
valgrind --tool=massif --massif-out-file=${DATA_STRUCTURE}.massif.out ${WORKSPACE_DIR}/build/tests/benchmark/data_structures_insert --benchmark_filter=BM_BenchmarkInsert${DATA_STRUCTURE}/10000 --benchmark_format=json --benchmark_out=${DATA_STRUCTURE}.json
|
||||
done
|
||||
}
|
||||
|
||||
ARG_1=${1:-"all"}
|
||||
case ${ARG_1} in
|
||||
all)
|
||||
test_all
|
||||
;;
|
||||
memory)
|
||||
test_memory
|
||||
;;
|
||||
*)
|
||||
echo "Select either `all` or `memory` benchmark!"
|
||||
;;
|
||||
esac
|
Loading…
Reference in New Issue
Block a user