FastString
FastString copied to clipboard
Fast, in stack, fixed size string implementation with constexpr noexcept constructors and accessors. fixed_size_string improves memory locality and latency of strings by avoiding heap allocations.
FastString
Fast, in stack, fixed size string implementation with constexpr noexcept constructors and accessors. FastString improves memory locality and latency of strings by avoiding heap allocations.
Abstract
std::string allocates heap memory for strings not qualified for SSO (small string optimization) and reduces memory locality. FastString is a thin wrapper around a plain char array with constexpr noexcept constructors and accessors for fast, in stack string manipulation. FastString is a fast and efficient alternative for std::string or plain char arrays in low latency applications and can be easily used with std::string_view for further operations.
Requirements
- C++20 compatible compiler (constexpr std::copy is required for constexpr constructors)
- Works with
- Microsoft (R) C/C++ Optimizing Compiler Version 19.27.29111 or above
- x86-64 gcc-trunk
Build
mkdir build && cd build && cmake .. && make
Performance
- Compiler version:MSVC 19.28.29334.0
- Using Google Benchmark (Tests are included in repo)
Run on (8 X 1498 MHz CPU s)
CPU Caches:
L1 Data 48 KiB (x4)
L1 Instruction 32 KiB (x4)
L2 Unified 512 KiB (x4)
L3 Unified 8192 KiB (x1)
--------------------------------------------------------------------------------------
Benchmark Time CPU Iterations
--------------------------------------------------------------------------------------
BM_std_string_from_const_char 498 ns 148 ns 5600000
BM_fixed_size_str_from_const_char 46.9 ns 16.3 ns 34461538
BM_std_string_from_constexpr_const_char 500 ns 153 ns 4480000
BM_fixed_size_str_from_constexpr_const_char 47.8 ns 16.7 ns 37333333
BM_std_string_from_buffer 505 ns 158 ns 5146257
BM_fixed_size_str_from_buffer 49.6 ns 14.2 ns 37333333
BM_std_string_from_constexpr_buffer 503 ns 188 ns 4072727
BM_fixed_size_str_from_constexpr_buffer 50.1 ns 12.6 ns 44800000
BM_std_string_from_const_char_large_str 1048 ns 384 ns 2240000
BM_fixed_size_str_from_const_char_large_str 153 ns 50.0 ns 10000000
BM_std_string_append 190 ns 62.5 ns 10000000
BM_fixed_size_str_append 164 ns 56.2 ns 14451613
BM_std_string_append_large 50.0 ns 16.4 ns 64000000
BM_fixed_size_str_append_large 61.5 ns 23.4 ns 37333333
- Compiler version: x86-64 gcc-trunk
- Test scenario - Function creates a string from a given char pointer and return length of the created string. Length is returned and assigned to a volatile variable to avoid compiler optimizing out the construction of the variable.
- Test code
- Using std::string
auto f(const char* str) { std::string a{str}; return a.length(); } void g(const char* str) { volatile auto x = f(str); } - Using FastString. Note use of constexpr and noexcept
constexpr auto f(const char* str) noexcept { using string64 = fss::fixed_size_str<63>; string64 a{str}; return a.length(); } void g(const char* str) { volatile auto x = f(str); }
- Using std::string
- Comparison of compiler output between std::string and FastString using https://godbolt.org/

Usage
Refer to below example code
/* define types */
using string8 = fss::fixed_size_str<7>;
using string64 = fss::fixed_size_str<63>;
/* default construction */
constexpr string8 a{};
constexpr auto a_length = a.length(); // a_length is 0
constexpr auto a_empty = a.empty(); // a_empty is true
constexpr auto a_max_size = a.max_size(); // a_max_size is 7
/* copy construction */
constexpr auto a_copy_c = a;
auto a_copy = a;
/* move construction */
auto a_move = std::move(a_copy);
/* copy assignment */
constexpr string8 b{ "1234", 4 }; // b is "1234"
string8 c{ "lmnopqrstuvxyz" }; // c is "lmnopqr"
string8 d{ "56789" }; // d is "789". rest is truncated.
c = b; // now c is "1234"
/* move assingment */
c = std::move(d); // c is "56789"
/* using with string view */
constexpr string8 e{ "abcdefghij", 10 }; // truncated. e is "abcdefg";
constexpr auto e_sub_str = e.str().substr(0, 2); // e_sub_str is "ab"
constexpr auto e_length = e.length(); // e_length is 7
/* comparison */
constexpr string8 f{ "abcd" };
constexpr string8 g{ "abcd" };
constexpr string8 h{ "abcf" };
constexpr auto i = (f == g); // i is true
constexpr auto j = (g == h); // j is false
/* append */
string8 k{ "abc" }; // k is "abc"
k.append("d"); // k is "abcd"
k.append("efghi", 5); // k is "abcdefg". rest is truncated
/* clear */
k.clear(); // k is empty() ""
auto k_empty = k.empty(); // k_empty is true
/* reset */
k.reset("1234"); // k is "1234";
auto k_length = k.length(); // k_length is 4
k.reset("xyz", 3); // k is "xyz"
/* remove_suffix */
/* there is no boundary check. similar to string_view */
string8 l{ "1234567" };
l.remove_suffix(3); // l is "1234"
/* remove_prefix */
/* there is no boundary check. similar to string_view */
l.remove_prefix(2); // l is "34"
/* stream operator */
std::cout << l << std::endl;
/* using for member variables */
struct test_struct
{
std::uint32_t a_{};
std::uint64_t b_{};
string8 c_{ "abcd" }; // uses only 8 + 4 bytes in stack
constexpr auto get_c() const { return c_.str(); }
constexpr void set_c(const char* str) { c_.reset(str); }
};
auto test_struct_size = sizeof(test_struct);
constexpr test_struct t;
constexpr auto t_a = t.get_c();
/* swap */
l.swap(k); // l is "xyz" and k is "34"
swap(l, k); // l is "34" and k is "xyz"