// Copyright 2019 Google LLC
// SPDX-License-Identifier: Apache-2.0
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#include "third_party/highway/hwy/timer.h"
#include <stdlib.h>
#include <chrono>
// NOLINT
#include <ratio>
// NOLINT
#include "third_party/highway/hwy/base.h"
#include "third_party/highway/hwy/robust_statistics.h"
#include "third_party/highway/hwy/x86_cpuid.h"
namespace hwy {
#if HWY_ARCH_X86
namespace x86 {
static bool HasRDTSCP() {
uint32_t abcd[
4];
Cpuid(
0x80000001U,
0, abcd);
// Extended feature flags
if ((abcd[
3] & (
1u <<
27)) ==
0)
return false;
// RDTSCP
Cpuid(
0x80000007U,
0, abcd);
if ((abcd[
3] & (
1u <<
8)) ==
0) {
HWY_WARN(
"TSC not constant/invariant, may vary frequency or jump.");
}
return true;
}
}
// namespace x86
#endif // HWY_ARCH_X86
// Measures the actual current frequency of Ticks. We cannot rely on the nominal
// frequency encoded in x86 GetCpuString because it is misleading on M1 Rosetta,
// and not reported by AMD. CPUID 0x15 is also not yet widely supported. Also
// used on RISC-V and aarch64.
static HWY_MAYBE_UNUSED
double MeasureNominalClockRate() {
double max_ticks_per_sec =
0.
0;
// Arbitrary, enough to ignore 2 outliers without excessive init time.
for (int rep =
0; rep <
3; ++rep) {
auto time0 = std::chrono::steady_clock::now();
using Time = decltype(time0);
const timer::Ticks ticks0 = timer::Start();
const Time time_min = time0 + std::chrono::milliseconds(
10);
Time time1;
timer::Ticks ticks1;
for (;;) {
time1 = std::chrono::steady_clock::now();
// Ideally this would be Stop, but that requires RDTSCP on x86. To avoid
// another codepath, just use Start instead. now() presumably has its own
// fence-like behavior.
ticks1 = timer::Start();
// Do not use Stop, see comment above
if (time1 >= time_min)
break;
}
const double dticks =
static_cast<
double>(ticks1 - ticks0);
std::chrono::duration<
double, std::ratio<
1>> dtime = time1 - time0;
const double ticks_per_sec = dticks / dtime.count();
max_ticks_per_sec = HWY_MAX(max_ticks_per_sec, ticks_per_sec);
}
return max_ticks_per_sec;
}
#if HWY_ARCH_PPC &&
defined(__GLIBC__) &&
defined(__powerpc64__)
namespace ppc {
static HWY_INLINE
double GetTimebaseFreq() {
const auto timebase_freq = __ppc_get_timebase_freq();
// If timebase_freq is greater than 0, then return timebase_freq.
// Otherwise, if timebase_freq is less than or equal to 0, fall back to
// MeasureNominalClockRate(). This works around issues if running on QEMU on
// non-PPC CPU's.
return (timebase_freq >
0) ?
static_cast<
double>(timebase_freq)
: MeasureNominalClockRate();
}
}
// namespace ppc
#endif
namespace platform {
HWY_DLLEXPORT
bool GetCpuString(char* cpu100) {
#if HWY_ARCH_X86
uint32_t abcd[
4];
// Check if brand string is supported (it is on all reasonable Intel/AMD)
x86::Cpuid(
0x80000000U,
0, abcd);
if (abcd[
0] <
0x80000004U) {
cpu100[
0] =
'\0';
return false;
}
for (size_t i =
0; i <
3; ++i) {
x86::Cpuid(
static_cast<uint32_t>(
0x80000002U + i),
0, abcd);
CopyBytes<
sizeof(abcd)>(&abcd[
0], cpu100 + i *
16);
// not same size
}
cpu100[
48] =
'\0';
return true;
#else
cpu100[
0] =
'?';
cpu100[
1] =
'\0';
return false;
#endif
}
HWY_DLLEXPORT
double Now() {
static const double mul =
1.
0 / InvariantTicksPerSecond();
return static_cast<
double>(timer::Start()) * mul;
}
HWY_DLLEXPORT
bool HaveTimerStop(char* cpu100) {
#if HWY_ARCH_X86
if (!x86::HasRDTSCP()) {
(
void)GetCpuString(cpu100);
return false;
}
#endif
*cpu100 =
'\0';
return true;
}
HWY_DLLEXPORT
double InvariantTicksPerSecond() {
#if HWY_ARCH_PPC &&
defined(__GLIBC__) &&
defined(__powerpc64__)
static const double freq = ppc::GetTimebaseFreq();
return freq;
#elif HWY_ARCH_X86 || HWY_ARCH_RISCV || (HWY_ARCH_ARM_A64 && !HWY_COMPILER_MSVC)
// We assume the x86 TSC is invariant; it is on all recent Intel/AMD CPUs.
static const double freq = MeasureNominalClockRate();
return freq;
#elif defined(_WIN32) ||
defined(_WIN64)
LARGE_INTEGER freq;
(
void)QueryPerformanceFrequency(&freq);
return static_cast<
double>(freq.QuadPart);
#elif defined(__APPLE__)
// https://developer.apple.com/library/mac/qa/qa1398/_index.html
mach_timebase_info_data_t timebase;
(
void)mach_timebase_info(&timebase);
return static_cast<
double>(timebase.denom) / timebase.numer *
1E9;
#else
return 1E9;
// Haiku and clock_gettime return nanoseconds.
#endif
}
HWY_DLLEXPORT uint64_t TimerResolution() {
char cpu100[
100];
bool can_use_stop = HaveTimerStop(cpu100);
// For measuring timer overhead/resolution. Used in a nested loop =>
// quadratic time, acceptable because we know timer overhead is "low".
// constexpr because this is used to define array bounds.
constexpr size_t kTimerSamples =
256;
// Nested loop avoids exceeding stack/L1 capacity.
timer::Ticks repetitions[kTimerSamples];
for (size_t rep =
0; rep < kTimerSamples; ++rep) {
timer::Ticks samples[kTimerSamples];
if (can_use_stop) {
for (size_t i =
0; i < kTimerSamples; ++i) {
const timer::Ticks t0 = timer::Start();
const timer::Ticks t1 = timer::Stop();
// we checked HasRDTSCP above
samples[i] = t1 - t0;
}
}
else {
for (size_t i =
0; i < kTimerSamples; ++i) {
const timer::Ticks t0 = timer::Start();
const timer::Ticks t1 = timer::Start();
// do not use Stop, see above
samples[i] = t1 - t0;
}
}
repetitions[rep] = robust_statistics::Mode(samples);
}
return robust_statistics::Mode(repetitions);
}
}
// namespace platform
}
// namespace hwy