#include "gromacs/gpu_utils/hostallocator.h"
+#include "config.h"
+
#include <type_traits>
#include <vector>
#include <gtest/gtest.h>
+#include "gromacs/gpu_utils/gpu_utils.h"
#include "gromacs/math/vectypes.h"
+#include "gromacs/utility/arrayref.h"
#include "gromacs/utility/real.h"
#include "devicetransfers.h"
namespace
{
-//! The types used in testing.
-typedef ::testing::Types<int, real, RVec> TestTypes;
-
-//! Typed test fixture
+/*! \internal \brief Typed test fixture for infrastructure for
+ * host-side memory used for GPU transfers. */
template <typename T>
-class HostAllocatorTest : public test::GpuTest
+class HostMemoryTest : public test::GpuTest
{
public:
//! Convenience type
using ValueType = T;
//! Convenience type
- using AllocatorType = HostAllocator<T>;
- //! Convenience type
- using VectorType = std::vector<ValueType, AllocatorType>;
- //! Convenience type
using ViewType = ArrayRef<ValueType>;
//! Convenience type
using ConstViewType = ArrayRef<const ValueType>;
//! Prepare contents of a VectorType.
+ template <typename VectorType>
void fillInput(VectorType *input) const;
//! Compares input and output vectors.
void compareVectors(ConstViewType input,
};
// Already documented
-template <typename T>
-void HostAllocatorTest<T>::fillInput(VectorType *input) const
+template <typename T> template <typename VectorType>
+void HostMemoryTest<T>::fillInput(VectorType *input) const
{
- input->push_back(1);
- input->push_back(2);
- input->push_back(3);
+ input->resize(3);
+ (*input)[0] = 1;
+ (*input)[1] = 2;
+ (*input)[2] = 3;
}
//! Initialization specialization for RVec
-template <>
-void HostAllocatorTest<RVec>::fillInput(VectorType *input) const
+template <> template <typename VectorType>
+void HostMemoryTest<RVec>::fillInput(VectorType *input) const
{
- input->push_back({1, 2, 3});
+ input->reserve(3);
+ input->resize(3);
+ (*input)[0] = {1, 2, 3};
+ (*input)[1] = {4, 5, 6};
+ (*input)[2] = {7, 8, 9};
}
// Already documented
template <typename T>
-void HostAllocatorTest<T>::compareVectors(ConstViewType input,
- ConstViewType output) const
+void HostMemoryTest<T>::compareVectors(ConstViewType input,
+ ConstViewType output) const
{
for (size_t i = 0; i != input.size(); ++i)
{
//! Comparison specialization for RVec
template <>
-void HostAllocatorTest<RVec>::compareVectors(ConstViewType input,
- ConstViewType output) const
+void HostMemoryTest<RVec>::compareVectors(ConstViewType input,
+ ConstViewType output) const
{
for (size_t i = 0; i != input.size(); ++i)
{
}
template <typename T>
-void HostAllocatorTest<T>::runTest(ConstViewType input, ViewType output) const
+void HostMemoryTest<T>::runTest(ConstViewType input, ViewType output) const
{
- // We can't do a test that does a transfer unless we have a
- // compatible device.
- if (!this->haveValidGpus())
- {
- return;
- }
-
// Convert the views of input and output to flat non-const chars,
// so that there's no templating when we call doDeviceTransfers.
auto inputRef = charArrayRefFromArray(input.data(), input.size());
this->compareVectors(input, output);
}
+//! The types used in testing.
+typedef ::testing::Types<int, real, RVec> TestTypes;
+
+//! Typed test fixture
+template <typename T>
+class HostAllocatorTest : public HostMemoryTest<T>
+{
+ public:
+ //! Convenience type
+ using ValueType = T;
+ //! Convenience type
+ using AllocatorType = HostAllocator<T>;
+ //! Convenience type
+ using VectorType = std::vector<ValueType, AllocatorType>;
+};
+
TYPED_TEST_CASE(HostAllocatorTest, TestTypes);
// Note that in GoogleTest typed tests, the use of TestFixture:: and
typename TestFixture::VectorType v;
}
-TYPED_TEST(HostAllocatorTest, TransfersUsingDefaultHostAllocatorWork)
+TYPED_TEST(HostAllocatorTest, VectorsWithDefaultHostAllocatorAlwaysWorks)
{
typename TestFixture::VectorType input = {{1, 2, 3}}, output;
output.resize(input.size());
+}
+
+// Several tests actually do CUDA transfers. This is not necessary
+// because the state of page alignment or pinning is not currently
+// relevant to the success of a CUDA transfer. CUDA checks happen only
+// during cudaHostRegister and cudaHostUnregister. Such tests are of
+// value only when this behaviour changes, if ever.
+
+TYPED_TEST(HostAllocatorTest, TransfersWithoutPinningWork)
+{
+ typename TestFixture::VectorType input;
+ this->fillInput(&input);
+ typename TestFixture::VectorType output;
+ output.resize(input.size());
this->runTest(input, output);
}
-TYPED_TEST(HostAllocatorTest, TransfersUsingNormalCpuHostAllocatorWork)
+TYPED_TEST(HostAllocatorTest, FillInputAlsoWorksAfterCallingReserve)
{
- // Make an allocator with a 'normal CPU' allocation policy. This
- // might be slower than another policy, but still works.
- using AllocatorType = typename TestFixture::AllocatorType;
- using AllocatorPolicyType = typename AllocatorType::allocation_policy;
- AllocatorPolicyType policy(AllocatorPolicyType::Impl::AllocateAligned);
- AllocatorType allocator(policy);
+ typename TestFixture::VectorType input;
+ input.reserve(3);
+ this->fillInput(&input);
+}
+
+#if GMX_GPU == GMX_GPU_CUDA
- typename TestFixture::VectorType input(allocator);
+// Policy suitable for pinning is only supported for a CUDA build
+
+TYPED_TEST(HostAllocatorTest, TransfersWithPinningWorkWithCuda)
+{
+ typename TestFixture::VectorType input;
+ changePinningPolicy(&input, PinningPolicy::CanBePinned);
this->fillInput(&input);
- typename TestFixture::VectorType output(allocator);
+ typename TestFixture::VectorType output;
+ changePinningPolicy(&output, PinningPolicy::CanBePinned);
output.resize(input.size());
this->runTest(input, output);
}
-TYPED_TEST(HostAllocatorTest, TransfersUsingGpuHostAllocatorWork)
+//! Helper function for wrapping a call to isHostMemoryPinned.
+template <typename VectorType>
+bool isPinned(const VectorType &v)
{
- // Make an allocator with a 'for GPU' allocation policy. This
- // should be more efficient, but we can't test that.
- using AllocatorType = typename TestFixture::AllocatorType;
- using AllocatorPolicyType = typename AllocatorType::allocation_policy;
- AllocatorPolicyType policy(AllocatorPolicyType::Impl::AllocateForGpu);
- AllocatorType allocator(policy);
+ void *data = const_cast<void *>(static_cast<const void *>(v.data()));
+ return isHostMemoryPinned(data);
+}
+
+TYPED_TEST(HostAllocatorTest, ManualPinningOperationsWorkWithCuda)
+{
+ typename TestFixture::VectorType input;
+ changePinningPolicy(&input, PinningPolicy::CanBePinned);
+ EXPECT_FALSE(isPinned(input));
+
+ // Unpin before allocation is fine, but does nothing.
+ input.get_allocator().getPolicy().unpin();
+ EXPECT_FALSE(isPinned(input));
- typename TestFixture::VectorType input(allocator);
+ // Pin with no contents is fine, but does nothing.
+ input.get_allocator().getPolicy().pin();
+ EXPECT_FALSE(isPinned(input));
+
+ // Fill some contents, which will be pinned because of the policy.
this->fillInput(&input);
- typename TestFixture::VectorType output(allocator);
- output.resize(input.size());
+ EXPECT_TRUE(isPinned(input));
- this->runTest(input, output);
+ // Unpin after pin is fine.
+ input.get_allocator().getPolicy().unpin();
+ EXPECT_FALSE(isPinned(input));
+
+ // Repeated unpin should be a no-op.
+ input.get_allocator().getPolicy().unpin();
+
+ // Pin after unpin is fine.
+ input.get_allocator().getPolicy().pin();
+ EXPECT_TRUE(isPinned(input));
+
+ // Repeated pin should be a no-op, and still pinned.
+ input.get_allocator().getPolicy().pin();
+ EXPECT_TRUE(isPinned(input));
+
+ // Switching policy to CannotBePinned must unpin the buffer (via
+ // realloc and copy).
+ auto oldInputData = input.data();
+ changePinningPolicy(&input, PinningPolicy::CannotBePinned);
+ EXPECT_FALSE(isPinned(input));
+ // These cannot be equal as both had to be allocated at the same
+ // time for the contents to be able to be copied.
+ EXPECT_NE(oldInputData, input.data());
+
+ // Switching policy to CanBePinned must pin the buffer (via
+ // realloc and copy).
+ oldInputData = input.data();
+ changePinningPolicy(&input, PinningPolicy::CanBePinned);
+ EXPECT_TRUE(isPinned(input));
+ // These cannot be equal as both had to be allocated at the same
+ // time for the contents to be able to be copied.
+ EXPECT_NE(oldInputData, input.data());
+}
+
+#else
+
+TYPED_TEST(HostAllocatorTest, ChangingPinningPolicyRequiresCuda)
+{
+ typename TestFixture::VectorType input;
+ EXPECT_DEATH(changePinningPolicy(&input, PinningPolicy::CanBePinned),
+ ".*A suitable build of GROMACS.* is required.*");
}
+TYPED_TEST(HostAllocatorTest, ManualPinningOperationsWorkEvenWithoutCuda)
+{
+ typename TestFixture::VectorType input;
+
+ // Since the buffer can't be pinned and isn't pinned, and the
+ // calling code can't be unhappy about this, these are OK.
+ input.get_allocator().getPolicy().pin();
+ input.get_allocator().getPolicy().unpin();
+}
+
+#endif
+
TYPED_TEST(HostAllocatorTest, StatefulAllocatorUsesMemory)
{
// The HostAllocator has state, so a container using it will be
sizeof(typename TestFixture::VectorType));
}
+//! Declare allocator types to test.
+using AllocatorTypesToTest = ::testing::Types<HostAllocator<real>,
+ HostAllocator<int>,
+ HostAllocator<RVec>
+ >;
+
+TYPED_TEST_CASE(AllocatorTest, AllocatorTypesToTest);
+
} // namespace
} // namespace
+
+// Includes tests common to all allocation policies.
+#include "gromacs/utility/tests/alignedallocator-impl.h"