/* * * Copyright 2017 gRPC authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * */ #include #include "src/core/lib/gpr/arena.h" #include #include #include #include #include #include #include "src/core/lib/gpr/alloc.h" #include "src/core/lib/gpr/env.h" #include "src/core/lib/gprpp/memory.h" namespace { enum init_strategy { NO_INIT, // Do not initialize the arena blocks. ZERO_INIT, // Initialize arena blocks with 0. NON_ZERO_INIT, // Initialize arena blocks with a non-zero value. }; gpr_once g_init_strategy_once = GPR_ONCE_INIT; init_strategy g_init_strategy = NO_INIT; } // namespace static void set_strategy_from_env() { char* str = gpr_getenv("GRPC_ARENA_INIT_STRATEGY"); if (str == nullptr) { g_init_strategy = NO_INIT; } else if (strcmp(str, "zero_init") == 0) { g_init_strategy = ZERO_INIT; } else if (strcmp(str, "non_zero_init") == 0) { g_init_strategy = NON_ZERO_INIT; } else { g_init_strategy = NO_INIT; } gpr_free(str); } static void* gpr_arena_alloc_maybe_init(size_t size) { void* mem = gpr_malloc_aligned(size, GPR_MAX_ALIGNMENT); gpr_once_init(&g_init_strategy_once, set_strategy_from_env); if (GPR_UNLIKELY(g_init_strategy != NO_INIT)) { if (g_init_strategy == ZERO_INIT) { memset(mem, 0, size); } else { // NON_ZERO_INIT. memset(mem, 0xFE, size); } } return mem; } void gpr_arena_init() { gpr_once_init(&g_init_strategy_once, set_strategy_from_env); } // Uncomment this to use a simple arena that simply allocates the // requested amount of memory for each call to gpr_arena_alloc(). This // effectively eliminates the efficiency gain of using an arena, but it // may be useful for debugging purposes. //#define SIMPLE_ARENA_FOR_DEBUGGING #ifdef SIMPLE_ARENA_FOR_DEBUGGING struct gpr_arena { gpr_arena() { gpr_mu_init(&mu); } ~gpr_arena() { gpr_mu_destroy(&mu); for (size_t i = 0; i < num_ptrs; ++i) { gpr_free_aligned(ptrs[i]); } gpr_free(ptrs); } gpr_mu mu; void** ptrs = nullptr; size_t num_ptrs = 0; }; gpr_arena* gpr_arena_create(size_t ignored_initial_size) { return grpc_core::New(); } size_t gpr_arena_destroy(gpr_arena* arena) { grpc_core::Delete(arena); return 1; // Value doesn't matter, since it won't be used. } void* gpr_arena_alloc(gpr_arena* arena, size_t size) { gpr_mu_lock(&arena->mu); arena->ptrs = (void**)gpr_realloc(arena->ptrs, sizeof(void*) * (arena->num_ptrs + 1)); void* retval = arena->ptrs[arena->num_ptrs++] = gpr_arena_alloc_maybe_init(size); gpr_mu_unlock(&arena->mu); return retval; } #else // SIMPLE_ARENA_FOR_DEBUGGING // TODO(roth): We currently assume that all callers need alignment of 16 // bytes, which may be wrong in some cases. As part of converting the // arena API to C++, we should consider replacing gpr_arena_alloc() with a // template that takes the type of the value being allocated, which // would allow us to use the alignment actually needed by the caller. typedef struct zone { zone* next = nullptr; } zone; struct gpr_arena { gpr_arena(size_t initial_size) : initial_zone_size(initial_size), last_zone(&initial_zone) { gpr_mu_init(&arena_growth_mutex); } ~gpr_arena() { gpr_mu_destroy(&arena_growth_mutex); zone* z = initial_zone.next; while (z) { zone* next_z = z->next; z->~zone(); gpr_free_aligned(z); z = next_z; } } // Keep track of the total used size. We use this in our call sizing // historesis. gpr_atm total_used = 0; size_t initial_zone_size; zone initial_zone; zone* last_zone; gpr_mu arena_growth_mutex; }; gpr_arena* gpr_arena_create(size_t initial_size) { initial_size = GPR_ROUND_UP_TO_ALIGNMENT_SIZE(initial_size); return new (gpr_arena_alloc_maybe_init( GPR_ROUND_UP_TO_ALIGNMENT_SIZE(sizeof(gpr_arena)) + initial_size)) gpr_arena(initial_size); } size_t gpr_arena_destroy(gpr_arena* arena) { const gpr_atm size = gpr_atm_no_barrier_load(&arena->total_used); arena->~gpr_arena(); gpr_free_aligned(arena); return static_cast(size); } void* gpr_arena_alloc(gpr_arena* arena, size_t size) { size = GPR_ROUND_UP_TO_ALIGNMENT_SIZE(size); size_t begin = gpr_atm_no_barrier_fetch_add(&arena->total_used, size); if (begin + size <= arena->initial_zone_size) { return reinterpret_cast(arena) + GPR_ROUND_UP_TO_ALIGNMENT_SIZE(sizeof(gpr_arena)) + begin; } else { // If the allocation isn't able to end in the initial zone, create a new // zone for this allocation, and any unused space in the initial zone is // wasted. This overflowing and wasting is uncommon because of our arena // sizing historesis (that is, most calls should have a large enough initial // zone and will not need to grow the arena). gpr_mu_lock(&arena->arena_growth_mutex); zone* z = new (gpr_arena_alloc_maybe_init( GPR_ROUND_UP_TO_ALIGNMENT_SIZE(sizeof(zone)) + size)) zone(); arena->last_zone->next = z; arena->last_zone = z; gpr_mu_unlock(&arena->arena_growth_mutex); return reinterpret_cast(z) + GPR_ROUND_UP_TO_ALIGNMENT_SIZE(sizeof(zone)); } } #endif // SIMPLE_ARENA_FOR_DEBUGGING