Commit bfb8b866 authored by Jens Petit's avatar Jens Petit
Browse files

DataHandlerGPU: Test core functionality (#21)

parent 7f24cd6d
......@@ -12,7 +12,7 @@ ELSA_TEST(PartitionDescriptor)
ELSA_TEST(RandomBlocksDescriptor)
ELSA_TEST(DataContainer)
ELSA_TEST(DataHandlers)
ELSA_TEST(DataHandlerMapCPU)
ELSA_TEST(DataHandlerMap)
ELSA_TEST(LinearOperator)
ELSA_TEST(ExpressionTemplates)
......
......@@ -14,7 +14,11 @@
#include <cstdlib>
using namespace elsa;
static const index_t dimension = 2;
// use max of 256
static const index_t dimension = 256;
// dimension of memory critical benchmarks
static const index_t dimensionMemCritic = 1024;
TEST_CASE("Expression benchmark using Eigen with n=" + std::to_string(dimension) + "^3")
{
......@@ -85,18 +89,21 @@ TEST_CASE("Expression benchmark using expression templates with n=" + std::to_st
{
result = dc * dc2 - dc2 / dc3 + dc * dc3;
};
BENCHMARK("reduction") { dc.sum(); };
}
TEST_CASE("Expression benchmark without expression templates with n=" + std::to_string(dimension)
TEST_CASE("Expression benchmark using GPU expression templates with n=" + std::to_string(dimension)
+ "^3")
{
IndexVector_t numCoeff(3);
numCoeff << dimension, dimension, dimension;
DataDescriptor desc(numCoeff);
DataContainer dc(desc);
DataContainer dc2(desc);
DataContainer dc3(desc);
DataContainer result(desc);
DataContainer dc(desc, DataHandlerType::GPU);
DataContainer dc2(desc, DataHandlerType::GPU);
DataContainer dc3(desc, DataHandlerType::GPU);
DataContainer result(desc, DataHandlerType::GPU);
for (index_t i = 0; i < dc.getSize(); ++i) {
dc[i] = static_cast<float>(rand()) / (static_cast<float>(RAND_MAX / 100.0));
......@@ -104,9 +111,6 @@ TEST_CASE("Expression benchmark without expression templates with n=" + std::to_
dc3[i] = static_cast<float>(rand()) / (static_cast<float>(RAND_MAX / 100.0));
}
// to avoid using expression templates
using namespace elsa::detail;
BENCHMARK("exp = dc - dc2;") { result = dc - dc2; };
BENCHMARK("exp = dc - dc2 + dc;") { result = dc - dc2 + dc; };
......@@ -119,4 +123,36 @@ TEST_CASE("Expression benchmark without expression templates with n=" + std::to_
{
result = dc * dc2 - dc2 / dc3 + dc * dc3;
};
BENCHMARK("reduction") { dc.sum(); };
}
TEST_CASE("Expression benchmark using GPU expression templates with n="
+ std::to_string(dimensionMemCritic) + "^3")
{
index_t size = dimensionMemCritic * dimensionMemCritic * dimensionMemCritic;
Eigen::Matrix<float, Eigen::Dynamic, 1> randVec(size);
Eigen::Matrix<float, Eigen::Dynamic, 1> randVec2(size);
randVec.setRandom();
randVec2.setRandom();
IndexVector_t numCoeff(3);
numCoeff << dimensionMemCritic, dimensionMemCritic, dimensionMemCritic;
DataDescriptor desc(numCoeff);
DataContainer dc(desc, randVec, DataHandlerType::GPU);
DataContainer dc2(desc, randVec2, DataHandlerType::GPU);
BENCHMARK("GPU: dc2 = 1.2 * dc + dc2;") { dc2 = 1.2f * dc + dc2; };
BENCHMARK("Eigen: dc2 = 1.2 * dc + dc2;")
{
randVec2 = (randVec.array() * 1.2f + randVec2.array()).matrix();
};
BENCHMARK("GPU: reduction") { return dc.sum(); };
BENCHMARK("Eigen: reduction") { return randVec.sum(); };
}
This diff is collapsed.
......@@ -13,16 +13,35 @@
#include "DataHandlerMapCPU.h"
#include "testHelpers.h"
#ifdef ELSA_CUDA_VECTOR
#include "DataHandlerGPU.h"
#include "DataHandlerMapGPU.h"
#endif
template <typename data_t>
long elsa::useCount(const DataHandlerCPU<data_t>& dh)
{
return dh._data.use_count();
}
#ifdef ELSA_CUDA_VECTOR
template <typename data_t>
long elsa::useCount(const DataHandlerGPU<data_t>& dh)
{
return dh._data.use_count();
}
#endif
using namespace elsa;
#ifdef ELSA_CUDA_VECTOR
TEMPLATE_PRODUCT_TEST_CASE("Scenario: Constructing DataHandler", "",
(DataHandlerCPU, DataHandlerGPU),
(float, double, std::complex<float>, std::complex<double>, index_t))
#else
TEMPLATE_PRODUCT_TEST_CASE("Scenario: Constructing DataHandler", "", (DataHandlerCPU),
(float, double, std::complex<float>, std::complex<double>, index_t))
#endif
{
using data_t = typename TestType::value_type;
......@@ -92,9 +111,15 @@ TEMPLATE_PRODUCT_TEST_CASE("Scenario: Constructing DataHandler", "", (DataHandle
}
}
#ifdef ELSA_CUDA_VECTOR
TEMPLATE_PRODUCT_TEST_CASE("Scenario: Testing equality operator on DataHandler", "",
(DataHandlerCPU, DataHandlerGPU),
(float, double, std::complex<float>, std::complex<double>, index_t))
#else
TEMPLATE_PRODUCT_TEST_CASE("Scenario: Testing equality operator on DataHandler", "",
(DataHandlerCPU),
(float, double, std::complex<float>, std::complex<double>, index_t))
#endif
{
using data_t = typename TestType::value_type;
......@@ -148,8 +173,14 @@ TEMPLATE_PRODUCT_TEST_CASE("Scenario: Testing equality operator on DataHandler",
}
}
#ifdef ELSA_CUDA_VECTOR
TEMPLATE_PRODUCT_TEST_CASE("Scenario: Assigning to DataHandlerCPU", "",
(DataHandlerCPU, DataHandlerGPU),
(float, double, std::complex<float>, std::complex<double>, index_t))
#else
TEMPLATE_PRODUCT_TEST_CASE("Scenario: Assigning to DataHandlerCPU", "", (DataHandlerCPU),
(float, double, std::complex<float>, std::complex<double>, index_t))
#endif
{
using data_t = typename TestType::value_type;
......@@ -376,7 +407,12 @@ TEMPLATE_PRODUCT_TEST_CASE("Scenario: Assigning to DataHandlerCPU", "", (DataHan
}
}
#ifdef ELSA_CUDA_VECTOR
TEMPLATE_TEST_CASE("Scenario: Cloning DataHandler", "", DataHandlerCPU<float>,
DataHandlerGPU<float>)
#else
TEMPLATE_TEST_CASE("Scenario: Cloning DataHandler", "", DataHandlerCPU<float>)
#endif
{
GIVEN("some DataHandler")
{
......@@ -403,9 +439,15 @@ TEMPLATE_TEST_CASE("Scenario: Cloning DataHandler", "", DataHandlerCPU<float>)
}
}
#ifdef ELSA_CUDA_VECTOR
TEMPLATE_PRODUCT_TEST_CASE("Scenario: Testing the reduction operatios of DataHandler", "",
(DataHandlerCPU, DataHandlerGPU),
(float, double, std::complex<float>, std::complex<double>, index_t))
#else
TEMPLATE_PRODUCT_TEST_CASE("Scenario: Testing the reduction operatios of DataHandler", "",
(DataHandlerCPU),
(float, double, std::complex<float>, std::complex<double>, index_t))
#endif
{
using data_t = typename TestType::value_type;
......@@ -421,7 +463,7 @@ TEMPLATE_PRODUCT_TEST_CASE("Scenario: Testing the reduction operatios of DataHan
THEN("the reductions work as expected")
{
REQUIRE(dh.sum() == randVec.sum());
REQUIRE(checkSameNumbers(dh.sum(), randVec.sum()));
REQUIRE(dh.l1Norm() == Approx(randVec.array().abs().sum()));
REQUIRE(dh.lInfNorm() == Approx(randVec.array().abs().maxCoeff()));
REQUIRE(dh.squaredL2Norm() == Approx(randVec.squaredNorm()));
......@@ -450,9 +492,15 @@ TEMPLATE_PRODUCT_TEST_CASE("Scenario: Testing the reduction operatios of DataHan
}
}
#ifdef ELSA_CUDA_VECTOR
TEMPLATE_PRODUCT_TEST_CASE("Scenario: Testing the element-wise operations of DataHandler", "",
(DataHandlerCPU, DataHandlerGPU),
(float, double, std::complex<float>, std::complex<double>, index_t))
#else
TEMPLATE_PRODUCT_TEST_CASE("Scenario: Testing the element-wise operations of DataHandler", "",
(DataHandlerCPU),
(float, double, std::complex<float>, std::complex<double>, index_t))
#endif
{
using data_t = typename TestType::value_type;
......@@ -504,24 +552,26 @@ TEMPLATE_PRODUCT_TEST_CASE("Scenario: Testing the element-wise operations of Dat
dh = oldDh;
dh *= dh2;
for (index_t i = 0; i < size; ++i)
REQUIRE(dh[i] == oldDh[i] * dh2[i]);
REQUIRE(checkSameNumbers(dh[i], oldDh[i] * dh2[i]));
dh = oldDh;
dh *= *dhMap;
for (index_t i = 0; i < size; ++i)
REQUIRE(dh[i] == oldDh[i] * dh2[i]);
REQUIRE(checkSameNumbers(dh[i], oldDh[i] * dh2[i]));
dh = oldDh;
dh /= dh2;
for (index_t i = 0; i < size; ++i)
if (dh2[i] != data_t(0))
REQUIRE(checkSameNumbers(dh[i], oldDh[i] / dh2[i]));
// due to floating point arithmetic less precision
REQUIRE(checkSameNumbers(dh[i], oldDh[i] / dh2[i], 100));
dh = oldDh;
dh /= *dhMap;
for (index_t i = 0; i < size; ++i)
if (dh2[i] != data_t(0))
REQUIRE(checkSameNumbers(dh[i], oldDh[i] / dh2[i]));
// due to floating point arithmetic less precision
REQUIRE(checkSameNumbers(dh[i], oldDh[i] / dh2[i], 100));
}
THEN("the element-wise binary scalar operations work as expected")
......@@ -561,8 +611,14 @@ TEMPLATE_PRODUCT_TEST_CASE("Scenario: Testing the element-wise operations of Dat
}
}
#ifdef ELSA_CUDA_VECTOR
TEMPLATE_PRODUCT_TEST_CASE("Scenario: Referencing blocks of DataHandler", "",
(DataHandlerCPU, DataHandlerGPU),
(float, double, std::complex<float>, std::complex<double>, index_t))
#else
TEMPLATE_PRODUCT_TEST_CASE("Scenario: Referencing blocks of DataHandler", "", (DataHandlerCPU),
(float, double, std::complex<float>, std::complex<double>, index_t))
#endif
{
using data_t = typename TestType::value_type;
......@@ -601,8 +657,14 @@ TEMPLATE_PRODUCT_TEST_CASE("Scenario: Referencing blocks of DataHandler", "", (D
}
}
#ifdef ELSA_CUDA_VECTOR
TEMPLATE_PRODUCT_TEST_CASE("Scenario: Testing the copy-on-write mechanism", "",
(DataHandlerCPU, DataHandlerGPU),
(float, double, std::complex<float>, std::complex<double>, index_t))
#else
TEMPLATE_PRODUCT_TEST_CASE("Scenario: Testing the copy-on-write mechanism", "", (DataHandlerCPU),
(float, double, std::complex<float>, std::complex<double>, index_t))
#endif
{
using data_t = typename TestType::value_type;
......
......@@ -102,13 +102,13 @@ TEMPLATE_TEST_CASE("Scenario: Expression templates", "", float, double)
THEN("the type is a DataContainer again")
{
DataContainer newDC2 = 2.8 * dc2;
DataContainer newDC2 = TestType(2.8) * dc2;
INFO(type_name<decltype(newDC2)>());
}
THEN("the type is a DataContainer again")
{
DataContainer newDC2 = dc2 * 2.8;
DataContainer newDC2 = dc2 * TestType(2.8);
INFO(type_name<decltype(newDC2)>());
}
}
......@@ -119,9 +119,9 @@ TEMPLATE_TEST_CASE("Scenario: Expression templates", "", float, double)
IndexVector_t numCoeff(3);
numCoeff << dimension, dimension, dimension;
DataDescriptor desc(numCoeff);
DataContainer dc1(desc);
DataContainer dc2(desc);
DataContainer dc3(desc);
DataContainer<TestType> dc1(desc);
DataContainer<TestType> dc2(desc);
DataContainer<TestType> dc3(desc);
for (index_t i = 0; i < dc1.getSize(); ++i) {
dc1[i] = float(i) + 1;
......@@ -202,7 +202,7 @@ TEMPLATE_TEST_CASE("Scenario: Expression templates", "", float, double)
index_t numBlocks = 7;
IdenticalBlocksDescriptor blockDesc(numBlocks, desc);
DataContainer dc(blockDesc);
DataContainer<TestType> dc(blockDesc);
for (index_t i = 0; i < dc.getSize(); ++i) {
dc[i] = float(i) + 1;
......@@ -214,9 +214,9 @@ TEMPLATE_TEST_CASE("Scenario: Expression templates", "", float, double)
{
THEN("the calculations are correct")
{
DataContainer result = 1.8 * dc + dc;
DataContainer result = TestType(1.8) * dc + dc;
for (index_t i = 0; i < dc.getSize(); i++) {
REQUIRE(Approx(result[i]) == 1.8 * dc[i] + dc[i]);
REQUIRE(Approx(result[i]) == TestType(1.8) * dc[i] + dc[i]);
}
}
}
......@@ -229,10 +229,11 @@ TEMPLATE_TEST_CASE("Scenario: Expression templates", "", float, double)
auto dcBlock2 = dc.getBlock(1);
auto dcBlock3 = dc.getBlock(2);
DataContainer result = 1.8 * dcBlock + dcBlock2 / dcBlock - square(dcBlock3);
DataContainer result =
TestType(1.8) * dcBlock + dcBlock2 / dcBlock - square(dcBlock3);
for (index_t i = 0; i < result.getSize(); i++) {
REQUIRE(Approx(result[i])
== 1.8 * dcBlock[i] + dcBlock2[i] / dcBlock[i]
== TestType(1.8) * dcBlock[i] + dcBlock2[i] / dcBlock[i]
- dcBlock3[i] * dcBlock3[i]);
}
}
......@@ -244,9 +245,11 @@ TEMPLATE_TEST_CASE("Scenario: Expression templates", "", float, double)
{
const auto dcBlock = dc.getBlock(0);
DataContainer result = 1.8 * dcBlock + dcBlock / dcBlock - square(dcBlock);
DataContainer result =
TestType(1.8) * dcBlock + dcBlock / dcBlock - square(dcBlock);
for (index_t i = 0; i < result.getSize(); i++) {
REQUIRE(Approx(result[i]) == 1.8 * dc[i] + dc[i] / dc[i] - dc[i] * dc[i]);
REQUIRE(Approx(result[i])
== TestType(1.8) * dc[i] + dc[i] / dc[i] - dc[i] * dc[i]);
}
}
}
......@@ -258,7 +261,7 @@ TEMPLATE_TEST_CASE("Scenario: Expression templates", "", float, double)
numCoeff << 52, 7, 29;
DataDescriptor desc(numCoeff);
DataContainer dc(desc);
DataContainer<TestType> dc(desc);
for (index_t i = 0; i < dc.getSize(); ++i) {
dc[i] = float(i) + 1;
......@@ -272,16 +275,181 @@ TEMPLATE_TEST_CASE("Scenario: Expression templates", "", float, double)
DataDescriptor linearDesc(numCoeff);
auto linearDc = dc.viewAs(linearDesc);
DataContainer result = 1.8 * linearDc + linearDc / linearDc - square(linearDc);
DataContainer result =
TestType(1.8) * linearDc + linearDc / linearDc - square(linearDc);
THEN("the calculations are correct")
{
for (index_t i = 0; i < result.getSize(); i++) {
REQUIRE(Approx(result[i])
== 1.8 * linearDc[i] + linearDc[i] / linearDc[i]
== TestType(1.8) * linearDc[i] + linearDc[i] / linearDc[i]
- linearDc[i] * linearDc[i]);
}
}
}
}
#ifdef ELSA_CUDA_VECTOR
cudaDeviceReset();
#endif
}
#ifdef ELSA_CUDA_VECTOR
TEMPLATE_TEST_CASE("Scenario: Expression templates on GPU", "", float, double)
{
GIVEN("Three random data containers")
{
srand(static_cast<unsigned>(time(nullptr)));
IndexVector_t numCoeff(3);
numCoeff << dimension, dimension, dimension;
DataDescriptor desc(numCoeff);
DataContainer<TestType> dc(desc, DataHandlerType::GPU);
DataContainer<TestType> dc2(desc, DataHandlerType::GPU);
DataContainer<TestType> dc3(desc, DataHandlerType::GPU);
DataContainer<TestType> result(desc, DataHandlerType::GPU);
for (index_t i = 0; i < dc.getSize(); ++i) {
dc[i] = static_cast<TestType>(rand()) / (static_cast<TestType>(RAND_MAX / 100.0));
dc2[i] = static_cast<TestType>(rand()) / (static_cast<TestType>(RAND_MAX / 100.0));
dc3[i] = static_cast<TestType>(rand()) / (static_cast<TestType>(RAND_MAX / 100.0));
}
WHEN("using auto with an expression")
{
auto exp = dc * dc2;
auto exp2 = dc * dc2 - dc;
THEN("the type is an (nested) expression type")
{
INFO(type_name<decltype(exp)>());
INFO(type_name<decltype(exp2)>());
}
}
WHEN("writing into a result DataContainer")
{
result = square(dc * dc2 - dc);
THEN("the type is a DataContainer again") { INFO(type_name<decltype(result)>()); }
}
WHEN("Mixing expression and DataContainers")
{
Expression exp = elsa::exp(sqrt(dc) * log(dc2));
result = dc * dc2 - dc / exp;
THEN("the type is a DataContainer again")
{
INFO(type_name<decltype(exp)>());
INFO(type_name<decltype(result)>());
}
}
WHEN("Constructing a new DataContainer out of an expression")
{
THEN("the type is a DataContainer again")
{
DataContainer newDC = dc * dc2 + dc3 / dc2;
INFO(type_name<decltype(newDC)>());
static_assert(std::is_same_v<typename decltype(newDC)::value_type, TestType>);
}
THEN("the type is a DataContainer again")
{
DataContainer newDC2 = TestType(2.8) * dc2;
INFO(type_name<decltype(newDC2)>());
static_assert(std::is_same_v<typename decltype(newDC2)::value_type, TestType>);
}
THEN("the type is a DataContainer again")
{
DataContainer newDC2 = dc2 * TestType(2.8);
INFO(type_name<decltype(newDC2)>());
static_assert(std::is_same_v<typename decltype(newDC2)::value_type, TestType>);
}
}
}
GIVEN("Three DataContainers")
{
IndexVector_t numCoeff(3);
numCoeff << dimension, dimension, dimension;
DataDescriptor desc(numCoeff);
DataContainer dc1(desc, DataHandlerType::GPU);
DataContainer dc2(desc, DataHandlerType::GPU);
DataContainer dc3(desc, DataHandlerType::GPU);
for (index_t i = 0; i < dc1.getSize(); ++i) {
dc1[i] = float(i) + 1;
dc2[i] = float(i) + 2;
dc3[i] = float(i) + 3;
}
WHEN("Performing calculations")
{
DataContainer result = dc1 + dc1 * sqrt(dc2);
THEN("the results have to be correct")
{
for (index_t i = 0; i < result.getSize(); ++i) {
REQUIRE(Approx(result[i]) == dc1[i] + dc1[i] * std::sqrt(dc2[i]));
}
}
}
WHEN("Performing calculations")
{
DataContainer result = dc3 / dc1 * dc2 - dc3;
THEN("the results have to be correct")
{
for (index_t i = 0; i < result.getSize(); ++i) {
REQUIRE(Approx(result[i]).epsilon(0.001) == dc3[i] / dc1[i] * dc2[i] - dc3[i]);
}
}
}
WHEN("Performing in-place calculations")
{
THEN("then the element-wise in-place addition works as expected")
{
DataContainer dc1Before = dc1;
dc1 += dc3 * 2 / dc2;
for (index_t i = 0; i < dc1.getSize(); ++i) {
REQUIRE(Approx(dc1[i]).epsilon(0.001) == dc1Before[i] + (dc3[i] * 2 / dc2[i]));
}
}
THEN("then the element-wise in-place multiplication works as expected")
{
DataContainer dc1Before = dc1;
dc1 *= dc3 * 2 / dc2;
for (index_t i = 0; i < dc1.getSize(); ++i) {
REQUIRE(Approx(dc1[i]).epsilon(0.001) == dc1Before[i] * (dc3[i] * 2 / dc2[i]));
}
}
THEN("then the element-wise in-place division works as expected")
{
DataContainer dc1Before = dc1;
dc1 /= dc3 * 2 / dc2;
for (index_t i = 0; i < dc1.getSize(); ++i) {
REQUIRE(Approx(dc1[i]).epsilon(0.001) == dc1Before[i] / (dc3[i] * 2 / dc2[i]));
}
}
THEN("then the element-wise in-place subtraction works as expected")
{
DataContainer dc1Before = dc1;
dc1 -= dc3 * 2 / dc2;
for (index_t i = 0; i < dc1.getSize(); ++i) {
REQUIRE(Approx(dc1[i]).epsilon(0.001) == dc1Before[i] - (dc3[i] * 2 / dc2[i]));
}
}
}
}
cudaDeviceReset();
}
#endif
......@@ -7,6 +7,7 @@
*/
#include <catch2/catch.hpp>
#include <iostream>
#include "elsaDefines.h"
using namespace elsa;
......@@ -36,4 +37,16 @@ SCENARIO("Testing compile-time predicates")
static_assert(!std::is_same_v<float, GetFloatingPointType_t<double>>);
REQUIRE(true);
}
SCENARIO("Printing default handler type")
{
switch (defaultHandlerType) {
case DataHandlerType::CPU:
std::cout << "CPU" << std::endl;
break;
case DataHandlerType::GPU:
std::cout << "GPU" << std::endl;
break;
}
}
\ No newline at end of file
#pragma once
#include <type_traits>
#include <complex.h>
#include <complex>
#include "elsaDefines.h"
/**
* \brief comparing two number types for approximate equality for complex and regular number
......@@ -13,15 +14,22 @@
* The CHECK(...) assertion in the function ensures that the values are reported when the test fails
*/
template <typename T>
bool checkSameNumbers(T right, T left)
bool checkSameNumbers(T left, T right, int epsilonFactor = 1)
{
using numericalBaseType = elsa::GetFloatingPointType_t<T>;
numericalBaseType eps = std::numeric_limits<numericalBaseType>::epsilon()
* static_cast<numericalBaseType>(epsilonFactor)
* static_cast<numericalBaseType>(100);