Lazily initialize std::ostream in LogMessage::OstreamView. Every `LogMessage` instance previously constructed and destroyed a `std::ostream` member (`LogMessageData::manipulated`) inside `LogMessageData`, invoking `std::basic_ios::init` and `std::ios_base::~ios_base` on every log statement even when the statement only formatted strings or literals via `CopyToEncodedBuffer` or was suppressed before formatting. Change `LogMessageData::manipulated` from `std::ostream` to `std::optional<std::ostream>` and defer its initialization to `LogMessage::OstreamView`, constructing the stream with the legacy `showbase | boolalpha` formatting flags only when `OstreamView` is entered. This avoids `std::ios_base` initialization and teardown on string-only and suppressed log paths and prepares `LogMessageData` for reuse across log statements. Also add `BM_EnabledLogToSinkString` and `BM_EnabledLogToSinkInt` multi-threaded benchmarks to `log_benchmark.cc`. PiperOrigin-RevId: 1004554083
diff --git a/absl/log/internal/log_message.cc b/absl/log/internal/log_message.cc index e56ef25..f15d41a 100644 --- a/absl/log/internal/log_message.cc +++ b/absl/log/internal/log_message.cc
@@ -25,6 +25,7 @@ #include <atomic> #include <ios> #include <memory> +#include <optional> #include <ostream> #include <string> #include <string_view> @@ -169,7 +170,8 @@ // non-sink targets (e.g. stderr, log files). bool extra_sinks_only; - std::ostream manipulated; // ostream with IO manipulators applied + // Lazily initialized ostream with IO manipulators applied. + std::optional<std::ostream> manipulated; // A `logging.proto.Event` proto message is built into `encoded_buf`. std::array<char, kLogMessageBufferSize> encoded_buf; @@ -200,12 +202,10 @@ void FinalizeEncodingAndFormat(); }; -LogMessage::LogMessageData::LogMessageData(absl::string_view file, - int line, absl::LogSeverity severity, +LogMessage::LogMessageData::LogMessageData(absl::string_view file, int line, + absl::LogSeverity severity, absl::Time timestamp) - : extra_sinks_only(false), manipulated(nullptr) { - // Legacy defaults for LOG's ostream: - manipulated.setf(std::ios_base::showbase | std::ios_base::boolalpha); + : extra_sinks_only(false) { entry.full_filename_ = file; entry.base_filename_ = Basename(file); entry.line_ = line; @@ -437,14 +437,16 @@ return *this; } -LogMessage& LogMessage::operator<<(std::ostream& (*m)(std::ostream& os)) { +LogMessage& LogMessage::operator<<( + std::ostream& (*absl_nonnull m)(std::ostream& os)) { OstreamView view(*data_); - data_->manipulated << m; + view.stream() << m; return *this; } -LogMessage& LogMessage::operator<<(std::ios_base& (*m)(std::ios_base& os)) { +LogMessage& LogMessage::operator<<( + std::ios_base& (*absl_nonnull m)(std::ios_base& os)) { OstreamView view(*data_); - data_->manipulated << m; + view.stream() << m; return *this; } // NOLINTBEGIN(runtime/int) @@ -515,11 +517,16 @@ &encoded_remaining_copy_); setp(encoded_remaining_copy_.data(), encoded_remaining_copy_.data() + encoded_remaining_copy_.size()); - data_.manipulated.rdbuf(this); + if (!data_.manipulated.has_value()) { + data_.manipulated.emplace(nullptr); + // Legacy defaults for LOG's ostream: + data_.manipulated->setf(std::ios_base::showbase | std::ios_base::boolalpha); + } + data_.manipulated->rdbuf(this); } LogMessage::OstreamView::~OstreamView() { - data_.manipulated.rdbuf(nullptr); + data_.manipulated->rdbuf(nullptr); if (!string_start_.data()) { // The second field header didn't fit. Whether the first one did or not, we // shouldn't commit `encoded_remaining_copy_`, and we also need to zero the @@ -536,7 +543,7 @@ data_.encoded_remaining() = encoded_remaining_copy_; } -std::ostream& LogMessage::OstreamView::stream() { return data_.manipulated; } +std::ostream& LogMessage::OstreamView::stream() { return *data_.manipulated; } bool LogMessage::IsFatal() const { return data_->entry.log_severity() == absl::LogSeverity::kFatal &&
diff --git a/absl/log/log_benchmark.cc b/absl/log/log_benchmark.cc index 50a64ea..878001b 100644 --- a/absl/log/log_benchmark.cc +++ b/absl/log/log_benchmark.cc
@@ -95,6 +95,29 @@ } BENCHMARK(BM_EnabledLogOverhead); +class LocalNullLogSink : public absl::LogSink { + public: + void Send(const absl::LogEntry&) override {} +}; + +static void BM_EnabledLogToSinkString(benchmark::State& state) { + absl::SetMinLogLevel(absl::LogSeverityAtLeast::kInfo); + LocalNullLogSink sink; + for (auto _ : state) { + LOG(INFO).ToSinkOnly(&sink) << "hello world"; + } +} +BENCHMARK(BM_EnabledLogToSinkString)->ThreadRange(1, 16); + +static void BM_EnabledLogToSinkInt(benchmark::State& state) { + absl::SetMinLogLevel(absl::LogSeverityAtLeast::kInfo); + LocalNullLogSink sink; + for (auto _ : state) { + LOG(INFO).ToSinkOnly(&sink) << "value: " << 42; + } +} +BENCHMARK(BM_EnabledLogToSinkInt)->ThreadRange(1, 16); + static void BM_VlogIsOnOverhead(benchmark::State& state) { // It would make sense to do this only when state.thread_index == 0, // but thread_index is an int on some platforms (e.g. Android) and a