Skip to content

Commit

Permalink
Improve field count typical case performance (#120)
Browse files Browse the repository at this point in the history
The tightest upper bound one can specify on the number of fields in a
struct is `sizeof(type) * CHAR_BIT`. So this was previously used when
performing a binary search for the field count. This upper bound is
extremely loose when considering a typical large struct, which is more
likely to contain a relatively small number of relatively large fields
rather than the other way around. The binary search range being multiple
orders of magnitude larger than necessary wouldn't have been a
significant issue if each test was cheap, but they're not. Testing a
field count of N costs O(N) memory and time. As a result, the initial
few steps of the binary search may be prohibitively expensive.

The primary optimization introduced by these changes is to use unbounded
binary search, a.k.a. exponential search, instead of the typically
loosely bounded binary search. This produces a tight upper bound (within
2x) on the field count to then perform the binary search with.

As an upside of this change, the compiler-specific limit placed on the
upper bound on the field count to stay within compiler limits could be
removed.
  • Loading branch information
runer112 authored Oct 9, 2024
1 parent 8cd1a96 commit ff415a2
Show file tree
Hide file tree
Showing 10 changed files with 1,369 additions and 75 deletions.
229 changes: 166 additions & 63 deletions include/boost/pfr/detail/fields_count.hpp

Large diffs are not rendered by default.

4 changes: 4 additions & 0 deletions test/core/Jamfile.v2
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,10 @@ test-suite pfr_tests
[ run ../../example/sample_printing.cpp : : : : auto_engine_sample_printing ]
[ run ../../example/get.cpp : : : : auto_engine_get ]
[ run ../../example/quick_examples.cpp : : : : auto_engine_quick ]

[ compile-fail fields_count_on_incomplete_type.cpp : <define>BOOST_PFR_RUN_TEST_ON=void : fields_count_on_incomplete_type_void ]
[ compile-fail fields_count_on_incomplete_type.cpp : <define>BOOST_PFR_RUN_TEST_ON="void()" : fields_count_on_incomplete_type_function ]
[ compile-fail fields_count_on_incomplete_type.cpp : <define>BOOST_PFR_RUN_TEST_ON="struct Foo" : fields_count_on_incomplete_type_struct ]
;

local BLACKLIST_TESTS_FOR_LOOPHOLE =
Expand Down
15 changes: 15 additions & 0 deletions test/core/compile-fail/constructible_0_or_more_args.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
// Copyright (c) 2023-2024 Antony Polukhin
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)

#include <boost/pfr/tuple_size.hpp>

struct A {
template <typename... Args>
explicit A(Args&&...) {}
};

int main() {
(void)boost::pfr::tuple_size<A>::value; // Must be a compile time error
}
15 changes: 15 additions & 0 deletions test/core/compile-fail/constructible_1_or_more_args.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
// Copyright (c) 2023-2024 Antony Polukhin
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)

#include <boost/pfr/tuple_size.hpp>

struct A {
template <typename Arg0, typename... Args>
explicit A(Arg0&&, Args&&...) {}
};

int main() {
(void)boost::pfr::tuple_size<A>::value; // Must be a compile time error
}
10 changes: 10 additions & 0 deletions test/core/fields_count_on_incomplete_type.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
// Copyright (c) 2023-2024 Antony Polukhin
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)

#include <boost/pfr/detail/fields_count.hpp>

int main() {
return static_cast<int>(boost::pfr::detail::fields_count<BOOST_PFR_RUN_TEST_ON>());
}
85 changes: 75 additions & 10 deletions test/core/run/bitfields_count.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -5,17 +5,82 @@

#include <boost/pfr/tuple_size.hpp>

