[experiments] Optimize `IsExperimentEnabled` (#37057)

Brings measured CPU for this function from 1.95ns down to 0.4ns.

Closes #37057

COPYBARA_INTEGRATE_REVIEW=https://github.com/grpc/grpc/pull/37057 from ctiller:xp 99a423016e
PiperOrigin-RevId: 647389088
pull/37080/head
Craig Tiller 5 months ago committed by Copybara-Service
parent 0b53cc6bce
commit a0f45af368
  1. 32
      src/core/lib/experiments/config.cc
  2. 56
      src/core/lib/experiments/config.h
  3. 50
      src/core/lib/experiments/experiments.h
  4. 2
      test/core/experiments/bm_experiments.cc
  5. 8
      test/core/experiments/fixtures/experiments.h
  6. 2
      tools/codegen/core/experiments_compiler.py

@ -170,6 +170,7 @@ Experiments& ExperimentsSingleton() {
} // namespace } // namespace
void TestOnlyReloadExperimentsFromConfigVariables() { void TestOnlyReloadExperimentsFromConfigVariables() {
ExperimentFlags::TestOnlyClear();
ExperimentsSingleton() = LoadExperimentsFromConfigVariable(); ExperimentsSingleton() = LoadExperimentsFromConfigVariable();
PrintExperimentsList(); PrintExperimentsList();
} }
@ -180,8 +181,35 @@ void LoadTestOnlyExperimentsFromMetadata(
new TestExperiments(experiment_metadata, num_experiments); new TestExperiments(experiment_metadata, num_experiments);
} }
bool IsExperimentEnabled(size_t experiment_id) { std::atomic<uint64_t>
return ExperimentsSingleton().enabled[experiment_id]; ExperimentFlags::experiment_flags_[kNumExperimentFlagsWords];
bool ExperimentFlags::LoadFlagsAndCheck(size_t experiment_id) {
static_assert(kNumExperiments < kNumExperimentFlagsWords * kFlagsPerWord,
"kNumExperiments must be less than "
"kNumExperimentFlagsWords*kFlagsPerWord; if this fails then "
"make kNumExperimentFlagsWords bigger.");
const auto& experiments = ExperimentsSingleton();
uint64_t building[kNumExperimentFlagsWords];
for (size_t i = 0; i < kNumExperimentFlagsWords; i++) {
building[i] = kLoadedFlag;
}
for (size_t i = 0; i < kNumExperiments; i++) {
if (!experiments.enabled[i]) continue;
auto bit = i % kFlagsPerWord;
auto word = i / kFlagsPerWord;
building[word] |= 1ull << bit;
}
for (size_t i = 0; i < kNumExperimentFlagsWords; i++) {
experiment_flags_[i].store(building[i], std::memory_order_relaxed);
}
return experiments.enabled[experiment_id];
}
void ExperimentFlags::TestOnlyClear() {
for (size_t i = 0; i < kNumExperimentFlagsWords; i++) {
experiment_flags_[i].store(0, std::memory_order_relaxed);
}
} }
bool IsExperimentEnabledInConfiguration(size_t experiment_id) { bool IsExperimentEnabledInConfiguration(size_t experiment_id) {

@ -18,6 +18,8 @@
#include <stddef.h> #include <stddef.h>
#include <stdint.h> #include <stdint.h>
#include <atomic>
#include "absl/functional/any_invocable.h" #include "absl/functional/any_invocable.h"
#include "absl/strings/string_view.h" #include "absl/strings/string_view.h"
@ -38,16 +40,68 @@ struct ExperimentMetadata {
}; };
#ifndef GRPC_EXPERIMENTS_ARE_FINAL #ifndef GRPC_EXPERIMENTS_ARE_FINAL
class ExperimentFlags {
public:
GPR_ATTRIBUTE_ALWAYS_INLINE_FUNCTION static bool IsExperimentEnabled(
size_t experiment_id) {
auto bit = experiment_id % kFlagsPerWord;
auto word = experiment_id / kFlagsPerWord;
auto cur = experiment_flags_[word].load(std::memory_order_relaxed);
if (cur & (1ull << bit)) return true;
if (cur & kLoadedFlag) return false;
return LoadFlagsAndCheck(experiment_id);
}
template <size_t kExperimentId>
GPR_ATTRIBUTE_ALWAYS_INLINE_FUNCTION static bool IsExperimentEnabled() {
auto bit = kExperimentId % kFlagsPerWord;
auto word = kExperimentId / kFlagsPerWord;
auto cur = experiment_flags_[word].load(std::memory_order_relaxed);
if (cur & (1ull << bit)) return true;
if (cur & kLoadedFlag) return false;
return LoadFlagsAndCheck(kExperimentId);
}
static void TestOnlyClear();
private:
static bool LoadFlagsAndCheck(size_t experiment_id);
// We layout experiment flags in groups of 63... each 64 bit word contains
// 63 enablement flags (one per experiment), and the high bit which indicates
// whether the flags have been loaded from the configuration.
// Consequently, with one load, we can tell if the experiment is definitely
// enabled (the bit is set), or definitely disabled (the bit is clear, and the
// loaded flag is set), or if we need to load the flags and re-check.
static constexpr size_t kNumExperimentFlagsWords = 8;
static constexpr size_t kFlagsPerWord = 63;
static constexpr uint64_t kLoadedFlag = 0x8000000000000000ull;
static std::atomic<uint64_t> experiment_flags_[kNumExperimentFlagsWords];
};
// Return true if experiment \a experiment_id is enabled. // Return true if experiment \a experiment_id is enabled.
// Experiments are numbered by their order in the g_experiment_metadata array // Experiments are numbered by their order in the g_experiment_metadata array
// declared in experiments.h. // declared in experiments.h.
bool IsExperimentEnabled(size_t experiment_id); inline bool IsExperimentEnabled(size_t experiment_id) {
return ExperimentFlags::IsExperimentEnabled(experiment_id);
}
template <size_t kExperimentId>
inline bool IsExperimentEnabled() {
return ExperimentFlags::IsExperimentEnabled<kExperimentId>();
}
// Given a test experiment id, returns true if the test experiment is enabled. // Given a test experiment id, returns true if the test experiment is enabled.
// Test experiments can be loaded using the LoadTestOnlyExperimentsFromMetadata // Test experiments can be loaded using the LoadTestOnlyExperimentsFromMetadata
// method. // method.
bool IsTestExperimentEnabled(size_t experiment_id); bool IsTestExperimentEnabled(size_t experiment_id);
template <size_t kExperimentId>
inline bool IsTestExperimentEnabled() {
return IsTestExperimentEnabled(kExperimentId);
}
// Slow check for if a named experiment is enabled. // Slow check for if a named experiment is enabled.
// Parses the configuration and looks up the experiment in that, so it does not // Parses the configuration and looks up the experiment in that, so it does not
// affect any global state, but it does require parsing the configuration every // affect any global state, but it does require parsing the configuration every

@ -193,103 +193,103 @@ enum ExperimentIds {
}; };
#define GRPC_EXPERIMENT_IS_INCLUDED_CALL_STATUS_OVERRIDE_ON_CANCELLATION #define GRPC_EXPERIMENT_IS_INCLUDED_CALL_STATUS_OVERRIDE_ON_CANCELLATION
inline bool IsCallStatusOverrideOnCancellationEnabled() { inline bool IsCallStatusOverrideOnCancellationEnabled() {
return IsExperimentEnabled(kExperimentIdCallStatusOverrideOnCancellation); return IsExperimentEnabled<kExperimentIdCallStatusOverrideOnCancellation>();
} }
#define GRPC_EXPERIMENT_IS_INCLUDED_CANARY_CLIENT_PRIVACY #define GRPC_EXPERIMENT_IS_INCLUDED_CANARY_CLIENT_PRIVACY
inline bool IsCanaryClientPrivacyEnabled() { inline bool IsCanaryClientPrivacyEnabled() {
return IsExperimentEnabled(kExperimentIdCanaryClientPrivacy); return IsExperimentEnabled<kExperimentIdCanaryClientPrivacy>();
} }
#define GRPC_EXPERIMENT_IS_INCLUDED_CLIENT_PRIVACY #define GRPC_EXPERIMENT_IS_INCLUDED_CLIENT_PRIVACY
inline bool IsClientPrivacyEnabled() { inline bool IsClientPrivacyEnabled() {
return IsExperimentEnabled(kExperimentIdClientPrivacy); return IsExperimentEnabled<kExperimentIdClientPrivacy>();
} }
#define GRPC_EXPERIMENT_IS_INCLUDED_EVENT_ENGINE_CLIENT #define GRPC_EXPERIMENT_IS_INCLUDED_EVENT_ENGINE_CLIENT
inline bool IsEventEngineClientEnabled() { inline bool IsEventEngineClientEnabled() {
return IsExperimentEnabled(kExperimentIdEventEngineClient); return IsExperimentEnabled<kExperimentIdEventEngineClient>();
} }
#define GRPC_EXPERIMENT_IS_INCLUDED_EVENT_ENGINE_DNS #define GRPC_EXPERIMENT_IS_INCLUDED_EVENT_ENGINE_DNS
inline bool IsEventEngineDnsEnabled() { inline bool IsEventEngineDnsEnabled() {
return IsExperimentEnabled(kExperimentIdEventEngineDns); return IsExperimentEnabled<kExperimentIdEventEngineDns>();
} }
#define GRPC_EXPERIMENT_IS_INCLUDED_EVENT_ENGINE_LISTENER #define GRPC_EXPERIMENT_IS_INCLUDED_EVENT_ENGINE_LISTENER
inline bool IsEventEngineListenerEnabled() { inline bool IsEventEngineListenerEnabled() {
return IsExperimentEnabled(kExperimentIdEventEngineListener); return IsExperimentEnabled<kExperimentIdEventEngineListener>();
} }
#define GRPC_EXPERIMENT_IS_INCLUDED_FREE_LARGE_ALLOCATOR #define GRPC_EXPERIMENT_IS_INCLUDED_FREE_LARGE_ALLOCATOR
inline bool IsFreeLargeAllocatorEnabled() { inline bool IsFreeLargeAllocatorEnabled() {
return IsExperimentEnabled(kExperimentIdFreeLargeAllocator); return IsExperimentEnabled<kExperimentIdFreeLargeAllocator>();
} }
#define GRPC_EXPERIMENT_IS_INCLUDED_HTTP2_STATS_FIX #define GRPC_EXPERIMENT_IS_INCLUDED_HTTP2_STATS_FIX
inline bool IsHttp2StatsFixEnabled() { inline bool IsHttp2StatsFixEnabled() {
return IsExperimentEnabled(kExperimentIdHttp2StatsFix); return IsExperimentEnabled<kExperimentIdHttp2StatsFix>();
} }
#define GRPC_EXPERIMENT_IS_INCLUDED_KEEPALIVE_FIX #define GRPC_EXPERIMENT_IS_INCLUDED_KEEPALIVE_FIX
inline bool IsKeepaliveFixEnabled() { inline bool IsKeepaliveFixEnabled() {
return IsExperimentEnabled(kExperimentIdKeepaliveFix); return IsExperimentEnabled<kExperimentIdKeepaliveFix>();
} }
#define GRPC_EXPERIMENT_IS_INCLUDED_KEEPALIVE_SERVER_FIX #define GRPC_EXPERIMENT_IS_INCLUDED_KEEPALIVE_SERVER_FIX
inline bool IsKeepaliveServerFixEnabled() { inline bool IsKeepaliveServerFixEnabled() {
return IsExperimentEnabled(kExperimentIdKeepaliveServerFix); return IsExperimentEnabled<kExperimentIdKeepaliveServerFix>();
} }
#define GRPC_EXPERIMENT_IS_INCLUDED_MAX_PINGS_WO_DATA_THROTTLE #define GRPC_EXPERIMENT_IS_INCLUDED_MAX_PINGS_WO_DATA_THROTTLE
inline bool IsMaxPingsWoDataThrottleEnabled() { inline bool IsMaxPingsWoDataThrottleEnabled() {
return IsExperimentEnabled(kExperimentIdMaxPingsWoDataThrottle); return IsExperimentEnabled<kExperimentIdMaxPingsWoDataThrottle>();
} }
#define GRPC_EXPERIMENT_IS_INCLUDED_MONITORING_EXPERIMENT #define GRPC_EXPERIMENT_IS_INCLUDED_MONITORING_EXPERIMENT
inline bool IsMonitoringExperimentEnabled() { inline bool IsMonitoringExperimentEnabled() {
return IsExperimentEnabled(kExperimentIdMonitoringExperiment); return IsExperimentEnabled<kExperimentIdMonitoringExperiment>();
} }
#define GRPC_EXPERIMENT_IS_INCLUDED_MULTIPING #define GRPC_EXPERIMENT_IS_INCLUDED_MULTIPING
inline bool IsMultipingEnabled() { inline bool IsMultipingEnabled() {
return IsExperimentEnabled(kExperimentIdMultiping); return IsExperimentEnabled<kExperimentIdMultiping>();
} }
#define GRPC_EXPERIMENT_IS_INCLUDED_PEER_STATE_BASED_FRAMING #define GRPC_EXPERIMENT_IS_INCLUDED_PEER_STATE_BASED_FRAMING
inline bool IsPeerStateBasedFramingEnabled() { inline bool IsPeerStateBasedFramingEnabled() {
return IsExperimentEnabled(kExperimentIdPeerStateBasedFraming); return IsExperimentEnabled<kExperimentIdPeerStateBasedFraming>();
} }
#define GRPC_EXPERIMENT_IS_INCLUDED_PICK_FIRST_NEW #define GRPC_EXPERIMENT_IS_INCLUDED_PICK_FIRST_NEW
inline bool IsPickFirstNewEnabled() { inline bool IsPickFirstNewEnabled() {
return IsExperimentEnabled(kExperimentIdPickFirstNew); return IsExperimentEnabled<kExperimentIdPickFirstNew>();
} }
#define GRPC_EXPERIMENT_IS_INCLUDED_PROMISE_BASED_INPROC_TRANSPORT #define GRPC_EXPERIMENT_IS_INCLUDED_PROMISE_BASED_INPROC_TRANSPORT
inline bool IsPromiseBasedInprocTransportEnabled() { inline bool IsPromiseBasedInprocTransportEnabled() {
return IsExperimentEnabled(kExperimentIdPromiseBasedInprocTransport); return IsExperimentEnabled<kExperimentIdPromiseBasedInprocTransport>();
} }
#define GRPC_EXPERIMENT_IS_INCLUDED_RSTPIT #define GRPC_EXPERIMENT_IS_INCLUDED_RSTPIT
inline bool IsRstpitEnabled() { inline bool IsRstpitEnabled() {
return IsExperimentEnabled(kExperimentIdRstpit); return IsExperimentEnabled<kExperimentIdRstpit>();
} }
#define GRPC_EXPERIMENT_IS_INCLUDED_SCHEDULE_CANCELLATION_OVER_WRITE #define GRPC_EXPERIMENT_IS_INCLUDED_SCHEDULE_CANCELLATION_OVER_WRITE
inline bool IsScheduleCancellationOverWriteEnabled() { inline bool IsScheduleCancellationOverWriteEnabled() {
return IsExperimentEnabled(kExperimentIdScheduleCancellationOverWrite); return IsExperimentEnabled<kExperimentIdScheduleCancellationOverWrite>();
} }
#define GRPC_EXPERIMENT_IS_INCLUDED_SERVER_PRIVACY #define GRPC_EXPERIMENT_IS_INCLUDED_SERVER_PRIVACY
inline bool IsServerPrivacyEnabled() { inline bool IsServerPrivacyEnabled() {
return IsExperimentEnabled(kExperimentIdServerPrivacy); return IsExperimentEnabled<kExperimentIdServerPrivacy>();
} }
#define GRPC_EXPERIMENT_IS_INCLUDED_TCP_FRAME_SIZE_TUNING #define GRPC_EXPERIMENT_IS_INCLUDED_TCP_FRAME_SIZE_TUNING
inline bool IsTcpFrameSizeTuningEnabled() { inline bool IsTcpFrameSizeTuningEnabled() {
return IsExperimentEnabled(kExperimentIdTcpFrameSizeTuning); return IsExperimentEnabled<kExperimentIdTcpFrameSizeTuning>();
} }
#define GRPC_EXPERIMENT_IS_INCLUDED_TCP_RCV_LOWAT #define GRPC_EXPERIMENT_IS_INCLUDED_TCP_RCV_LOWAT
inline bool IsTcpRcvLowatEnabled() { inline bool IsTcpRcvLowatEnabled() {
return IsExperimentEnabled(kExperimentIdTcpRcvLowat); return IsExperimentEnabled<kExperimentIdTcpRcvLowat>();
} }
#define GRPC_EXPERIMENT_IS_INCLUDED_TRACE_RECORD_CALLOPS #define GRPC_EXPERIMENT_IS_INCLUDED_TRACE_RECORD_CALLOPS
inline bool IsTraceRecordCallopsEnabled() { inline bool IsTraceRecordCallopsEnabled() {
return IsExperimentEnabled(kExperimentIdTraceRecordCallops); return IsExperimentEnabled<kExperimentIdTraceRecordCallops>();
} }
#define GRPC_EXPERIMENT_IS_INCLUDED_UNCONSTRAINED_MAX_QUOTA_BUFFER_SIZE #define GRPC_EXPERIMENT_IS_INCLUDED_UNCONSTRAINED_MAX_QUOTA_BUFFER_SIZE
inline bool IsUnconstrainedMaxQuotaBufferSizeEnabled() { inline bool IsUnconstrainedMaxQuotaBufferSizeEnabled() {
return IsExperimentEnabled(kExperimentIdUnconstrainedMaxQuotaBufferSize); return IsExperimentEnabled<kExperimentIdUnconstrainedMaxQuotaBufferSize>();
} }
#define GRPC_EXPERIMENT_IS_INCLUDED_WORK_SERIALIZER_CLEARS_TIME_CACHE #define GRPC_EXPERIMENT_IS_INCLUDED_WORK_SERIALIZER_CLEARS_TIME_CACHE
inline bool IsWorkSerializerClearsTimeCacheEnabled() { inline bool IsWorkSerializerClearsTimeCacheEnabled() {
return IsExperimentEnabled(kExperimentIdWorkSerializerClearsTimeCache); return IsExperimentEnabled<kExperimentIdWorkSerializerClearsTimeCache>();
} }
#define GRPC_EXPERIMENT_IS_INCLUDED_WORK_SERIALIZER_DISPATCH #define GRPC_EXPERIMENT_IS_INCLUDED_WORK_SERIALIZER_DISPATCH
inline bool IsWorkSerializerDispatchEnabled() { inline bool IsWorkSerializerDispatchEnabled() {
return IsExperimentEnabled(kExperimentIdWorkSerializerDispatch); return IsExperimentEnabled<kExperimentIdWorkSerializerDispatch>();
} }
extern const ExperimentMetadata g_experiment_metadata[kNumExperiments]; extern const ExperimentMetadata g_experiment_metadata[kNumExperiments];

