Skip to content

Commit 8d4a72e

Browse files
Replace std::vector<bool> with std::vector<char> for faster computations (#25)
While profiling tesseract, I noticed bottlenecks in the `to_node` function and `VectorBoolHash` function class when testing **Surface Code Transversal CX Protocols** for larger values of r, d and p (specifically, **r=11,d=11** and **r=13,d=13** and **p=0.002**). A significant percentage of decoding time was spent in code regions operating with `std::vector<bool>` data structures. Using these data structures to store boolean elements can be inefficient (see https://www.geeksforgeeks.org/problem-with-std-vector-bool-in-cpp/ for more detail). In this PR, I replaced `std::vector<bool>` data structures inside the `TesseractDecoder` class with `std::vector<char>` and retained statements that were assigning true/false literal values to specific elements, as they can be implicitly converted to a char (1 or 0). I also slightly modified the logic for hashing the array of boolean elements in the `VectorBoolHash` function class. I tested and evaluated the performance impact of this optimization on a **single shot/simulation of a quantum circuit for larger search problems of Surface Code Transversal CX Protocols**. I performed 10 runs with a fixed seed for generating a shot/simulation and detectors ordering and computed the average decoding time across those runs. Note that when performing runs of multiple shots, the optimization will be propagated on each shot and the final result will contain performance improvement aggregated (summed up) across each shot. ![Screenshot 2025-05-28 10 49 57 AM](https://github.com/user-attachments/assets/9fe3abf3-ef51-4da6-9398-50958ecc33b7) <b>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;Performance Impact on Surface Code Transversal CX Protocols (r=13, d=13, p=0.0005, p=0.001, Short Beam)</b> ![Screenshot 2025-05-28 10 51 25 AM](https://github.com/user-attachments/assets/a8dbd93b-e43a-4f6b-82a2-a1d2c9b5bc8b) <b>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;Performance Impact on Surface Code Transversal CX Protocols (r=13, d=13, p=0.0005, p=0.001, Long Beam)</b> ![Screenshot 2025-05-28 10 52 53 AM](https://github.com/user-attachments/assets/2121f1b2-9f75-4ef1-8d55-b982e96cc264) <b>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;Performance Impact on Surface Code Transversal CX Protocols (r=13, d=13, p=0.002, Short Beam)</b> ![Screenshot 2025-05-28 10 53 28 AM](https://github.com/user-attachments/assets/35f58749-4c27-4f1e-9c5f-1c944f4ad674) <b>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;Time Reduction in Key Functions for Surface Code Transversal CX Protocols (r=13, d=13, p=0.002, Short Beam)</b> --------- Signed-off-by: Dragana Grbic <[email protected]>
1 parent becf5a3 commit 8d4a72e

File tree

3 files changed

+26
-24
lines changed

3 files changed

+26
-24
lines changed

src/tesseract.cc

Lines changed: 20 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -23,9 +23,9 @@ bool Node::operator>(const Node& other) const {
2323
}
2424

2525
double TesseractDecoder::get_detcost(size_t d,
26-
const std::vector<bool>& blocked_errs,
26+
const std::vector<char>& blocked_errs,
2727
const std::vector<size_t>& det_counts,
28-
const std::vector<bool>& dets) const {
28+
const std::vector<char>& dets) const {
2929
double min_cost = INF;
3030
for (size_t ei : d2e[d]) {
3131
if (!blocked_errs[ei]) {
@@ -85,15 +85,17 @@ void TesseractDecoder::initialize_structures(size_t num_detectors) {
8585
}
8686
}
8787

88-
struct VectorBoolHash {
89-
size_t operator()(const std::vector<bool>& v) const {
90-
std::hash<bool> bool_hash;
91-
size_t seed = 0;
92-
for (bool b : v) {
93-
// Combine hash values of individual booleans.
94-
// A simple way is to use XOR and bit shifting,
95-
// but you can use other combining strategies as well.
96-
seed ^= bool_hash(b) + 0x9e3779b9 + (seed << 6) + (seed >> 2);
88+
struct VectorCharHash {
89+
size_t operator()(const std::vector<char>& v) const {
90+
size_t seed = v.size(); // Still good practice to incorporate vector size
91+
92+
// Iterate over char elements. Accessing 'b_val' is now a direct memory read.
93+
for (char b_val : v) {
94+
// The polynomial rolling hash with 31 (or another prime)
95+
// 'b_val' is already a char (an 8-bit integer).
96+
// static_cast<size_t>(b_val) ensures it's promoted to size_t before arithmetic.
97+
// This cast is efficient (likely a simple register extension/move).
98+
seed = seed * 31 + static_cast<size_t>(b_val);
9799
}
98100
return seed;
99101
}
@@ -151,7 +153,7 @@ bool QNode::operator>(const QNode& other) const {
151153
}
152154

153155
void TesseractDecoder::to_node(const QNode& qnode,
154-
const std::vector<bool>& shot_dets,
156+
const std::vector<char>& shot_dets,
155157
size_t det_order, Node& node) const {
156158
node.cost = qnode.cost;
157159
node.errs = qnode.errs;
@@ -197,20 +199,20 @@ void TesseractDecoder::decode_to_errors(const std::vector<uint64_t>& detections,
197199
size_t det_beam = config.det_beam;
198200
predicted_errors_buffer.clear();
199201
low_confidence_flag = false;
200-
std::vector<bool> dets(num_detectors, false);
202+
std::vector<char> dets(num_detectors, false);
201203
for (size_t d : detections) {
202204
dets[d] = true;
203205
}
204206

205207
std::priority_queue<QNode, std::vector<QNode>, std::greater<QNode>> pq;
206208
std::unordered_map<size_t,
207-
std::unordered_set<std::vector<bool>, VectorBoolHash>>
209+
std::unordered_set<std::vector<char>, VectorCharHash>>
208210
discovered_dets;
209211

210212
size_t min_num_dets;
211213
{
212214
std::vector<size_t> errs;
213-
std::vector<bool> blocked_errs(num_errors, false);
215+
std::vector<char> blocked_errs(num_errors, false);
214216
std::vector<size_t> det_counts(num_errors, 0);
215217

216218
for (size_t d = 0; d < num_detectors; ++d) {
@@ -238,8 +240,8 @@ void TesseractDecoder::decode_to_errors(const std::vector<uint64_t>& detections,
238240
size_t max_num_dets = min_num_dets + det_beam;
239241
Node node;
240242
std::vector<size_t> next_det_counts;
241-
std::vector<bool> next_next_blocked_errs;
242-
std::vector<bool> next_dets;
243+
std::vector<char> next_next_blocked_errs;
244+
std::vector<char> next_dets;
243245
std::vector<size_t> next_errs;
244246
while (!pq.empty()) {
245247
const QNode qnode = pq.top();
@@ -311,7 +313,7 @@ void TesseractDecoder::decode_to_errors(const std::vector<uint64_t>& detections,
311313
}
312314
// We cache as we recompute the det costs
313315
std::vector<double> det_costs(num_detectors, -1);
314-
std::vector<bool> next_blocked_errs = node.blocked_errs;
316+
std::vector<char> next_blocked_errs = node.blocked_errs;
315317
if (config.at_most_two_errors_per_detector) {
316318
for (int ei : d2e[min_det]) {
317319
// Block all errors of this detector -- note this is an approximation

src/tesseract.h

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -41,10 +41,10 @@ struct TesseractConfig {
4141
class Node {
4242
public:
4343
std::vector<size_t> errs;
44-
std::vector<bool> dets;
44+
std::vector<char> dets;
4545
double cost;
4646
size_t num_dets;
47-
std::vector<bool> blocked_errs;
47+
std::vector<char> blocked_errs;
4848

4949
bool operator>(const Node& other) const;
5050
};
@@ -96,10 +96,10 @@ struct TesseractDecoder {
9696
size_t num_errors;
9797

9898
void initialize_structures(size_t num_detectors);
99-
double get_detcost(size_t d, const std::vector<bool>& blocked_errs,
99+
double get_detcost(size_t d, const std::vector<char>& blocked_errs,
100100
const std::vector<size_t>& det_counts,
101-
const std::vector<bool>& dets) const;
102-
void to_node(const QNode& qnode, const std::vector<bool>& shot_dets,
101+
const std::vector<char>& dets) const;
102+
void to_node(const QNode& qnode, const std::vector<char>& shot_dets,
103103
size_t det_order, Node& node) const;
104104
};
105105

src/tesseract_main.cc

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -317,7 +317,7 @@ struct Args {
317317

318318
int main(int argc, char* argv[]) {
319319
std::cout.precision(16);
320-
argparse::ArgumentParser program("simplex");
320+
argparse::ArgumentParser program("tesseract");
321321
Args args;
322322
program.add_argument("--circuit")
323323
.help("Stim circuit file path")

0 commit comments

Comments
 (0)