Skip to content

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.

License

Notifications You must be signed in to change notification settings

m3janitha/FastString

Repository files navigation

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);
      }
  • Comparison of compiler output between std::string and FastString using https://godbolt.org/ image

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"

About

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.

Topics

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published