Reduce default inline storage in absl::FixedArray from 256 to 48 bytes. With a stateless allocator and without AddressSanitizer redzones, 48 bytes of inline element storage plus the size and data pointer keep a default absl::FixedArray within one 64-byte cache line for every element type. Element types larger than 48 bytes select zero inline elements and occupy 16 bytes. A 256-byte default inline buffer wastes memory inside heap-allocated objects when member arrays hold only a few elements or allocate out of line anyway. PiperOrigin-RevId: 1007192305
diff --git a/absl/container/fixed_array.h b/absl/container/fixed_array.h index 713c685..867ba37 100644 --- a/absl/container/fixed_array.h +++ b/absl/container/fixed_array.h
@@ -82,7 +82,11 @@ static_assert(!std::is_array_v<T> || std::extent_v<T> > 0, "Arrays with unknown bounds cannot be used with FixedArray."); - static constexpr size_t kInlineBytesDefault = 256; + // 48 bytes of inline elements plus the size and data pointer keep a default + // FixedArray with a stateless allocator within one 64-byte cache line. + // Larger default buffers waste memory in heap-resident objects that hold few + // elements or allocate out of line. + static constexpr size_t kInlineBytesDefault = 48; using AllocatorTraits = std::allocator_traits<A>; template <typename Iterator>
diff --git a/absl/container/fixed_array_test.cc b/absl/container/fixed_array_test.cc index e82919e..ba16d81 100644 --- a/absl/container/fixed_array_test.cc +++ b/absl/container/fixed_array_test.cc
@@ -16,6 +16,7 @@ #include <stdio.h> +#include <array> #include <cassert> #include <cstdint> #include <cstring> @@ -523,6 +524,28 @@ "default-sized absl::FixedArray should have same alignment as Data."); } +#ifndef ABSL_HAVE_ADDRESS_SANITIZER +TEST(FixedArrayTest, DefaultInlinedSpaceFitsInCacheLine) { + struct alignas(32) Aligned32 { + char c[32]; + }; + struct alignas(64) Aligned64 { + char c[64]; + }; + + constexpr size_t kHeaderSize = sizeof(void*) + sizeof(size_t); + EXPECT_EQ(sizeof(absl::FixedArray<char>), 48 + kHeaderSize); + EXPECT_EQ(sizeof(absl::FixedArray<int>), 48 + kHeaderSize); + EXPECT_EQ(sizeof(absl::FixedArray<void*>), 48 + kHeaderSize); + EXPECT_EQ(sizeof(absl::FixedArray<ThreeInts>), 48 + kHeaderSize); + EXPECT_EQ(sizeof(absl::FixedArray<std::array<char, 24>>), 48 + kHeaderSize); + EXPECT_EQ(sizeof(absl::FixedArray<std::array<char, 40>>), 40 + kHeaderSize); + EXPECT_EQ(sizeof(absl::FixedArray<std::array<char, 48>>), 48 + kHeaderSize); + EXPECT_EQ(sizeof(absl::FixedArray<Aligned32>), size_t{64}); + EXPECT_EQ(sizeof(absl::FixedArray<Aligned64>), kHeaderSize); +} +#endif // ABSL_HAVE_ADDRESS_SANITIZER + // PickyDelete EXPECTs its class-scope deallocation funcs are unused. struct PickyDelete { PickyDelete() {} @@ -840,19 +863,19 @@ } TEST(FixedArrayTest, AddressSanitizerAnnotations4) { - absl::FixedArray<ThreeInts> a(10); + absl::FixedArray<ThreeInts> a(2); ThreeInts* raw = a.data(); raw[0] = ThreeInts(); - raw[9] = ThreeInts(); + raw[1] = ThreeInts(); // Note: raw[-1] is pointing to 12 bytes before the container range. However, // there is only a 8-byte red zone before the container range, so we only // access the last 4 bytes of the struct to make sure it stays within the red // zone. EXPECT_DEATH_IF_SUPPORTED(raw[-1].z_ = 0, "container-overflow"); - EXPECT_DEATH_IF_SUPPORTED(raw[10] = ThreeInts(), "container-overflow"); - // The actual size of storage is kDefaultBytes=256, 21*12 = 252, - // so reading raw[21] should still trigger the correct warning. - EXPECT_DEATH_IF_SUPPORTED(raw[21] = ThreeInts(), "container-overflow"); + EXPECT_DEATH_IF_SUPPORTED(raw[2] = ThreeInts(), "container-overflow"); + // The actual size of storage is kInlineBytesDefault=48, 4*12 = 48, + // so reading raw[4] should still trigger the correct warning. + EXPECT_DEATH_IF_SUPPORTED(raw[4] = ThreeInts(), "container-overflow"); } #endif // ABSL_HAVE_ADDRESS_SANITIZER