vtk-m/vtkm/cont/Timer.cxx
Kenneth Moreland ef58bd9c4a Fix intermittent UnitTestTimer failures with CUDA
We have been noticing occasional failures of UnitTestTimer on the
dashboard for CUDA devices. The timer seems to be recording less time
than is actually elapsed.

The problem might be that the CUDA timer actually inserts fences to the
CUDA stream rather than record the current time. Thus, the actual time
might start after some pending operations complete.

To attempt to match the UnitTestTimer measurements closer to wall-clock
time, add a synchronize before starting the timer.
2022-07-12 08:52:12 -06:00

338 lines
9.9 KiB
C++

//============================================================================
// Copyright (c) Kitware, Inc.
// All rights reserved.
// See LICENSE.txt for details.
//
// This software is distributed WITHOUT ANY WARRANTY; without even
// the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
// PURPOSE. See the above copyright notice for more information.
//============================================================================
#include <vtkm/cont/Algorithm.h>
#include <vtkm/cont/DeviceAdapter.h>
#include <vtkm/cont/DeviceAdapterList.h>
#include <vtkm/cont/Logging.h>
#include <vtkm/cont/RuntimeDeviceTracker.h>
#include <vtkm/cont/Timer.h>
#include <tuple>
namespace
{
template <typename Device>
using DeviceInvalid = std::integral_constant<bool, !Device::IsEnabled>;
using EnabledDeviceList = vtkm::ListRemoveIf<vtkm::cont::DeviceAdapterListCommon, DeviceInvalid>;
template <typename Device>
using DeviceTimerPtr = std::unique_ptr<vtkm::cont::DeviceAdapterTimerImplementation<Device>>;
using EnabledTimerImpls = vtkm::ListTransform<EnabledDeviceList, DeviceTimerPtr>;
using EnabledTimerImplTuple = vtkm::ListApply<EnabledTimerImpls, std::tuple>;
// C++11 does not support get tuple element by type. C++14 does support that.
// Get the index of a type in tuple elements
template <class T, class Tuple>
struct Index;
template <class T, template <typename...> class Container, class... Types>
struct Index<T, Container<T, Types...>>
{
static const std::size_t value = 0;
};
template <class T, class U, template <typename...> class Container, class... Types>
struct Index<T, Container<U, Types...>>
{
static const std::size_t value = 1 + Index<T, Container<Types...>>::value;
};
template <typename Device>
VTKM_CONT inline
typename std::tuple_element<Index<Device, EnabledDeviceList>::value, EnabledTimerImplTuple>::type&
GetUniqueTimerPtr(Device, EnabledTimerImplTuple& enabledTimers)
{
return std::get<Index<Device, EnabledDeviceList>::value>(enabledTimers);
}
struct InitFunctor
{
template <typename Device>
VTKM_CONT void operator()(Device, EnabledTimerImplTuple& timerImpls)
{
//We don't use the runtime device tracker to very initializtion support
//so that the following use case is supported:
//
// GetRuntimeDeviceTracker().Disable( openMP );
// vtkm::cont::Timer timer; //tracks all active devices
// GetRuntimeDeviceTracker().Enable( openMP );
// timer.Start() //want to test openmp
//
// timer.GetElapsedTime()
//
// When `GetElapsedTime` is called we need to make sure that the OpenMP
// device timer is safe to call. At the same time we still need to make
// sure that we have the required runtime and not just compile time support
// this is why we use `DeviceAdapterRuntimeDetector`
bool haveRequiredRuntimeSupport = vtkm::cont::DeviceAdapterRuntimeDetector<Device>{}.Exists();
if (haveRequiredRuntimeSupport)
{
std::get<Index<Device, EnabledDeviceList>::value>(timerImpls)
.reset(new vtkm::cont::DeviceAdapterTimerImplementation<Device>());
}
}
};
struct ResetFunctor
{
template <typename Device>
VTKM_CONT void operator()(Device device,
vtkm::cont::DeviceAdapterId deviceToRunOn,
const vtkm::cont::RuntimeDeviceTracker& tracker,
EnabledTimerImplTuple& timerImpls)
{
if ((deviceToRunOn == device || deviceToRunOn == vtkm::cont::DeviceAdapterTagAny()) &&
tracker.CanRunOn(device))
{
GetUniqueTimerPtr(device, timerImpls)->Reset();
}
}
};
struct StartFunctor
{
template <typename Device>
VTKM_CONT void operator()(Device device,
vtkm::cont::DeviceAdapterId deviceToRunOn,
const vtkm::cont::RuntimeDeviceTracker& tracker,
EnabledTimerImplTuple& timerImpls)
{
if ((deviceToRunOn == device || deviceToRunOn == vtkm::cont::DeviceAdapterTagAny()) &&
tracker.CanRunOn(device))
{
GetUniqueTimerPtr(device, timerImpls)->Start();
}
}
};
struct StopFunctor
{
template <typename Device>
VTKM_CONT void operator()(Device device,
vtkm::cont::DeviceAdapterId deviceToRunOn,
const vtkm::cont::RuntimeDeviceTracker& tracker,
EnabledTimerImplTuple& timerImpls)
{
if ((deviceToRunOn == device || deviceToRunOn == vtkm::cont::DeviceAdapterTagAny()) &&
tracker.CanRunOn(device))
{
GetUniqueTimerPtr(device, timerImpls)->Stop();
}
}
};
struct StartedFunctor
{
bool Value = true;
template <typename Device>
VTKM_CONT void operator()(Device device,
vtkm::cont::DeviceAdapterId deviceToRunOn,
const vtkm::cont::RuntimeDeviceTracker& tracker,
EnabledTimerImplTuple& timerImpls)
{
if ((deviceToRunOn == device || deviceToRunOn == vtkm::cont::DeviceAdapterTagAny()) &&
tracker.CanRunOn(device))
{
this->Value &= GetUniqueTimerPtr(device, timerImpls)->Started();
}
}
};
struct StoppedFunctor
{
bool Value = true;
template <typename Device>
VTKM_CONT void operator()(Device device,
vtkm::cont::DeviceAdapterId deviceToRunOn,
const vtkm::cont::RuntimeDeviceTracker& tracker,
EnabledTimerImplTuple& timerImpls)
{
if ((deviceToRunOn == device || deviceToRunOn == vtkm::cont::DeviceAdapterTagAny()) &&
tracker.CanRunOn(device))
{
this->Value &= GetUniqueTimerPtr(device, timerImpls)->Stopped();
}
}
};
struct ReadyFunctor
{
bool Value = true;
template <typename Device>
VTKM_CONT void operator()(Device device,
vtkm::cont::DeviceAdapterId deviceToRunOn,
const vtkm::cont::RuntimeDeviceTracker& tracker,
EnabledTimerImplTuple& timerImpls)
{
if ((deviceToRunOn == device || deviceToRunOn == vtkm::cont::DeviceAdapterTagAny()) &&
tracker.CanRunOn(device))
{
this->Value &= GetUniqueTimerPtr(device, timerImpls)->Ready();
}
}
};
struct ElapsedTimeFunctor
{
vtkm::Float64 ElapsedTime = 0.0;
template <typename Device>
VTKM_CONT void operator()(Device device,
vtkm::cont::DeviceAdapterId deviceToRunOn,
const vtkm::cont::RuntimeDeviceTracker& tracker,
EnabledTimerImplTuple& timerImpls)
{
if ((deviceToRunOn == device || deviceToRunOn == vtkm::cont::DeviceAdapterTagAny()) &&
tracker.CanRunOn(device))
{
this->ElapsedTime =
vtkm::Max(this->ElapsedTime, GetUniqueTimerPtr(device, timerImpls)->GetElapsedTime());
}
}
};
} // anonymous namespace
namespace vtkm
{
namespace cont
{
namespace detail
{
struct EnabledDeviceTimerImpls
{
EnabledDeviceTimerImpls()
{
vtkm::ListForEach(InitFunctor(), EnabledDeviceList(), this->EnabledTimers);
}
~EnabledDeviceTimerImpls() {}
// A tuple of enabled timer implementations
EnabledTimerImplTuple EnabledTimers;
};
}
}
} // namespace vtkm::cont::detail
namespace vtkm
{
namespace cont
{
Timer::Timer()
: Device(vtkm::cont::DeviceAdapterTagAny())
, Internal(new detail::EnabledDeviceTimerImpls)
{
}
Timer::Timer(vtkm::cont::DeviceAdapterId device)
: Device(device)
, Internal(new detail::EnabledDeviceTimerImpls)
{
const auto& tracker = vtkm::cont::GetRuntimeDeviceTracker();
if (!tracker.CanRunOn(device))
{
VTKM_LOG_S(vtkm::cont::LogLevel::Error,
"Device '" << device.GetName()
<< "' can not run on current Device."
"Thus timer is not usable");
}
}
Timer::~Timer() = default;
void Timer::Reset()
{
const auto& tracker = vtkm::cont::GetRuntimeDeviceTracker();
vtkm::ListForEach(
ResetFunctor(), EnabledDeviceList(), this->Device, tracker, this->Internal->EnabledTimers);
}
void Timer::Reset(vtkm::cont::DeviceAdapterId device)
{
const auto& tracker = vtkm::cont::GetRuntimeDeviceTracker();
if (!tracker.CanRunOn(device))
{
VTKM_LOG_S(vtkm::cont::LogLevel::Error,
"Device '" << device.GetName()
<< "' can not run on current Device."
"Thus timer is not usable");
}
this->Device = device;
this->Reset();
}
void Timer::Start()
{
const auto& tracker = vtkm::cont::GetRuntimeDeviceTracker();
vtkm::ListForEach(
StartFunctor(), EnabledDeviceList(), this->Device, tracker, this->Internal->EnabledTimers);
}
void Timer::Stop()
{
const auto& tracker = vtkm::cont::GetRuntimeDeviceTracker();
vtkm::ListForEach(
StopFunctor(), EnabledDeviceList(), this->Device, tracker, this->Internal->EnabledTimers);
}
bool Timer::Started() const
{
const auto& tracker = vtkm::cont::GetRuntimeDeviceTracker();
StartedFunctor functor;
vtkm::ListForEach(
functor, EnabledDeviceList(), this->Device, tracker, this->Internal->EnabledTimers);
return functor.Value;
}
bool Timer::Stopped() const
{
const auto& tracker = vtkm::cont::GetRuntimeDeviceTracker();
StoppedFunctor functor;
vtkm::ListForEach(
functor, EnabledDeviceList(), this->Device, tracker, this->Internal->EnabledTimers);
return functor.Value;
}
bool Timer::Ready() const
{
const auto& tracker = vtkm::cont::GetRuntimeDeviceTracker();
ReadyFunctor functor;
vtkm::ListForEach(
functor, EnabledDeviceList(), this->Device, tracker, this->Internal->EnabledTimers);
return functor.Value;
}
vtkm::Float64 Timer::GetElapsedTime() const
{
//Throw an exception if a timer bound device now can't be used
auto& tracker = vtkm::cont::GetRuntimeDeviceTracker();
ElapsedTimeFunctor functor;
vtkm::ListForEach(
functor, EnabledDeviceList(), this->Device, tracker, this->Internal->EnabledTimers);
return functor.ElapsedTime;
}
void Timer::Synchronize() const
{
vtkm::cont::Algorithm::Synchronize(this->Device);
}
}
} // namespace vtkm::cont