Skip to content

Commit f805da4

Browse files
authored
Merge pull request #19 from poyrazK/feature/engine-optimizations
feat: high-performance engine optimizations and Prepared Statement API
2 parents 0cedf2a + e9a845e commit f805da4

24 files changed

Lines changed: 1049 additions & 280 deletions

benchmarks/sqlite_comparison_bench.cpp

Lines changed: 23 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -108,15 +108,31 @@ struct SQLiteContext {
108108
static void BM_CloudSQL_Insert(benchmark::State& state) {
109109
CloudSQLContext ctx("./bench_cloudsql_insert_" + std::to_string(state.thread_index()));
110110

111+
// Prepare the statement once outside the hot loop
112+
auto prepared = ctx.executor->prepare("INSERT INTO bench_table VALUES (?, ?, ?);");
113+
if (!prepared) {
114+
state.SkipWithError("Failed to prepare statement");
115+
return;
116+
}
117+
118+
// Pre-allocate params to avoid heap allocations in the loop
119+
std::vector<common::Value> params;
120+
params.reserve(3);
121+
params.push_back(common::Value::make_int64(0));
122+
params.push_back(common::Value::make_float64(3.14));
123+
params.push_back(common::Value::make_text("some_payload_data"));
124+
125+
// Use a single transaction for the whole benchmark to reveal raw engine speed
126+
ctx.executor->execute("BEGIN");
127+
128+
int64_t i = 0;
111129
for (auto _ : state) {
112-
state.PauseTiming();
113-
std::string sql = "INSERT INTO bench_table VALUES (" + std::to_string(state.iterations()) +
114-
", 3.14, 'some_payload_data');";
115-
auto stmt = ParseSQL(sql);
116-
state.ResumeTiming();
117-
118-
ctx.executor->execute(*stmt);
130+
// Update only the changing value
131+
params[0] = common::Value::make_int64(i++);
132+
ctx.executor->execute(*prepared, params);
119133
}
134+
135+
ctx.executor->execute("COMMIT");
120136
state.SetItemsProcessed(state.iterations());
121137
}
122138
BENCHMARK(BM_CloudSQL_Insert);

include/common/arena_allocator.hpp

Lines changed: 119 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,119 @@
1+
/**
2+
* @file arena_allocator.hpp
3+
* @brief High-performance bump allocator for execution-scoped data
4+
*/
5+
6+
#ifndef CLOUDSQL_COMMON_ARENA_ALLOCATOR_HPP
7+
#define CLOUDSQL_COMMON_ARENA_ALLOCATOR_HPP
8+
9+
#include <algorithm>
10+
#include <cstddef>
11+
#include <cstdint>
12+
#include <memory>
13+
#include <memory_resource>
14+
#include <vector>
15+
16+
namespace cloudsql::common {
17+
18+
/**
19+
* @class ArenaAllocator
20+
* @brief Manages memory chunks and provides fast, contiguous allocations.
21+
*
22+
* Implements std::pmr::memory_resource for compatibility with standard
23+
* containers like std::pmr::vector.
24+
*/
25+
class ArenaAllocator : public std::pmr::memory_resource {
26+
public:
27+
static constexpr size_t DEFAULT_CHUNK_SIZE = 65536; // 64KB
28+
29+
explicit ArenaAllocator(size_t chunk_size = DEFAULT_CHUNK_SIZE)
30+
: chunk_size_(chunk_size), current_chunk_idx_(0), current_offset_(0) {}
31+
32+
~ArenaAllocator() override {
33+
for (auto* chunk : chunks_) {
34+
delete[] chunk;
35+
}
36+
}
37+
38+
// Disable copy
39+
ArenaAllocator(const ArenaAllocator&) = delete;
40+
ArenaAllocator& operator=(const ArenaAllocator&) = delete;
41+
42+
/**
43+
* @brief Reset the arena, reclaiming all memory for reuse.
44+
*
45+
* Keeps all allocated chunks but resets pointers so they can be overwritten.
46+
* This is an O(1) or O(N_chunks) operation with zero heap overhead.
47+
*/
48+
void reset() {
49+
current_chunk_idx_ = 0;
50+
current_offset_ = 0;
51+
}
52+
53+
protected:
54+
/**
55+
* @brief Internal allocation logic for PMR
56+
*/
57+
void* do_allocate(size_t bytes, size_t alignment) override {
58+
if (bytes == 0) return nullptr;
59+
60+
// Align the offset
61+
size_t mask = alignment - 1;
62+
63+
// Try current chunk
64+
if (current_chunk_idx_ < chunks_.size()) {
65+
size_t aligned_offset = (current_offset_ + mask) & ~mask;
66+
if (aligned_offset + bytes <= chunk_size_) {
67+
void* result = chunks_[current_chunk_idx_] + aligned_offset;
68+
current_offset_ = aligned_offset + bytes;
69+
return result;
70+
}
71+
72+
// Move to next existing chunk if possible
73+
current_chunk_idx_++;
74+
current_offset_ = 0;
75+
return do_allocate(bytes, alignment);
76+
}
77+
78+
// Need a new chunk
79+
if (bytes > chunk_size_) {
80+
auto* large_chunk = new uint8_t[bytes];
81+
chunks_.push_back(large_chunk);
82+
// We don't make this the "current" chunk for small allocations
83+
// to avoid wasting space. We just return it.
84+
return large_chunk;
85+
}
86+
87+
allocate_new_chunk();
88+
return do_allocate(bytes, alignment);
89+
}
90+
91+
/**
92+
* @brief PMR deallocate is a no-op for bump allocators (we reset the whole arena)
93+
*/
94+
void do_deallocate(void* p, size_t bytes, size_t alignment) override {
95+
// No-op
96+
(void)p;
97+
(void)bytes;
98+
(void)alignment;
99+
}
100+
101+
bool do_is_equal(const std::pmr::memory_resource& other) const noexcept override {
102+
return this == &other;
103+
}
104+
105+
private:
106+
void allocate_new_chunk() {
107+
chunks_.push_back(new uint8_t[chunk_size_]);
108+
// Don't change current_chunk_idx_ here, let the recursive call handle it
109+
}
110+
111+
size_t chunk_size_;
112+
std::vector<uint8_t*> chunks_;
113+
size_t current_chunk_idx_;
114+
size_t current_offset_;
115+
};
116+
117+
} // namespace cloudsql::common
118+
119+
#endif // CLOUDSQL_COMMON_ARENA_ALLOCATOR_HPP

include/executor/operator.hpp

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
#define CLOUDSQL_EXECUTOR_OPERATOR_HPP
88

99
#include <memory>
10+
#include <memory_resource>
1011
#include <optional>
1112
#include <string>
1213
#include <unordered_map>
@@ -52,6 +53,8 @@ class Operator {
5253
std::string error_message_;
5354
Transaction* txn_;
5455
LockManager* lock_manager_;
56+
std::pmr::memory_resource* mr_ = nullptr;
57+
const std::vector<common::Value>* params_ = nullptr;
5558

5659
public:
5760
explicit Operator(OperatorType type, Transaction* txn = nullptr,
@@ -71,6 +74,14 @@ class Operator {
7174
[[nodiscard]] Transaction* get_txn() const { return txn_; }
7275
[[nodiscard]] LockManager* get_lock_manager() const { return lock_manager_; }
7376

77+
virtual void set_memory_resource(std::pmr::memory_resource* mr) { mr_ = mr; }
78+
[[nodiscard]] std::pmr::memory_resource* get_memory_resource() const {
79+
return mr_ ? mr_ : std::pmr::get_default_resource();
80+
}
81+
82+
virtual void set_params(const std::vector<common::Value>* params) { params_ = params; }
83+
[[nodiscard]] const std::vector<common::Value>* get_params() const { return params_; }
84+
7485
virtual bool init() { return true; }
7586
virtual bool open() { return true; }
7687
virtual bool next(Tuple& out_tuple) {
@@ -191,6 +202,9 @@ class FilterOperator : public Operator {
191202
void close() override;
192203
[[nodiscard]] Schema& output_schema() override;
193204
void add_child(std::unique_ptr<Operator> child) override;
205+
206+
void set_memory_resource(std::pmr::memory_resource* mr) override;
207+
void set_params(const std::vector<common::Value>* params) override;
194208
};
195209

196210
/**
@@ -212,6 +226,9 @@ class ProjectOperator : public Operator {
212226
void close() override;
213227
[[nodiscard]] Schema& output_schema() override;
214228
void add_child(std::unique_ptr<Operator> child) override;
229+
230+
void set_memory_resource(std::pmr::memory_resource* mr) override;
231+
void set_params(const std::vector<common::Value>* params) override;
215232
};
216233

217234
/**
@@ -236,6 +253,9 @@ class SortOperator : public Operator {
236253
bool next(Tuple& out_tuple) override;
237254
void close() override;
238255
[[nodiscard]] Schema& output_schema() override;
256+
257+
void set_memory_resource(std::pmr::memory_resource* mr) override;
258+
void set_params(const std::vector<common::Value>* params) override;
239259
};
240260

241261
/**
@@ -270,6 +290,9 @@ class AggregateOperator : public Operator {
270290
bool next(Tuple& out_tuple) override;
271291
void close() override;
272292
[[nodiscard]] Schema& output_schema() override;
293+
294+
void set_memory_resource(std::pmr::memory_resource* mr) override;
295+
void set_params(const std::vector<common::Value>* params) override;
273296
};
274297

275298
/**
@@ -319,6 +342,9 @@ class HashJoinOperator : public Operator {
319342
void close() override;
320343
[[nodiscard]] Schema& output_schema() override;
321344
void add_child(std::unique_ptr<Operator> child) override;
345+
346+
void set_memory_resource(std::pmr::memory_resource* mr) override;
347+
void set_params(const std::vector<common::Value>* params) override;
322348
};
323349

324350
/**
@@ -341,6 +367,9 @@ class LimitOperator : public Operator {
341367
void close() override;
342368
[[nodiscard]] Schema& output_schema() override;
343369
void add_child(std::unique_ptr<Operator> child) override;
370+
371+
void set_memory_resource(std::pmr::memory_resource* mr) override;
372+
void set_params(const std::vector<common::Value>* params) override;
344373
};
345374

346375
} // namespace cloudsql::executor

include/executor/query_executor.hpp

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,11 @@
66
#ifndef CLOUDSQL_EXECUTOR_QUERY_EXECUTOR_HPP
77
#define CLOUDSQL_EXECUTOR_QUERY_EXECUTOR_HPP
88

9+
#include <mutex>
10+
#include <unordered_map>
11+
912
#include "catalog/catalog.hpp"
13+
#include "common/arena_allocator.hpp"
1014
#include "common/cluster_manager.hpp"
1115
#include "distributed/raft_types.hpp"
1216
#include "executor/operator.hpp"
@@ -18,6 +22,20 @@
1822

1923
namespace cloudsql::executor {
2024

25+
/**
26+
* @brief Represents a pre-parsed and pre-planned SQL statement
27+
*/
28+
struct PreparedStatement {
29+
std::shared_ptr<parser::Statement> stmt;
30+
std::string sql;
31+
32+
// Cached execution state for hot-path optimization
33+
const TableInfo* table_meta = nullptr;
34+
std::unique_ptr<Schema> schema;
35+
std::unique_ptr<storage::HeapTable> table;
36+
std::vector<std::unique_ptr<storage::BTreeIndex>> indexes;
37+
};
38+
2139
/**
2240
* @brief State machine for a specific data shard
2341
*/
@@ -62,11 +80,32 @@ class QueryExecutor {
6280
*/
6381
void set_local_only(bool local) { is_local_only_ = local; }
6482

83+
/**
84+
* @brief Prepare a SQL string into a reusable PreparedStatement
85+
*/
86+
std::shared_ptr<PreparedStatement> prepare(const std::string& sql);
87+
6588
/**
6689
* @brief Execute a SQL statement and return results
6790
*/
6891
QueryResult execute(const parser::Statement& stmt);
6992

93+
/**
94+
* @brief Execute a SQL string (includes parsing and cache lookup)
95+
*/
96+
QueryResult execute(const std::string& sql);
97+
98+
/**
99+
* @brief Execute a PreparedStatement with bound parameters
100+
*/
101+
QueryResult execute(const PreparedStatement& prepared,
102+
const std::vector<common::Value>& params);
103+
104+
/**
105+
* @brief Get access to the query-scoped arena
106+
*/
107+
common::ArenaAllocator& arena() { return arena_; }
108+
70109
private:
71110
Catalog& catalog_;
72111
storage::BufferPoolManager& bpm_;
@@ -78,6 +117,16 @@ class QueryExecutor {
78117
transaction::Transaction* current_txn_ = nullptr;
79118
bool is_local_only_ = false;
80119

120+
// Bound parameters for the current execution
121+
const std::vector<common::Value>* current_params_ = nullptr;
122+
123+
// Performance structures
124+
common::ArenaAllocator arena_;
125+
126+
// Global statement cache (thread-safe)
127+
static std::unordered_map<std::string, std::shared_ptr<parser::Statement>> statement_cache_;
128+
static std::mutex cache_mutex_;
129+
81130
QueryResult execute_select(const parser::SelectStatement& stmt, transaction::Transaction* txn);
82131
QueryResult execute_create_table(const parser::CreateTableStatement& stmt);
83132
QueryResult execute_create_index(const parser::CreateIndexStatement& stmt);

include/executor/types.hpp

Lines changed: 30 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,9 @@
1111
#define CLOUDSQL_EXECUTOR_TYPES_HPP
1212

1313
#include <cstdint>
14+
#include <initializer_list>
1415
#include <memory>
16+
#include <memory_resource>
1517
#include <stdexcept>
1618
#include <string>
1719
#include <vector>
@@ -120,14 +122,38 @@ class Schema {
120122

121123
/**
122124
* @brief A single data row used in the row-oriented (Volcano) execution model.
125+
*
126+
* Uses std::pmr::vector to support custom allocators (e.g. ArenaAllocator).
123127
*/
124128
class Tuple {
125129
private:
126-
std::vector<common::Value> values_;
130+
std::pmr::vector<common::Value> values_;
127131

128132
public:
129133
Tuple() = default;
130-
explicit Tuple(std::vector<common::Value> values) : values_(std::move(values)) {}
134+
135+
// Explicit PMR vector constructor
136+
explicit Tuple(std::pmr::vector<common::Value> values) : values_(std::move(values)) {}
137+
138+
// Initializer list constructor
139+
Tuple(std::initializer_list<common::Value> list) : values_(list) {}
140+
141+
// Support allocation from a custom memory resource
142+
explicit Tuple(std::pmr::memory_resource* mr)
143+
: values_(mr ? mr : std::pmr::get_default_resource()) {}
144+
145+
// Support construction from standard vector or PMR vector with specific resource
146+
template <typename VectorType,
147+
typename = std::enable_if_t<!std::is_same_v<std::decay_t<VectorType>, Tuple>>,
148+
typename std::enable_if_t<
149+
!std::is_same_v<std::decay_t<VectorType>, std::pmr::memory_resource*>>* = nullptr>
150+
Tuple(const VectorType& values, std::pmr::memory_resource* mr = nullptr)
151+
: values_(values.begin(), values.end(), mr ? mr : std::pmr::get_default_resource()) {}
152+
153+
template <typename VectorType,
154+
typename = std::enable_if_t<!std::is_same_v<std::decay_t<VectorType>, Tuple>>>
155+
explicit Tuple(VectorType&& values)
156+
: values_(std::make_move_iterator(values.begin()), std::make_move_iterator(values.end())) {}
131157

132158
Tuple(const Tuple& other) = default;
133159
Tuple(Tuple&& other) noexcept = default;
@@ -159,8 +185,8 @@ class Tuple {
159185
[[nodiscard]] size_t size() const { return values_.size(); }
160186
[[nodiscard]] bool empty() const { return values_.empty(); }
161187

162-
[[nodiscard]] const std::vector<common::Value>& values() const { return values_; }
163-
[[nodiscard]] std::vector<common::Value>& values() { return values_; }
188+
[[nodiscard]] const std::pmr::vector<common::Value>& values() const { return values_; }
189+
[[nodiscard]] std::pmr::vector<common::Value>& values() { return values_; }
164190

165191
[[nodiscard]] std::string to_string() const;
166192
};

0 commit comments

Comments
 (0)