struct bf {
unsigned int i1: 1;
unsigned int i2: 1;
unsigned int i3: 1;
unsigned int i4: 1;
unsigned int i5: 1;
unsigned int i6: 1;
#include <cstdint>

struct bf7 {
uint8_t b0 : 1;
uint8_t b1 : 1;
uint8_t b2 : 1;
uint8_t b3 : 1;
uint8_t b4 : 1;
uint8_t b5 : 1;
uint8_t b6 : 1;
};
static_assert(sizeof(bf7) == 1, "");

int main() {
static_assert(boost::pfr::tuple_size<bf>::value == 6, "");
}
struct bf8 {
uint8_t b0 : 1;
uint8_t b1 : 1;
uint8_t b2 : 1;
uint8_t b3 : 1;
uint8_t b4 : 1;
uint8_t b5 : 1;
uint8_t b6 : 1;
uint8_t b7 : 1;
};
static_assert(sizeof(bf8) == 1, "");

struct bf16 {
uint8_t b0 : 1;
uint8_t b1 : 1;
uint8_t b2 : 1;
uint8_t b3 : 1;
uint8_t b4 : 1;
uint8_t b5 : 1;
uint8_t b6 : 1;
uint8_t b7 : 1;
uint8_t b8 : 1;
uint8_t b9 : 1;
uint8_t b10 : 1;
uint8_t b11 : 1;
uint8_t b12 : 1;
uint8_t b13 : 1;
uint8_t b14 : 1;
uint8_t b15 : 1;
};
static_assert(sizeof(bf16) == 2, "");

struct bf24 {
uint8_t b0 : 1;
uint8_t b1 : 1;
uint8_t b2 : 1;
uint8_t b3 : 1;
uint8_t b4 : 1;
uint8_t b5 : 1;
uint8_t b6 : 1;
uint8_t b7 : 1;
uint8_t b8 : 1;
uint8_t b9 : 1;
uint8_t b10 : 1;
uint8_t b11 : 1;
uint8_t b12 : 1;
uint8_t b13 : 1;
uint8_t b14 : 1;
uint8_t b15 : 1;
uint8_t b16 : 1;
uint8_t b17 : 1;
uint8_t b18 : 1;
uint8_t b19 : 1;
uint8_t b20 : 1;
uint8_t b21 : 1;
uint8_t b22 : 1;
uint8_t b23 : 1;
};
static_assert(sizeof(bf24) == 3, "");

int main() {
static_assert(boost::pfr::tuple_size_v<bf7> == 7, "");
static_assert(boost::pfr::tuple_size_v<bf8> == 8, "");
static_assert(boost::pfr::tuple_size_v<bf16> == 16, "");
static_assert(boost::pfr::tuple_size_v<bf24> == 24, "");
}
63 changes: 63 additions & 0 deletions test/core/run/huge_count.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
// Copyright (c) 2024 Antony Polukhin
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)

#include <boost/pfr/tuple_size.hpp>

#include <climits>
#include <cstdint>

#if defined(__clang__)
# if SIZE_MAX > (1ULL << 32) - 1
# define ARRAY_MAX (SIZE_MAX >> 3)
# else
# define ARRAY_MAX SIZE_MAX
# endif
# define OBJECT_MAX SIZE_MAX
#elif defined(__GNUC__)
# define ARRAY_MAX INT_MAX
# define OBJECT_MAX (SIZE_MAX >> 1)
#elif defined(_MSC_VER)
# define ARRAY_MAX INT_MAX
# define OBJECT_MAX UINT_MAX
#else // Let's play it safe
# define ARRAY_MAX INT_MAX
# define OBJECT_MAX INT_MAX
#endif

#pragma pack(1)
struct A {
char x[ARRAY_MAX <= (OBJECT_MAX >> 3) ? ARRAY_MAX : OBJECT_MAX >> 3];
};

struct B {
A a;
A b;
A c;
A d;
A e;
A f;
A g;
A h;
};

struct C {
A& a;
A b;
A c;
A d;
A e;
A f;
A g;
A h;
};
#pragma pack()

int main() {
#ifndef _MSC_VER
static_assert(boost::pfr::tuple_size_v<char[ARRAY_MAX]> == ARRAY_MAX, "");
#endif
static_assert(boost::pfr::tuple_size_v<B> == 8, "");
static_assert(boost::pfr::tuple_size_v<C> == 8, "");
}
Loading

0 comments on commit ff415a2

Please sign in to comment.