arena.cc 32 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912
  1. // Protocol Buffers - Google's data interchange format
  2. // Copyright 2008 Google Inc. All rights reserved.
  3. // https://developers.google.com/protocol-buffers/
  4. //
  5. // Redistribution and use in source and binary forms, with or without
  6. // modification, are permitted provided that the following conditions are
  7. // met:
  8. //
  9. // * Redistributions of source code must retain the above copyright
  10. // notice, this list of conditions and the following disclaimer.
  11. // * Redistributions in binary form must reproduce the above
  12. // copyright notice, this list of conditions and the following disclaimer
  13. // in the documentation and/or other materials provided with the
  14. // distribution.
  15. // * Neither the name of Google Inc. nor the names of its
  16. // contributors may be used to endorse or promote products derived from
  17. // this software without specific prior written permission.
  18. //
  19. // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
  20. // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
  21. // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
  22. // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
  23. // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
  24. // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
  25. // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
  26. // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
  27. // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
  28. // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
  29. // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  30. #include "google/protobuf/arena.h"
  31. #include <algorithm>
  32. #include <atomic>
  33. #include <cstddef>
  34. #include <cstdint>
  35. #include <limits>
  36. #include <string>
  37. #include <typeinfo>
  38. #include "absl/base/attributes.h"
  39. #include "absl/synchronization/mutex.h"
  40. #include "google/protobuf/arena_allocation_policy.h"
  41. #include "google/protobuf/arenaz_sampler.h"
  42. #include "google/protobuf/port.h"
  43. #include "google/protobuf/serial_arena.h"
  44. #include "google/protobuf/thread_safe_arena.h"
  45. #ifdef ADDRESS_SANITIZER
  46. #include <sanitizer/asan_interface.h>
  47. #endif // ADDRESS_SANITIZER
  48. // Must be included last.
  49. #include "google/protobuf/port_def.inc"
  50. namespace google {
  51. namespace protobuf {
  52. namespace internal {
  53. namespace {
  54. #if defined(__GNUC__) && __GNUC__ >= 5
  55. // kSentryArenaBlock is used for arenas which can be referenced pre-main. So,
  56. // constexpr is required.
  57. constexpr ArenaBlock kSentryArenaBlock;
  58. ArenaBlock* SentryArenaBlock() {
  59. // const_cast<> is okay as kSentryArenaBlock will never be mutated.
  60. return const_cast<ArenaBlock*>(&kSentryArenaBlock);
  61. }
  62. #else
  63. // TODO(b/248322260) Remove this once we're not using GCC 4.9 for tests.
  64. // There is a compiler bug in this version that causes the above constexpr to
  65. // fail. This version is no longer in our support window, but we use it in
  66. // some of our aarch64 docker images.
  67. ArenaBlock* SentryArenaBlock() {
  68. static const ArenaBlock kSentryArenaBlock;
  69. // const_cast<> is okay as kSentryArenaBlock will never be mutated.
  70. return const_cast<ArenaBlock*>(&kSentryArenaBlock);
  71. }
  72. #endif
  73. } // namespace
  74. static SizedPtr AllocateMemory(const AllocationPolicy* policy_ptr,
  75. size_t last_size, size_t min_bytes) {
  76. AllocationPolicy policy; // default policy
  77. if (policy_ptr) policy = *policy_ptr;
  78. size_t size;
  79. if (last_size != 0) {
  80. // Double the current block size, up to a limit.
  81. auto max_size = policy.max_block_size;
  82. size = std::min(2 * last_size, max_size);
  83. } else {
  84. size = policy.start_block_size;
  85. }
  86. // Verify that min_bytes + kBlockHeaderSize won't overflow.
  87. ABSL_CHECK_LE(min_bytes, std::numeric_limits<size_t>::max() -
  88. SerialArena::kBlockHeaderSize);
  89. size = std::max(size, SerialArena::kBlockHeaderSize + min_bytes);
  90. if (policy.block_alloc == nullptr) {
  91. return AllocateAtLeast(size);
  92. }
  93. return {policy.block_alloc(size), size};
  94. }
  95. class GetDeallocator {
  96. public:
  97. GetDeallocator(const AllocationPolicy* policy, size_t* space_allocated)
  98. : dealloc_(policy ? policy->block_dealloc : nullptr),
  99. space_allocated_(space_allocated) {}
  100. void operator()(SizedPtr mem) const {
  101. #ifdef ADDRESS_SANITIZER
  102. // This memory was provided by the underlying allocator as unpoisoned,
  103. // so return it in an unpoisoned state.
  104. ASAN_UNPOISON_MEMORY_REGION(mem.p, mem.n);
  105. #endif // ADDRESS_SANITIZER
  106. if (dealloc_) {
  107. dealloc_(mem.p, mem.n);
  108. } else {
  109. internal::SizedDelete(mem.p, mem.n);
  110. }
  111. *space_allocated_ += mem.n;
  112. }
  113. private:
  114. void (*dealloc_)(void*, size_t);
  115. size_t* space_allocated_;
  116. };
  117. // It is guaranteed that this is constructed in `b`. IOW, this is not the first
  118. // arena and `b` cannot be sentry.
  119. SerialArena::SerialArena(ArenaBlock* b, ThreadSafeArena& parent)
  120. : ptr_{b->Pointer(kBlockHeaderSize + ThreadSafeArena::kSerialArenaSize)},
  121. limit_{b->Limit()},
  122. head_{b},
  123. space_allocated_{b->size},
  124. parent_{parent} {
  125. ABSL_DCHECK(!b->IsSentry());
  126. }
  127. // It is guaranteed that this is the first SerialArena. Use sentry block.
  128. SerialArena::SerialArena(ThreadSafeArena& parent)
  129. : head_{SentryArenaBlock()}, parent_{parent} {}
  130. // It is guaranteed that this is the first SerialArena but `b` may be user
  131. // provided or newly allocated to store AllocationPolicy.
  132. SerialArena::SerialArena(FirstSerialArena, ArenaBlock* b,
  133. ThreadSafeArena& parent)
  134. : head_{b}, space_allocated_{b->size}, parent_{parent} {
  135. if (b->IsSentry()) return;
  136. set_ptr(b->Pointer(kBlockHeaderSize));
  137. limit_ = b->Limit();
  138. }
  139. void SerialArena::Init(ArenaBlock* b, size_t offset) {
  140. set_ptr(b->Pointer(offset));
  141. limit_ = b->Limit();
  142. head_.store(b, std::memory_order_relaxed);
  143. space_used_.store(0, std::memory_order_relaxed);
  144. space_allocated_.store(b->size, std::memory_order_relaxed);
  145. cached_block_length_ = 0;
  146. cached_blocks_ = nullptr;
  147. string_block_ = nullptr;
  148. string_block_unused_.store(0, std::memory_order_relaxed);
  149. }
  150. SerialArena* SerialArena::New(SizedPtr mem, ThreadSafeArena& parent) {
  151. ABSL_DCHECK_LE(kBlockHeaderSize + ThreadSafeArena::kSerialArenaSize, mem.n);
  152. ThreadSafeArenaStats::RecordAllocateStats(parent.arena_stats_.MutableStats(),
  153. /*used=*/0, /*allocated=*/mem.n,
  154. /*wasted=*/0);
  155. auto b = new (mem.p) ArenaBlock{nullptr, mem.n};
  156. return new (b->Pointer(kBlockHeaderSize)) SerialArena(b, parent);
  157. }
  158. template <typename Deallocator>
  159. SizedPtr SerialArena::Free(Deallocator deallocator) {
  160. ArenaBlock* b = head();
  161. SizedPtr mem = {b, b->size};
  162. while (b->next) {
  163. b = b->next; // We must first advance before deleting this block
  164. deallocator(mem);
  165. mem = {b, b->size};
  166. }
  167. return mem;
  168. }
  169. PROTOBUF_NOINLINE
  170. void* SerialArena::AllocateAlignedFallback(size_t n) {
  171. AllocateNewBlock(n);
  172. return AllocateFromExisting(n);
  173. }
  174. PROTOBUF_NOINLINE
  175. void* SerialArena::AllocateFromStringBlockFallback() {
  176. if (string_block_) {
  177. ABSL_DCHECK_EQ(string_block_unused_.load(std::memory_order_relaxed), 0U);
  178. space_used_.store(space_used_.load(std::memory_order_relaxed) +
  179. string_block_->effective_size(),
  180. std::memory_order_relaxed);
  181. }
  182. string_block_ = StringBlock::New(string_block_);
  183. space_allocated_.store(space_allocated_.load(std::memory_order_relaxed) +
  184. string_block_->allocated_size(),
  185. std::memory_order_relaxed);
  186. size_t unused = string_block_->effective_size() - sizeof(std::string);
  187. string_block_unused_.store(unused, std::memory_order_relaxed);
  188. return string_block_->AtOffset(unused);
  189. }
  190. PROTOBUF_NOINLINE
  191. void* SerialArena::AllocateAlignedWithCleanupFallback(
  192. size_t n, size_t align, void (*destructor)(void*)) {
  193. size_t required = AlignUpTo(n, align) + cleanup::Size(destructor);
  194. AllocateNewBlock(required);
  195. return AllocateFromExistingWithCleanupFallback(n, align, destructor);
  196. }
  197. PROTOBUF_NOINLINE
  198. void SerialArena::AddCleanupFallback(void* elem, void (*destructor)(void*)) {
  199. size_t required = cleanup::Size(destructor);
  200. AllocateNewBlock(required);
  201. AddCleanupFromExisting(elem, destructor);
  202. }
  203. void SerialArena::AllocateNewBlock(size_t n) {
  204. size_t used = 0;
  205. size_t wasted = 0;
  206. ArenaBlock* old_head = head();
  207. if (!old_head->IsSentry()) {
  208. // Sync limit to block
  209. old_head->cleanup_nodes = limit_;
  210. // Record how much used in this block.
  211. used = static_cast<size_t>(ptr() - old_head->Pointer(kBlockHeaderSize));
  212. wasted = old_head->size - used;
  213. space_used_.store(space_used_.load(std::memory_order_relaxed) + used,
  214. std::memory_order_relaxed);
  215. }
  216. // TODO(sbenza): Evaluate if pushing unused space into the cached blocks is a
  217. // win. In preliminary testing showed increased memory savings as expected,
  218. // but with a CPU regression. The regression might have been an artifact of
  219. // the microbenchmark.
  220. auto mem = AllocateMemory(parent_.AllocPolicy(), old_head->size, n);
  221. // We don't want to emit an expensive RMW instruction that requires
  222. // exclusive access to a cacheline. Hence we write it in terms of a
  223. // regular add.
  224. space_allocated_.store(
  225. space_allocated_.load(std::memory_order_relaxed) + mem.n,
  226. std::memory_order_relaxed);
  227. ThreadSafeArenaStats::RecordAllocateStats(parent_.arena_stats_.MutableStats(),
  228. /*used=*/used,
  229. /*allocated=*/mem.n, wasted);
  230. auto* new_head = new (mem.p) ArenaBlock{old_head, mem.n};
  231. set_ptr(new_head->Pointer(kBlockHeaderSize));
  232. limit_ = new_head->Limit();
  233. // Previous writes must take effect before writing new head.
  234. head_.store(new_head, std::memory_order_release);
  235. #ifdef ADDRESS_SANITIZER
  236. ASAN_POISON_MEMORY_REGION(ptr(), limit_ - ptr());
  237. #endif // ADDRESS_SANITIZER
  238. }
  239. uint64_t SerialArena::SpaceUsed() const {
  240. // Note: the calculation below technically causes a race with
  241. // AllocateNewBlock when called from another thread (which happens in
  242. // ThreadSafeArena::SpaceUsed). However, worst-case space_used_ will have
  243. // stale data and the calculation will incorrectly assume 100%
  244. // usage of the *current* block.
  245. // TODO(mkruskal) Consider eliminating this race in exchange for a possible
  246. // performance hit on ARM (see cl/455186837).
  247. uint64_t current_space_used =
  248. string_block_ ? string_block_->effective_size() -
  249. string_block_unused_.load(std::memory_order_relaxed)
  250. : 0;
  251. const ArenaBlock* h = head_.load(std::memory_order_acquire);
  252. if (h->IsSentry()) return current_space_used;
  253. const uint64_t current_block_size = h->size;
  254. current_space_used += std::min(
  255. static_cast<uint64_t>(
  256. ptr() - const_cast<ArenaBlock*>(h)->Pointer(kBlockHeaderSize)),
  257. current_block_size);
  258. return current_space_used + space_used_.load(std::memory_order_relaxed);
  259. }
  260. size_t SerialArena::FreeStringBlocks(StringBlock* string_block,
  261. size_t unused_bytes) {
  262. ABSL_DCHECK(string_block != nullptr);
  263. StringBlock* next = string_block->next();
  264. std::string* end = string_block->end();
  265. for (std::string* s = string_block->AtOffset(unused_bytes); s != end; ++s) {
  266. s->~basic_string();
  267. }
  268. size_t deallocated = StringBlock::Delete(string_block);
  269. while ((string_block = next) != nullptr) {
  270. next = string_block->next();
  271. for (std::string& s : *string_block) {
  272. s.~basic_string();
  273. }
  274. deallocated += StringBlock::Delete(string_block);
  275. }
  276. return deallocated;
  277. }
  278. void SerialArena::CleanupList() {
  279. ArenaBlock* b = head();
  280. if (b->IsSentry()) return;
  281. b->cleanup_nodes = limit_;
  282. do {
  283. char* limit = b->Limit();
  284. char* it = reinterpret_cast<char*>(b->cleanup_nodes);
  285. ABSL_DCHECK(!b->IsSentry() || it == limit);
  286. while (it < limit) {
  287. it += cleanup::DestroyNode(it);
  288. }
  289. b = b->next;
  290. } while (b);
  291. }
  292. // Stores arrays of void* and SerialArena* instead of linked list of
  293. // SerialArena* to speed up traversing all SerialArena. The cost of walk is non
  294. // trivial when there are many nodes. Separately storing "ids" minimizes cache
  295. // footprints and more efficient when looking for matching arena.
  296. //
  297. // Uses absl::container_internal::Layout to emulate the following:
  298. //
  299. // struct SerialArenaChunk {
  300. // struct SerialArenaChunkHeader {
  301. // SerialArenaChunk* next_chunk;
  302. // uint32_t capacity;
  303. // std::atomic<uint32_t> size;
  304. // } header;
  305. // std::atomic<void*> ids[];
  306. // std::atomic<SerialArena*> arenas[];
  307. // };
  308. //
  309. // where the size of "ids" and "arenas" is determined at runtime; hence the use
  310. // of Layout.
  311. struct SerialArenaChunkHeader {
  312. constexpr SerialArenaChunkHeader(uint32_t capacity, uint32_t size)
  313. : next_chunk(nullptr), capacity(capacity), size(size) {}
  314. ThreadSafeArena::SerialArenaChunk* next_chunk;
  315. uint32_t capacity;
  316. std::atomic<uint32_t> size;
  317. };
  318. class ThreadSafeArena::SerialArenaChunk {
  319. public:
  320. SerialArenaChunk(uint32_t capacity, void* me, SerialArena* serial) {
  321. new (&header()) SerialArenaChunkHeader{capacity, 1};
  322. new (&id(0)) std::atomic<void*>{me};
  323. for (uint32_t i = 1; i < capacity; ++i) {
  324. new (&id(i)) std::atomic<void*>{nullptr};
  325. }
  326. new (&arena(0)) std::atomic<SerialArena*>{serial};
  327. for (uint32_t i = 1; i < capacity; ++i) {
  328. new (&arena(i)) std::atomic<void*>{nullptr};
  329. }
  330. }
  331. bool IsSentry() const { return capacity() == 0; }
  332. // next_chunk
  333. const SerialArenaChunk* next_chunk() const { return header().next_chunk; }
  334. SerialArenaChunk* next_chunk() { return header().next_chunk; }
  335. void set_next(SerialArenaChunk* next_chunk) {
  336. header().next_chunk = next_chunk;
  337. }
  338. // capacity
  339. uint32_t capacity() const { return header().capacity; }
  340. void set_capacity(uint32_t capacity) { header().capacity = capacity; }
  341. // ids: returns up to size().
  342. absl::Span<const std::atomic<void*>> ids() const {
  343. return Layout(capacity()).Slice<kIds>(ptr()).first(safe_size());
  344. }
  345. absl::Span<std::atomic<void*>> ids() {
  346. return Layout(capacity()).Slice<kIds>(ptr()).first(safe_size());
  347. }
  348. std::atomic<void*>& id(uint32_t i) {
  349. ABSL_DCHECK_LT(i, capacity());
  350. return Layout(capacity()).Pointer<kIds>(ptr())[i];
  351. }
  352. // arenas: returns up to size().
  353. absl::Span<const std::atomic<SerialArena*>> arenas() const {
  354. return Layout(capacity()).Slice<kArenas>(ptr()).first(safe_size());
  355. }
  356. absl::Span<std::atomic<SerialArena*>> arenas() {
  357. return Layout(capacity()).Slice<kArenas>(ptr()).first(safe_size());
  358. }
  359. const std::atomic<SerialArena*>& arena(uint32_t i) const {
  360. ABSL_DCHECK_LT(i, capacity());
  361. return Layout(capacity()).Pointer<kArenas>(ptr())[i];
  362. }
  363. std::atomic<SerialArena*>& arena(uint32_t i) {
  364. ABSL_DCHECK_LT(i, capacity());
  365. return Layout(capacity()).Pointer<kArenas>(ptr())[i];
  366. }
  367. // Tries to insert {id, serial} to head chunk. Returns false if the head is
  368. // already full.
  369. //
  370. // Note that the updating "size", "id", "arena" is individually atomic but
  371. // those are not protected by a mutex. This is acceptable because concurrent
  372. // lookups from SpaceUsed or SpaceAllocated accept inaccuracy due to race. On
  373. // other paths, either race is not possible (GetSerialArenaFallback) or must
  374. // be prevented by users (CleanupList, Free).
  375. bool insert(void* me, SerialArena* serial) {
  376. uint32_t idx = size().fetch_add(1, std::memory_order_relaxed);
  377. // Bail out if this chunk is full.
  378. if (idx >= capacity()) {
  379. // Write old value back to avoid potential overflow.
  380. size().store(capacity(), std::memory_order_relaxed);
  381. return false;
  382. }
  383. id(idx).store(me, std::memory_order_relaxed);
  384. arena(idx).store(serial, std::memory_order_release);
  385. return true;
  386. }
  387. constexpr static size_t AllocSize(size_t n) { return Layout(n).AllocSize(); }
  388. private:
  389. constexpr static int kHeader = 0;
  390. constexpr static int kIds = 1;
  391. constexpr static int kArenas = 2;
  392. using layout_type = absl::container_internal::Layout<
  393. SerialArenaChunkHeader, std::atomic<void*>, std::atomic<SerialArena*>>;
  394. const char* ptr() const { return reinterpret_cast<const char*>(this); }
  395. char* ptr() { return reinterpret_cast<char*>(this); }
  396. SerialArenaChunkHeader& header() {
  397. return *layout_type::Partial().Pointer<kHeader>(ptr());
  398. }
  399. const SerialArenaChunkHeader& header() const {
  400. return *layout_type::Partial().Pointer<kHeader>(ptr());
  401. }
  402. std::atomic<uint32_t>& size() { return header().size; }
  403. const std::atomic<uint32_t>& size() const { return header().size; }
  404. // Returns the size capped by the capacity as fetch_add may result in a size
  405. // greater than capacity.
  406. uint32_t safe_size() const {
  407. return std::min(capacity(), size().load(std::memory_order_relaxed));
  408. }
  409. constexpr static layout_type Layout(size_t n) {
  410. return layout_type(
  411. /*header*/ 1,
  412. /*ids*/ n,
  413. /*arenas*/ n);
  414. }
  415. };
  416. constexpr SerialArenaChunkHeader kSentryArenaChunk = {0, 0};
  417. ThreadSafeArena::SerialArenaChunk* ThreadSafeArena::SentrySerialArenaChunk() {
  418. // const_cast is okay because the sentry chunk is never mutated. Also,
  419. // reinterpret_cast is acceptable here as it should be identical to
  420. // SerialArenaChunk with zero payload. This is a necessary trick to
  421. // constexpr initialize kSentryArenaChunk.
  422. return reinterpret_cast<SerialArenaChunk*>(
  423. const_cast<SerialArenaChunkHeader*>(&kSentryArenaChunk));
  424. }
  425. alignas(kCacheAlignment) ABSL_CONST_INIT
  426. std::atomic<ThreadSafeArena::LifecycleId> ThreadSafeArena::lifecycle_id_{0};
  427. #if defined(PROTOBUF_NO_THREADLOCAL)
  428. ThreadSafeArena::ThreadCache& ThreadSafeArena::thread_cache() {
  429. static internal::ThreadLocalStorage<ThreadCache>* thread_cache_ =
  430. new internal::ThreadLocalStorage<ThreadCache>();
  431. return *thread_cache_->Get();
  432. }
  433. #elif defined(PROTOBUF_USE_DLLS)
  434. ThreadSafeArena::ThreadCache& ThreadSafeArena::thread_cache() {
  435. static PROTOBUF_THREAD_LOCAL ThreadCache thread_cache;
  436. return thread_cache;
  437. }
  438. #else
  439. PROTOBUF_CONSTINIT PROTOBUF_THREAD_LOCAL
  440. ThreadSafeArena::ThreadCache ThreadSafeArena::thread_cache_;
  441. #endif
  442. ThreadSafeArena::ThreadSafeArena() : first_arena_(*this) { Init(); }
  443. ThreadSafeArena::ThreadSafeArena(char* mem, size_t size)
  444. : first_arena_(FirstSerialArena{}, FirstBlock(mem, size), *this) {
  445. Init();
  446. }
  447. ThreadSafeArena::ThreadSafeArena(void* mem, size_t size,
  448. const AllocationPolicy& policy)
  449. : first_arena_(FirstSerialArena{}, FirstBlock(mem, size, policy), *this) {
  450. InitializeWithPolicy(policy);
  451. }
  452. ArenaBlock* ThreadSafeArena::FirstBlock(void* buf, size_t size) {
  453. ABSL_DCHECK_EQ(reinterpret_cast<uintptr_t>(buf) & 7, 0u);
  454. if (buf == nullptr || size <= kBlockHeaderSize) {
  455. return SentryArenaBlock();
  456. }
  457. // Record user-owned block.
  458. alloc_policy_.set_is_user_owned_initial_block(true);
  459. return new (buf) ArenaBlock{nullptr, size};
  460. }
  461. ArenaBlock* ThreadSafeArena::FirstBlock(void* buf, size_t size,
  462. const AllocationPolicy& policy) {
  463. if (policy.IsDefault()) return FirstBlock(buf, size);
  464. ABSL_DCHECK_EQ(reinterpret_cast<uintptr_t>(buf) & 7, 0u);
  465. SizedPtr mem;
  466. if (buf == nullptr || size < kBlockHeaderSize + kAllocPolicySize) {
  467. mem = AllocateMemory(&policy, 0, kAllocPolicySize);
  468. } else {
  469. mem = {buf, size};
  470. // Record user-owned block.
  471. alloc_policy_.set_is_user_owned_initial_block(true);
  472. }
  473. return new (mem.p) ArenaBlock{nullptr, mem.n};
  474. }
  475. void ThreadSafeArena::InitializeWithPolicy(const AllocationPolicy& policy) {
  476. Init();
  477. if (policy.IsDefault()) return;
  478. #ifndef NDEBUG
  479. const uint64_t old_alloc_policy = alloc_policy_.get_raw();
  480. // If there was a policy (e.g., in Reset()), make sure flags were preserved.
  481. #define ABSL_DCHECK_POLICY_FLAGS_() \
  482. if (old_alloc_policy > 3) \
  483. ABSL_CHECK_EQ(old_alloc_policy & 3, alloc_policy_.get_raw() & 3)
  484. #else
  485. #define ABSL_DCHECK_POLICY_FLAGS_()
  486. #endif // NDEBUG
  487. // We ensured enough space so this cannot fail.
  488. void* p;
  489. if (!first_arena_.MaybeAllocateAligned(kAllocPolicySize, &p)) {
  490. ABSL_LOG(FATAL) << "MaybeAllocateAligned cannot fail here.";
  491. return;
  492. }
  493. new (p) AllocationPolicy{policy};
  494. // Low bits store flags, so they mustn't be overwritten.
  495. ABSL_DCHECK_EQ(0u, reinterpret_cast<uintptr_t>(p) & 3);
  496. alloc_policy_.set_policy(reinterpret_cast<AllocationPolicy*>(p));
  497. ABSL_DCHECK_POLICY_FLAGS_();
  498. #undef ABSL_DCHECK_POLICY_FLAGS_
  499. }
  500. uint64_t ThreadSafeArena::GetNextLifeCycleId() {
  501. ThreadCache& tc = thread_cache();
  502. uint64_t id = tc.next_lifecycle_id;
  503. constexpr uint64_t kInc = ThreadCache::kPerThreadIds;
  504. if (PROTOBUF_PREDICT_FALSE((id & (kInc - 1)) == 0)) {
  505. // On platforms that don't support uint64_t atomics we can certainly not
  506. // afford to increment by large intervals and expect uniqueness due to
  507. // wrapping, hence we only add by 1.
  508. id = lifecycle_id_.fetch_add(1, std::memory_order_relaxed) * kInc;
  509. }
  510. tc.next_lifecycle_id = id + 1;
  511. return id;
  512. }
  513. // We assume that #threads / arena is bimodal; i.e. majority small ones are
  514. // single threaded but some big ones are highly concurrent. To balance between
  515. // memory overhead and minimum pointer chasing, we start with few entries and
  516. // exponentially (4x) grow with a limit (255 entries). Note that parameters are
  517. // picked for x64 architectures as hint and the actual size is calculated by
  518. // Layout.
  519. ThreadSafeArena::SerialArenaChunk* ThreadSafeArena::NewSerialArenaChunk(
  520. uint32_t prev_capacity, void* id, SerialArena* serial) {
  521. constexpr size_t kMaxBytes = 4096; // Can hold up to 255 entries.
  522. constexpr size_t kGrowthFactor = 4;
  523. constexpr size_t kHeaderSize = SerialArenaChunk::AllocSize(0);
  524. constexpr size_t kEntrySize = SerialArenaChunk::AllocSize(1) - kHeaderSize;
  525. // On x64 arch: {4, 16, 64, 256, 256, ...} * 16.
  526. size_t prev_bytes = SerialArenaChunk::AllocSize(prev_capacity);
  527. size_t next_bytes = std::min(kMaxBytes, prev_bytes * kGrowthFactor);
  528. uint32_t next_capacity =
  529. static_cast<uint32_t>(next_bytes - kHeaderSize) / kEntrySize;
  530. // Growth based on bytes needs to be adjusted by AllocSize.
  531. next_bytes = SerialArenaChunk::AllocSize(next_capacity);
  532. // If we allocate bigger memory than requested, we should expand
  533. // size to use that extra space, and add extra entries permitted
  534. // by the extra space.
  535. SizedPtr mem = AllocateAtLeast(next_bytes);
  536. next_capacity = static_cast<uint32_t>(mem.n - kHeaderSize) / kEntrySize;
  537. ABSL_DCHECK_LE(SerialArenaChunk::AllocSize(next_capacity), mem.n);
  538. return new (mem.p) SerialArenaChunk{next_capacity, id, serial};
  539. }
  540. // Tries to reserve an entry by atomic fetch_add. If the head chunk is already
  541. // full (size >= capacity), acquires the mutex and adds a new head.
  542. void ThreadSafeArena::AddSerialArena(void* id, SerialArena* serial) {
  543. SerialArenaChunk* head = head_.load(std::memory_order_acquire);
  544. // Fast path without acquiring mutex.
  545. if (!head->IsSentry() && head->insert(id, serial)) {
  546. return;
  547. }
  548. // Slow path with acquiring mutex.
  549. absl::MutexLock lock(&mutex_);
  550. // Refetch and if someone else installed a new head, try allocating on that!
  551. SerialArenaChunk* new_head = head_.load(std::memory_order_acquire);
  552. if (new_head != head) {
  553. if (new_head->insert(id, serial)) return;
  554. // Update head to link to the latest one.
  555. head = new_head;
  556. }
  557. new_head = NewSerialArenaChunk(head->capacity(), id, serial);
  558. new_head->set_next(head);
  559. // Use "std::memory_order_release" to make sure prior stores are visible after
  560. // this one.
  561. head_.store(new_head, std::memory_order_release);
  562. }
  563. void ThreadSafeArena::Init() {
  564. tag_and_id_ = GetNextLifeCycleId();
  565. arena_stats_ = Sample();
  566. head_.store(SentrySerialArenaChunk(), std::memory_order_relaxed);
  567. first_owner_ = &thread_cache();
  568. // Record allocation for the first block that was either user-provided or
  569. // newly allocated.
  570. ThreadSafeArenaStats::RecordAllocateStats(
  571. arena_stats_.MutableStats(),
  572. /*used=*/0,
  573. /*allocated=*/first_arena_.SpaceAllocated(),
  574. /*wasted=*/0);
  575. CacheSerialArena(&first_arena_);
  576. }
  577. ThreadSafeArena::~ThreadSafeArena() {
  578. // Have to do this in a first pass, because some of the destructors might
  579. // refer to memory in other blocks.
  580. CleanupList();
  581. size_t space_allocated = 0;
  582. auto mem = Free(&space_allocated);
  583. if (alloc_policy_.is_user_owned_initial_block()) {
  584. #ifdef ADDRESS_SANITIZER
  585. // Unpoison the initial block, now that it's going back to the user.
  586. ASAN_UNPOISON_MEMORY_REGION(mem.p, mem.n);
  587. #endif // ADDRESS_SANITIZER
  588. space_allocated += mem.n;
  589. } else if (mem.n > 0) {
  590. GetDeallocator(alloc_policy_.get(), &space_allocated)(mem);
  591. }
  592. }
  593. SizedPtr ThreadSafeArena::Free(size_t* space_allocated) {
  594. auto deallocator = GetDeallocator(alloc_policy_.get(), space_allocated);
  595. WalkSerialArenaChunk([&](SerialArenaChunk* chunk) {
  596. absl::Span<std::atomic<SerialArena*>> span = chunk->arenas();
  597. // Walks arenas backward to handle the first serial arena the last. Freeing
  598. // in reverse-order to the order in which objects were created may not be
  599. // necessary to Free and we should revisit this. (b/247560530)
  600. for (auto it = span.rbegin(); it != span.rend(); ++it) {
  601. SerialArena* serial = it->load(std::memory_order_relaxed);
  602. ABSL_DCHECK_NE(serial, nullptr);
  603. // Free string blocks
  604. *space_allocated += serial->FreeStringBlocks();
  605. // Always frees the first block of "serial" as it cannot be user-provided.
  606. SizedPtr mem = serial->Free(deallocator);
  607. ABSL_DCHECK_NE(mem.p, nullptr);
  608. deallocator(mem);
  609. }
  610. // Delete the chunk as we're done with it.
  611. internal::SizedDelete(chunk,
  612. SerialArenaChunk::AllocSize(chunk->capacity()));
  613. });
  614. // The first block of the first arena is special and let the caller handle it.
  615. *space_allocated += first_arena_.FreeStringBlocks();
  616. return first_arena_.Free(deallocator);
  617. }
  618. uint64_t ThreadSafeArena::Reset() {
  619. // Have to do this in a first pass, because some of the destructors might
  620. // refer to memory in other blocks.
  621. CleanupList();
  622. // Discard all blocks except the first one. Whether it is user-provided or
  623. // allocated, always reuse the first block for the first arena.
  624. size_t space_allocated = 0;
  625. auto mem = Free(&space_allocated);
  626. space_allocated += mem.n;
  627. // Reset the first arena with the first block. This avoids redundant
  628. // free / allocation and re-allocating for AllocationPolicy. Adjust offset if
  629. // we need to preserve alloc_policy_.
  630. if (alloc_policy_.is_user_owned_initial_block() ||
  631. alloc_policy_.get() != nullptr) {
  632. size_t offset = alloc_policy_.get() == nullptr
  633. ? kBlockHeaderSize
  634. : kBlockHeaderSize + kAllocPolicySize;
  635. first_arena_.Init(new (mem.p) ArenaBlock{nullptr, mem.n}, offset);
  636. } else {
  637. first_arena_.Init(SentryArenaBlock(), 0);
  638. }
  639. // Since the first block and potential alloc_policy on the first block is
  640. // preserved, this can be initialized by Init().
  641. Init();
  642. return space_allocated;
  643. }
  644. void* ThreadSafeArena::AllocateAlignedWithCleanup(size_t n, size_t align,
  645. void (*destructor)(void*)) {
  646. SerialArena* arena;
  647. if (PROTOBUF_PREDICT_TRUE(GetSerialArenaFast(&arena))) {
  648. return arena->AllocateAlignedWithCleanup(n, align, destructor);
  649. } else {
  650. return AllocateAlignedWithCleanupFallback(n, align, destructor);
  651. }
  652. }
  653. void ThreadSafeArena::AddCleanup(void* elem, void (*cleanup)(void*)) {
  654. SerialArena* arena;
  655. if (PROTOBUF_PREDICT_FALSE(!GetSerialArenaFast(&arena))) {
  656. arena = GetSerialArenaFallback(kMaxCleanupNodeSize);
  657. }
  658. arena->AddCleanup(elem, cleanup);
  659. }
  660. PROTOBUF_NOINLINE
  661. void* ThreadSafeArena::AllocateAlignedWithCleanupFallback(
  662. size_t n, size_t align, void (*destructor)(void*)) {
  663. return GetSerialArenaFallback(n + kMaxCleanupNodeSize)
  664. ->AllocateAlignedWithCleanup(n, align, destructor);
  665. }
  666. PROTOBUF_NOINLINE
  667. void* ThreadSafeArena::AllocateFromStringBlock() {
  668. SerialArena* arena;
  669. if (PROTOBUF_PREDICT_FALSE(!GetSerialArenaFast(&arena))) {
  670. arena = GetSerialArenaFallback(0);
  671. }
  672. return arena->AllocateFromStringBlock();
  673. }
  674. template <typename Functor>
  675. void ThreadSafeArena::WalkConstSerialArenaChunk(Functor fn) const {
  676. const SerialArenaChunk* chunk = head_.load(std::memory_order_acquire);
  677. for (; !chunk->IsSentry(); chunk = chunk->next_chunk()) {
  678. fn(chunk);
  679. }
  680. }
  681. template <typename Functor>
  682. void ThreadSafeArena::WalkSerialArenaChunk(Functor fn) {
  683. // By omitting an Acquire barrier we help the sanitizer that any user code
  684. // that doesn't properly synchronize Reset() or the destructor will throw a
  685. // TSAN warning.
  686. SerialArenaChunk* chunk = head_.load(std::memory_order_relaxed);
  687. while (!chunk->IsSentry()) {
  688. // Cache next chunk in case this chunk is destroyed.
  689. SerialArenaChunk* next_chunk = chunk->next_chunk();
  690. fn(chunk);
  691. chunk = next_chunk;
  692. }
  693. }
  694. template <typename Functor>
  695. void ThreadSafeArena::PerConstSerialArenaInChunk(Functor fn) const {
  696. WalkConstSerialArenaChunk([&fn](const SerialArenaChunk* chunk) {
  697. for (const auto& each : chunk->arenas()) {
  698. const SerialArena* serial = each.load(std::memory_order_acquire);
  699. // It is possible that newly added SerialArena is not updated although
  700. // size was. This is acceptable for SpaceAllocated and SpaceUsed.
  701. if (serial == nullptr) continue;
  702. fn(serial);
  703. }
  704. });
  705. }
  706. uint64_t ThreadSafeArena::SpaceAllocated() const {
  707. uint64_t space_allocated = first_arena_.SpaceAllocated();
  708. PerConstSerialArenaInChunk([&space_allocated](const SerialArena* serial) {
  709. space_allocated += serial->SpaceAllocated();
  710. });
  711. return space_allocated;
  712. }
  713. uint64_t ThreadSafeArena::SpaceUsed() const {
  714. // First arena is inlined to ThreadSafeArena and the first block's overhead is
  715. // smaller than others that contain SerialArena.
  716. uint64_t space_used = first_arena_.SpaceUsed();
  717. PerConstSerialArenaInChunk([&space_used](const SerialArena* serial) {
  718. // SerialArena on chunks directly allocated from the block and needs to be
  719. // subtracted from SpaceUsed.
  720. space_used += serial->SpaceUsed() - kSerialArenaSize;
  721. });
  722. return space_used - (alloc_policy_.get() ? sizeof(AllocationPolicy) : 0);
  723. }
  724. template <AllocationClient alloc_client>
  725. PROTOBUF_NOINLINE void* ThreadSafeArena::AllocateAlignedFallback(size_t n) {
  726. return GetSerialArenaFallback(n)->AllocateAligned<alloc_client>(n);
  727. }
  728. template void* ThreadSafeArena::AllocateAlignedFallback<
  729. AllocationClient::kDefault>(size_t);
  730. template void*
  731. ThreadSafeArena::AllocateAlignedFallback<AllocationClient::kArray>(size_t);
  732. void ThreadSafeArena::CleanupList() {
  733. WalkSerialArenaChunk([](SerialArenaChunk* chunk) {
  734. absl::Span<std::atomic<SerialArena*>> span = chunk->arenas();
  735. // Walks arenas backward to handle the first serial arena the last.
  736. // Destroying in reverse-order to the construction is often assumed by users
  737. // and required not to break inter-object dependencies. (b/247560530)
  738. for (auto it = span.rbegin(); it != span.rend(); ++it) {
  739. SerialArena* serial = it->load(std::memory_order_relaxed);
  740. ABSL_DCHECK_NE(serial, nullptr);
  741. serial->CleanupList();
  742. }
  743. });
  744. // First arena must be cleaned up last. (b/247560530)
  745. first_arena_.CleanupList();
  746. }
  747. PROTOBUF_NOINLINE
  748. SerialArena* ThreadSafeArena::GetSerialArenaFallback(size_t n) {
  749. void* const id = &thread_cache();
  750. if (id == first_owner_) {
  751. CacheSerialArena(&first_arena_);
  752. return &first_arena_;
  753. }
  754. // Search matching SerialArena.
  755. SerialArena* serial = nullptr;
  756. WalkConstSerialArenaChunk([&serial, id](const SerialArenaChunk* chunk) {
  757. absl::Span<const std::atomic<void*>> ids = chunk->ids();
  758. for (uint32_t i = 0; i < ids.size(); ++i) {
  759. if (ids[i].load(std::memory_order_relaxed) == id) {
  760. serial = chunk->arena(i).load(std::memory_order_relaxed);
  761. ABSL_DCHECK_NE(serial, nullptr);
  762. break;
  763. }
  764. }
  765. });
  766. if (!serial) {
  767. // This thread doesn't have any SerialArena, which also means it doesn't
  768. // have any blocks yet. So we'll allocate its first block now. It must be
  769. // big enough to host SerialArena and the pending request.
  770. serial = SerialArena::New(
  771. AllocateMemory(alloc_policy_.get(), 0, n + kSerialArenaSize), *this);
  772. AddSerialArena(id, serial);
  773. }
  774. CacheSerialArena(serial);
  775. return serial;
  776. }
  777. } // namespace internal
  778. void* Arena::Allocate(size_t n) { return impl_.AllocateAligned(n); }
  779. void* Arena::AllocateForArray(size_t n) {
  780. return impl_.AllocateAligned<internal::AllocationClient::kArray>(n);
  781. }
  782. void* Arena::AllocateAlignedWithCleanup(size_t n, size_t align,
  783. void (*destructor)(void*)) {
  784. return impl_.AllocateAlignedWithCleanup(n, align, destructor);
  785. }
  786. } // namespace protobuf
  787. } // namespace google
  788. #include "google/protobuf/port_undef.inc"