Skip to content

Commit

Permalink
add back transform_async() as Transform_par() (#764)
Browse files Browse the repository at this point in the history
  • Loading branch information
J. Daniel Smith authored Jan 16, 2024
1 parent 63659ee commit 82d5412
Show file tree
Hide file tree
Showing 3 changed files with 138 additions and 16 deletions.
3 changes: 3 additions & 0 deletions UnitTest/mt.cpp
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
#include "pch.h"
#include "CppUnitTest.h"

#include <sys/ByteSwap.h>

#include <mt/CriticalSection.h>
#include <mt/ThreadPlanner.h>
#include <mt/ThreadGroup.h>
Expand All @@ -11,6 +13,7 @@
#include <mt/ThreadPoolException.h>
#include <mt/GenerationThreadPool.h>
#include <mt/ThreadedByteSwap.h>
#include <mt/Algorithm.h>

namespace mt
{
Expand Down
69 changes: 60 additions & 9 deletions modules/c++/mt/include/mt/Algorithm.h
Original file line number Diff line number Diff line change
Expand Up @@ -20,22 +20,73 @@
*
*/

#ifndef CODA_OSS_mt_Algorithm_h_INCLUDED_
#define CODA_OSS_mt_Algorithm_h_INCLUDED_
#pragma once

#include <algorithm>
#include <iterator>
#include <future>

#include "coda_oss/CPlusPlus.h"
#if CODA_OSS_cpp17
// <execution> is broken with the older version of GCC we're using
#if (__GNUC__ >= 10) || _MSC_VER
#include <execution>
#define CODA_OSS_mt_Algorithm_has_execution 1
#endif
#endif

namespace mt
{
// There was a transform_async() utility here, but I removed it.
//
// First of all, C++11's std::async() is now (in 2023) thought of as maybe a
// bit "half baked," and perhaps shouldn't be emulated. Then, C++17 added
// parallel algorithms which might be a better ... although we're still at C++14.
}
// "Roll our own" `std::transform(execution::par)` using std::async()
// https://en.cppreference.com/w/cpp/algorithm/transform

// Our own `Transform_par_()` is built on `std::async()`; for that we need to control
// a couple of settings.
struct Transform_par_settings final
{
Transform_par_settings() = default;

Transform_par_settings(ptrdiff_t cutoff) : cutoff_(cutoff) { }
Transform_par_settings(std::launch policy) : policy_(policy) { }
Transform_par_settings(ptrdiff_t cutoff, std::launch policy) : cutoff_(cutoff), policy_(policy) { }
Transform_par_settings(std::launch policy, ptrdiff_t cutoff) : Transform_par_settings(cutoff, policy) { }

// The value of "default_cutoff" was determined by testing; there is nothing
// special about it, feel free to change it.
static constexpr ptrdiff_t dimension = 128 * 8;
static constexpr ptrdiff_t default_cutoff = dimension * dimension;
ptrdiff_t cutoff_ = default_cutoff;

#endif // CODA_OSS_mt_Algorithm_h_INCLUDED_
// https://en.cppreference.com/w/cpp/thread/launch
std::launch policy_ = std::launch::async; // "the task is executed on a different thread, potentially by creating and launching it first"
};

template <typename InputIt, typename OutputIt, typename UnaryOperation>
inline OutputIt Transform_par_(InputIt first1, InputIt last1, OutputIt d_first, UnaryOperation unary_op,
const Transform_par_settings& settings)
{
// https://en.cppreference.com/w/cpp/thread/async
const auto len = std::distance(first1, last1);
if (len < settings.cutoff_)
{
return std::transform(first1, last1, d_first, unary_op);
}

const auto mid1 = first1 + len / 2;
const auto d_mid = d_first + len / 2;
auto handle = std::async(settings.policy_, Transform_par_<InputIt, OutputIt, UnaryOperation>, mid1, last1, d_mid, unary_op, settings);
Transform_par_(first1, mid1, d_first, unary_op, settings);
return handle.get();
}
template <typename InputIt, typename OutputIt, typename UnaryOperation>
inline OutputIt Transform_par(InputIt first1, InputIt last1, OutputIt d_first, UnaryOperation unary_op,
Transform_par_settings settings = Transform_par_settings{})
{
#if CODA_OSS_mt_Algorithm_has_execution
return std::transform(std::execution::par, first1, last1, d_first, unary_op);
#else
return Transform_par_(first1, last1, d_first, unary_op, settings);
#endif // CODA_OSS_mt_Algorithm_has_execution
}

}
82 changes: 75 additions & 7 deletions modules/c++/mt/unittests/test_mt_byte_swap.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -28,14 +28,20 @@
#include <std/cstddef> // std::byte
#include <std/span>

#include <sys/ByteSwap.h>

#include <mt/ThreadedByteSwap.h>
#include <mt/Algorithm.h>

#undef min
#undef max

static std::vector<uint64_t> make_origValues(size_t NUM_PIXELS)
static std::vector<uint64_t> make_origValues_(size_t count)
{
::srand(334);

std::vector<uint64_t> retval(NUM_PIXELS);
for (size_t ii = 0; ii < NUM_PIXELS; ++ii)
std::vector<uint64_t> retval(count);
for (size_t ii = 0; ii < count; ++ii)
{
const auto value = static_cast<float>(::rand()) / RAND_MAX *
std::numeric_limits<uint64_t>::max();
Expand All @@ -44,10 +50,16 @@ static std::vector<uint64_t> make_origValues(size_t NUM_PIXELS)
return retval;
}

static constexpr size_t NUM_PIXELS = 10000;
static const std::vector<uint64_t>& make_origValues()
{
static const auto retval = make_origValues_(NUM_PIXELS);
return retval;
}

TEST_CASE(testThreadedByteSwap)
{
constexpr size_t NUM_PIXELS = 10000;
const auto origValues = make_origValues(NUM_PIXELS);
const auto& origValues = make_origValues();

constexpr size_t numThreads = 4;

Expand All @@ -59,14 +71,70 @@ TEST_CASE(testThreadedByteSwap)
std::vector<uint64_t> swappedValues2(origValues.size());
mt::threadedByteSwap(origValues.data(), sizeof(origValues[0]), NUM_PIXELS, numThreads, swappedValues2.data());

// Everything should match
for (size_t ii = 0; ii < NUM_PIXELS; ++ii)
for (size_t ii = 0; ii < NUM_PIXELS; ++ii) // Everything should match
{
TEST_ASSERT_EQ(values1[ii], swappedValues2[ii]);
}
}

TEST_CASE(test_transform_ByteSwap)
{
const auto& origValues = make_origValues();

// Byte swap the old-fashioned way
constexpr size_t numThreads = 4;
auto expected_(origValues);
constexpr auto elemSize = sizeof(expected_[0]);
mt::threadedByteSwap(expected_.data(), elemSize, NUM_PIXELS, numThreads);
const auto& expected = expected_;

// Byte swap into output buffer
const auto byteSwap = [&](const auto& buffer_) {
auto buffer = buffer_;
sys::byteSwap(&buffer, elemSize, 1 /*numElements*/);
return buffer;
};

std::vector<uint64_t> actual(origValues.size());
std::transform(origValues.begin(), origValues.end(), actual.begin(), byteSwap);
for (size_t ii = 0; ii < NUM_PIXELS; ++ii) // Everything should match
{
TEST_ASSERT_EQ(expected[ii], actual[ii]);
}
}

TEST_CASE(test_Transform_par_ByteSwap)
{
const auto& origValues = make_origValues();

// Byte swap the old-fashioned way
constexpr size_t numThreads = 4;
auto expected_(origValues);
constexpr auto elemSize = sizeof(expected_[0]);
mt::threadedByteSwap(expected_.data(), elemSize, NUM_PIXELS, numThreads);
const auto& expected = expected_;

// Byte swap into output buffer
const auto byteSwap = [&](const auto& buffer_) {
auto buffer = buffer_;
sys::byteSwap(&buffer, elemSize, 1 /*numElements*/);
return buffer;
};

// be sure we do something more than just call std::transform()
const mt::Transform_par_settings settings{ NUM_PIXELS / 4 /*cutoff*/ };

std::vector<uint64_t> actual(origValues.size());
mt::Transform_par(origValues.begin(), origValues.end(), actual.begin(), byteSwap, settings);
for (size_t ii = 0; ii < NUM_PIXELS; ++ii) // Everything should match
{
TEST_ASSERT_EQ(expected[ii], actual[ii]);
}
}

TEST_MAIN(
TEST_CHECK(testThreadedByteSwap);
TEST_CHECK(test_transform_ByteSwap);
TEST_CHECK(test_Transform_par_ByteSwap);
)

0 comments on commit 82d5412

Please sign in to comment.