current commit
261004.7 · deps: update V8 to 15.2.124.38
// Copyright 2023 the V8 project authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#ifndef V8_HEAP_BASE_BYTES_H_
#define V8_HEAP_BASE_BYTES_H_

#include <algorithm>
#include <cstddef>
#include <limits>
#include <optional>

#include "src/base/platform/time.h"
#include "src/base/ring-buffer.h"
#include "src/base/strong-alias.h"

namespace heap::base {

struct BytesAndDuration final {
  constexpr BytesAndDuration() = default;
  constexpr BytesAndDuration(size_t bytes, v8::base::TimeDelta duration)
      : bytes(bytes), duration(duration) {}

  size_t bytes = 0;
  v8::base::TimeDelta duration;
};

using BytesAndDurationBuffer = v8::base::RingBuffer<BytesAndDuration>;

// Returns the average speed of events recorded in `buffer` including an
// `initial` event in Bytes/ms. If provided, `selected_duration` will bound the
// events considered (which uses the order of events in
// `BytesAndDurationBuffer`). The bounds are in Bytes/ms and can be used to
// bound non-zero speeds.
inline std::optional<double> AverageSpeed(
    const BytesAndDurationBuffer& buffer, const BytesAndDuration& initial,
    std::optional<v8::base::TimeDelta> selected_duration,
    size_t min_non_empty_speed = 0,
    size_t max_speed = std::numeric_limits<size_t>::max()) {
  const BytesAndDuration sum = buffer.Reduce(
      [selected_duration](const BytesAndDuration& a,
                          const BytesAndDuration& b) {
        if (selected_duration.has_value() &&
            a.duration >= selected_duration.value()) {
          return a;
        }
        return BytesAndDuration(a.bytes + b.bytes, a.duration + b.duration);
      },
      initial);
  const auto duration = sum.duration;
  if (duration.IsZero()) {
    return std::nullopt;
  }
  return std::max(
      std::min(static_cast<double>(sum.bytes) / duration.InMillisecondsF(),
               static_cast<double>(max_speed)),
      static_cast<double>(min_non_empty_speed));
}

class SmoothedBytesAndDuration {
 public:
  explicit SmoothedBytesAndDuration(v8::base::TimeDelta increase_decay,
                                    v8::base::TimeDelta decrease_decay)
      : increase_decay_(increase_decay), decrease_decay_(decrease_decay) {}

  void Update(BytesAndDuration bytes_and_duration) {
    if (bytes_and_duration.duration.IsZero()) {
      return;
    }
    const double new_throughput = bytes_and_duration.bytes /
                                  bytes_and_duration.duration.InMillisecondsF();

    const auto decay =
        new_throughput >= throughput_ ? increase_decay_ : decrease_decay_;
    throughput_ = new_throughput + Decay(throughput_ - new_throughput,
                                         bytes_and_duration.duration, decay);
  }
  // Return throughput of memory (in bytes) over time (in millis).
  double GetThroughput() const { return throughput_; }

  // Returns throughput decayed as if `delay` passed. The new allocation
  // throughput is assumed to be 0, so this only tests decay of the current
  // throughput. This is why we use `decrease_decay_` here.
  double GetThroughputForTesting(v8::base::TimeDelta delay) const {
    return Decay(throughput_, delay, decrease_decay_);
  }

 private:
  double Decay(double throughput, v8::base::TimeDelta delay,
               v8::base::TimeDelta decay) const {
    return throughput *
           exp2(-delay.InMillisecondsF() / decay.InMillisecondsF());
  }

  double throughput_ = 0.0;

  // Possibly distinct decay rates for increasing/decreasing throughput. This
  // allows us to react e.g. faster to a growing allocation rate relative to a
  // decreasing one.
  const v8::base::TimeDelta increase_decay_;
  const v8::base::TimeDelta decrease_decay_;
};

using ByteSize = v8::base::StrongAlias<struct ByteSizeTag, size_t>;

}  // namespace heap::base

#endif  // V8_HEAP_BASE_BYTES_H_