# Fast and Small C++ - When Efficiency Matters - [Fast and Small C++](https://www.youtube.com/watch?v=rNl591__9zY) - Author: Andreas Fertig (C++ Insight Author) --- ### Topic * std::unique_ptr * Small String Optimization(SSO) * Constexpr * std::initializer_list --- ### std::unique_ptr zero cost abstract - like a raw pointer ```cpp sizeof(std::unique_ptr<int>) == 8 ``` --- ### std::unique_ptr If you set a deleter to the unique_ptr, the size is two times. ```cpp auto f = std::unique_ptr<FILE, decltype(&fclose)>{fopen(SomeFile.txt, "r"), &fclose}; // sizeof(f) == 16 ``` --- ### std::unique_ptr - Compressed Pair [no_unique_address](https://en.cppreference.com/w/cpp/language/attributes/no_unique_address): Allows this data member to be overlapped. ```cpp template<typename T> class default_deleter { void operator()(T* ptr) { delete ptr; } }; template<typename T, typename U> struct CompressedPair { [[no_unique_address]] T first; [[no_unique_address]] U second; }; template<typename T, typename Deleter = default_deleter<T>> class unique_ptr { CompressedPair<Del, T*> mPair; }; ``` --- ### std::unique_ptr Learn the default_deleter ```cpp // auto f = std::unique_ptr<FILE, decltype(&fclose)>{fopen(SomeFile.txt, "r"), &fclose}; template<typename T, auto DeleteFn> usinhg unique_ptr_deleter = std::unique_ptr<T, decltype([](T* obj){DeleteFn(obj);})>; auto f = unique_ptr_deleter<FILE, fclose> { fopen("SomeFile.txt", "r") }; // sizeof(f) = 8 static_assert(sizeof(f) == sizeof(void*)); ``` --- ### std::unique_ptr - gcc ``` template<typename T, typename Del> class unique_ptr { private: tuple<pointer, _Dp> _M_t; }; ``` --- ### Small String Optimization(SSO) The purpose of SSO is to avoid allocating in heap when the string is small. ``` struct String { size_t mSize{}; size_t mCapacity{}; char* mData{}; char MSSOBuffer[16]{}; bool mIsSSO{true}; }; static_assert(sizeof(String) == 48); ``` --- ### Small String Optimization(SSO) ![image](https://hackmd.io/_uploads/rJ_Px21tyl.png) --- ### Small String Optimization(SSO) If you want to make `sizeof(String) == 24`, how? * GCC (libstdc++) * MSVC * Clang (libc++) * folly (facebook library) --- #### Small String Optimization(SSO) - GCC ```cpp struct String { char* mPtr; size_t mSize{}; union { size_t mCapacity; char mBuf[8]; } constexpr String(): mPtr{mBuf}, mBuf{} {} constexpr String(const char* data, size_t len): mPtr{fits_into_sso(len)? mBuf : new char[len]}, mSize{len}, mBuf{} { if (is_long()) {mCapacity = len;} // copy data to mPtr } constexpr static size_t is_long() const {return mPtr != mBuf;} } ``` --- #### Small String Optimization(SSO) - MSVC ```cpp struct String { union { char* mPtr; char mBuf[8]; }; size_t mSize{}; size_t mCapacity{}; constexpr static size_t sso_cap() {return sizeof(mBuf) - 1;} constexpr bool is_long() const {return mCapacity > sso_cap();} constexpr String() : mBuf{}, mCapacity{sso_cap()} {} constexpr String(const char* _data, size_t len): mBuf{}, mSize{len}, mCapacity{fits_into_sso(len) ? sso_cap() : len} { if (is_long()) {mPtr = new char[len];} // copy _data to mPtr } }; ``` --- #### Small String Optimization(SSO) - Clang * Bigger sso size, 16 bytes. ```cpp struct String { static constexpr unsigned BIT_FOR_CAP{sizeof(size_t) * 8 - 1}; struct normal { size_t large : 1; size_t capacity : BIT_FOR_CAP; size_t size; char* data; }; struct sso { uint8_t large : 1; uint8_t size : (sizeof(uint8_t) * 8 - 1); uint8_t padding[sizeof(size_t) - sizeof(uint8_t)]; char data[sizeof(normal) - sizeof(size_t)]; } union { sso small; normal large; } packed; constexpr String(): packed{} {} constexpr static size_t sso_cap() { return sizeof(sso::data) - 1; } constexpr bool is_long() const { return packed.small.large; } constexpr String(const char* _data, size_t len): packed{} { if (fits_into_sso(len)) { packed.small.size = len; } else { packed.large.large = true; packed.large.size = len; packed.large.data = new char[len]; packed.large.capacity = len; } // copy _data to data() } constexpr size_t size() const {return is_long() ? packed.large.size : packed.small.size;} constexpr const char* data() const {return is_long() ? packed.large.data : packed.small.data;} constexpr const char* capacity() const {return is_long() ? packed.large.capacity : sso_cap();} } ``` --- #### Small String Optimization(SSO) - Clang ![image](https://hackmd.io/_uploads/BkJoT21Kyg.png) --- #### Small String Optimization(SSO) - [Folly](https://github.com/facebook/folly/blob/main/folly/FBString.h) **23** bytes in sso mode. ```cpp struct String { struct normal { char* data; size_t size; size_t capacity; } struct sso { char data[sizeof(normal)]; } union { sso small; normal large; } packed; }; ``` --- #### Small String Optimization(SSO) - [Folly](https://github.com/facebook/folly/blob/main/folly/FBString.h) * get_mode_byte() to return last byte. ```cpp struct String { struct normal { char* data; size_t size; size_t capacity; } struct sso { char data[sizeof(normal)]; } union { sso small; normal large; } packed; constexpr static size_t sso_cap() { sizeof(sso) - 1; } constexpr char& get_mode_byte() { return packed.small.data[sizeof(normal)] - 1; } constexpr char get_mode_byte() const { return packed.small.data[sizeof(normal)] - 1; } constexpr bool is_long() const {return get_mode_byte() & 0x40;} constexpr String(const char* _data, size_t len): packed{} { if (fits_into_sso(len)) { // the bytes remaining! get_mode_byte() = sso_cap() - len; } else { get_mode_byte() = 0x40; packed.large.size() = len; } // copy the _data to String } constexpr size_t size() const { return is_long() ? packed.large.size : sso_cap() - get_mode_byte(); } }; ``` --- ### Small String Optimization(SSO) - GCC * sizeof(std::string) == 32 ```cpp struct _Alloc_hider : allocator_type { pointer _M_p; // The actual data. }; // 8 byte _Alloc_hider _M_dataplus; // 8 byte size_type _M_string_length; enum { _S_local_capacity = 15 / sizeof(_CharT) }; // 16 bytes union { _CharT _M_local_buf[_S_local_capacity + 1]; size_type _M_allocated_capacity; }; ``` --- #### Small String Optimization(SSO) - [Folly](https://github.com/facebook/folly/blob/main/folly/FBString.h) * small < 23 bytes * medium < 254 bytes * Large -> Copy On Write ```cpp enum class Category : category_type { isSmall = 0, isMedium = kIsLittleEndian ? 0x80 : 0x2, isLarge = kIsLittleEndian ? 0x40 : 0x1, }; Category category() const { // works for both big-endian and little-endian return static_cast<Category>(bytes_[lastChar] & categoryExtractMask); } struct MediumLarge { Char* data_; size_t size_; size_t capacity_; size_t capacity() const { return kIsLittleEndian ? capacity_ & capacityExtractMask : capacity_ >> 2; } void setCapacity(size_t cap, Category cat) { capacity_ = kIsLittleEndian ? cap | (static_cast<size_t>(cat) << kCategoryShift) : (cap << 2) | static_cast<size_t>(cat); } }; union { uint8_t bytes_[sizeof(MediumLarge)]; // For accessing the last byte. Char small_[sizeof(MediumLarge) / sizeof(Char)]; MediumLarge ml_; }; template <class Char> FOLLY_NOINLINE void fbstring_core<Char>::copyLarge(const fbstring_core& rhs) { // Large strings are just refcounted ml_ = rhs.ml_; RefCounted::incrementRefs(ml_.data_); assert(category() == Category::isLarge && size() == rhs.size()); } ``` --- ### The Power of constexpr * why not constexpr? ![image](https://hackmd.io/_uploads/rJ34t6xK1g.png) --- ### The Power of constexpr * why not constexpr? * [GodBolt](https://godbolt.org/z/54hhz5Y93) ![image](https://hackmd.io/_uploads/r1dGTalF1x.png) --- ### std::initializer_list * Define a const int array first. ![image](https://hackmd.io/_uploads/Hylp0peY1x.png) --- ### std::initializer_list [GodBolt](https://godbolt.org/z/ereE3TEvP) * Question? * Initialize the list every time, even if we don't modify it. * Compiler assume that Fun may be called recursively. ![image](https://hackmd.io/_uploads/HJ_bl0lYyx.png) --- ### std::initializer_list [GodBolt](https://godbolt.org/z/8KfebbTaW) * Add `static` ![image](https://hackmd.io/_uploads/Sy_8WClFJl.png) --- ### std::initializer_list [GodBolt](https://godbolt.org/z/rf4q8Tq6c) * Define a static const int array first after gcc 14.1 ![image](https://hackmd.io/_uploads/HJqemRgKkl.png) --- ### Conclusion * std::unique_ptr * Use `Empty Object` as the deleter * Small String Optimization * GCC union the capacity * MSVC union the data * Clang union the size and data, and give a large bit. * Folly use get_mode_byte and Copy-On-Write. --- ### Conclusion * constexpr * constexpr reduce the code size and avoid redundant operation * std::initializer_list * use `static const` in function to avoid redundant initialization. ---
{"title":"Fast and Small C++ ","slideOptions":"{\"theme\":\"white\"}","description":"When Efficiency Matters","contributors":"[{\"id\":\"09379b25-db04-47a4-8912-78e722b7a548\",\"add\":12311,\"del\":2620}]"}
    241 views