Problem
Two hot signing paths build hex strings via repeated `+=`:
- `src/signer.cc:85-94` — `GetSignature`, called on every signed request
- `src/utils.cc:266-271` — `Sha256Hash`, called for body hashing on every PUT/POST
```cpp
std::string signature;
char buf[3];
for (std::size_t i = 0; i < hash.size(); ++i) {
snprintf(buf, 3, "%02x", (unsigned char)hash[i]);
signature += buf; // O(n²) — each += may reallocate
}
```
For 32-byte SHA256 digests that's 32 small reallocations per call. Multiply by every signed request.
Suggested approach
```cpp
constexpr char kHex[] = "0123456789abcdef";
std::string out(hash.size() * 2, '\0');
for (size_t i = 0; i < hash.size(); ++i) {
const auto b = static_cast(hash[i]);
out[2i] = kHex[b >> 4];
out[2i + 1] = kHex[b & 0x0f];
}
```
Or `std::to_chars` per byte if we want stricter no-allocation guarantees. Either way: a single allocation, zero `snprintf`, zero realloc.
Impact
Small per call, large in aggregate — every signed S3 op pays this cost.
Roadmap
Tier 2 item from the C++ modernization audit. Trivial to land independently.
Problem
Two hot signing paths build hex strings via repeated `+=`:
```cpp
std::string signature;
char buf[3];
for (std::size_t i = 0; i < hash.size(); ++i) {
snprintf(buf, 3, "%02x", (unsigned char)hash[i]);
signature += buf; // O(n²) — each += may reallocate
}
```
For 32-byte SHA256 digests that's 32 small reallocations per call. Multiply by every signed request.
Suggested approach
```cpp
constexpr char kHex[] = "0123456789abcdef";
std::string out(hash.size() * 2, '\0');
for (size_t i = 0; i < hash.size(); ++i) {
const auto b = static_cast(hash[i]);
out[2i] = kHex[b >> 4];
out[2i + 1] = kHex[b & 0x0f];
}
```
Or `std::to_chars` per byte if we want stricter no-allocation guarantees. Either way: a single allocation, zero `snprintf`, zero realloc.
Impact
Small per call, large in aggregate — every signed S3 op pays this cost.
Roadmap
Tier 2 item from the C++ modernization audit. Trivial to land independently.