@ -18,7 +18,7 @@
static void BM_IsExperimentEnabled(benchmark::State& state) { static void BM_IsExperimentEnabled(benchmark::State& state) {
for (auto _ : state) { for (auto _ : state) {
grpc_core::IsMonitoringExperimentEnabled(); benchmark::DoNotOptimize(grpc_core::IsMonitoringExperimentEnabled());
} }
} }
BENCHMARK(BM_IsExperimentEnabled); BENCHMARK(BM_IsExperimentEnabled);

@ -133,19 +133,19 @@ enum ExperimentIds {
}; };
#define GRPC_EXPERIMENT_IS_INCLUDED_TEST_EXPERIMENT_1 #define GRPC_EXPERIMENT_IS_INCLUDED_TEST_EXPERIMENT_1
inline bool IsTestExperiment1Enabled() { inline bool IsTestExperiment1Enabled() {
return IsTestExperimentEnabled(kExperimentIdTestExperiment1); return IsTestExperimentEnabled<kExperimentIdTestExperiment1>();
} }
#define GRPC_EXPERIMENT_IS_INCLUDED_TEST_EXPERIMENT_2 #define GRPC_EXPERIMENT_IS_INCLUDED_TEST_EXPERIMENT_2
inline bool IsTestExperiment2Enabled() { inline bool IsTestExperiment2Enabled() {
return IsTestExperimentEnabled(kExperimentIdTestExperiment2); return IsTestExperimentEnabled<kExperimentIdTestExperiment2>();
} }
#define GRPC_EXPERIMENT_IS_INCLUDED_TEST_EXPERIMENT_3 #define GRPC_EXPERIMENT_IS_INCLUDED_TEST_EXPERIMENT_3
inline bool IsTestExperiment3Enabled() { inline bool IsTestExperiment3Enabled() {
return IsTestExperimentEnabled(kExperimentIdTestExperiment3); return IsTestExperimentEnabled<kExperimentIdTestExperiment3>();
} }
#define GRPC_EXPERIMENT_IS_INCLUDED_TEST_EXPERIMENT_4 #define GRPC_EXPERIMENT_IS_INCLUDED_TEST_EXPERIMENT_4
inline bool IsTestExperiment4Enabled() { inline bool IsTestExperiment4Enabled() {
return IsTestExperimentEnabled(kExperimentIdTestExperiment4); return IsTestExperimentEnabled<kExperimentIdTestExperiment4>();
} }
extern const ExperimentMetadata g_test_experiment_metadata[kNumTestExperiments]; extern const ExperimentMetadata g_test_experiment_metadata[kNumTestExperiments];

@ -456,7 +456,7 @@ class ExperimentsCompiler(object):
) )
print( print(
"inline bool Is%sEnabled() { return" "inline bool Is%sEnabled() { return"
" Is%sExperimentEnabled(kExperimentId%s); }" " Is%sExperimentEnabled<kExperimentId%s>(); }"
% ( % (
SnakeToPascal(exp.name), SnakeToPascal(exp.name),
"Test" if mode == "test" else "", "Test" if mode == "test" else "",

Loading…
Cancel
Save