The simple program reads in some channel data and parameters from text files and should be able to generically transform the data using natural math syntax.
I am primarily concerned about performance. Any improvements in readability, correctness, and defensive coding style will also be appreciated.
main.cpp:
#include <string>
#include <iostream>
#include <stdexcept>
#include "channel.hpp"
#include "parameter.hpp"
using namespace std;
using namespace string_literals;
using namespace channel_processing_system;
int main()
{
try
{
Channel<double> X("channels.txt"s, ',');
Parameters<double> parameters("parameters.txt"s, ',');
Channel<double> Y = X * parameters['m'] + parameters['c'];
Channel<double> A = 1.0 / X;
Channel<double> B = A + Y;
parameters.InsertParameter('b', B.Mean());
Channel<double> C = A + parameters['b'];
std::cout << "The value of b is: " << parameters['b'] << "\n";
return 0;
}
catch (const std::exception& e)
{
std::cerr << e.what() << std::endl;
}
catch (...)
{
std::cerr << "Program terminated due to an unhandled exception." << std::endl;
}
return -1;
}
channel.hpp:
#include <vector>
#include <string>
#include <fstream>
#include <ostream>
#include <sstream>
#include <iomanip>
#include <cassert>
#include <numeric>
#include <stdexcept>
#include <algorithm>
namespace channel_processing_system
{
template <typename DataType>
class Channel
{
const std::string input_file_;
const char delimiter_ = ',';
std::vector<DataType> data_;
char channel_name_;
void ReadFile()
{
try
{
assert(!input_file_.empty());
std::fstream channel(input_file_);
if (!channel.good())
throw std::runtime_error("Could not open channel input file: " + input_file_);
channel >> std::skipws;
channel >> channel_name_;
assert(channel_name_ >= 'A' && channel_name_ <= 'Z');
DataType value;
char delimiter;
while (channel >> delimiter >> value && delimiter == delimiter_)
{
data_.emplace_back(value);
};
assert(data_.size() > 0); // Reading channel from file was not successful
}
catch (const std::exception& e)
{
throw std::runtime_error("Something went wrong whilst trying to parse channel input file: " + std::string(e.what()));
}
}
public:
Channel() = default;
Channel(const std::string& input_file, const char delimiter) :
input_file_(input_file), delimiter_(delimiter)
{
ReadFile();
}
template<typename RHSType>
Channel operator+(const RHSType& rhs)
{
Channel result;
result.data_.reserve(data_.size());
if constexpr (std::is_scalar_v<RHSType>)
{
std::for_each(data_.begin(), data_.end(),
[&](const auto& src) { result.data_.emplace_back(src + rhs); });
}
else
{
if constexpr (std::is_same_v<RHSType, Channel<DataType>>)
{
std::transform(rhs.data_.begin(), rhs.data_.end(), this->data_.begin(),
std::back_inserter(result.data_), std::plus<DataType>());
}
else
{
static_assert(false, "An adaption of operator+ is not provided for the instantiated type.");
}
}
return result;
}
template<typename Scalar>
Channel operator*(const Scalar& rhs)
{
static_assert(std::is_scalar_v<Scalar>, "The operator* is only defined for arguments of scalar type.");
Channel result;
result.data_.reserve(data_.size());
std::for_each(data_.begin(), data_.end(),
[&](const auto& src) { result.data_.emplace_back(src * rhs); });
return result;
}
DataType Mean() const
{
assert(data_.size() > 0);
return std::accumulate(data_.begin(), data_.end(), 0.0) / data_.size();
}
template <typename DataType_, typename Scalar> friend Channel<DataType_> operator/(const Scalar& lhs, const Channel<DataType_>& rhs);
template <typename DataType_> friend std::ostream& operator<<(std::ostream& os, Channel<DataType_> rhs);
};
template <typename DataType, typename Scalar>
Channel<DataType> operator/(const Scalar& lhs, const Channel<DataType>& rhs)
{
static_assert(std::is_scalar_v<Scalar>, "The non-member operator/ is only defined for arguments of scalar type.");
assert(!std::any_of(rhs.data_.begin(), rhs.data_.end(),
[](const auto& src) {return src == (DataType)0; }));
Channel<DataType> result;
result.data_.reserve(rhs.data_.size());
std::for_each(rhs.data_.begin(), rhs.data_.end(), [&](const auto& src) { result.data_.emplace_back(lhs / src); });
return result;
}
template <typename DataType>
std::ostream& operator<<(std::ostream& os, Channel<DataType> rhs)
{
os << "Channel " << rhs.channel_name_ << ": ";
for (const auto& element : rhs.data_)
{
os << element << ", ";
}
os << std::endl;
return os;
}
}
parameter.hpp:
#pragma once
#include <map>
#include <string>
#include <fstream>
#include <sstream>
#include <iomanip>
#include <cassert>
#include <utility>
#include <stdexcept>
namespace channel_processing_system
{
template <typename ValueType, typename KeyType = char>
class Parameters
{
const std::string input_file_;
const char delimiter_ = ',';
std::map<KeyType, ValueType> parameters_;
void ReadFile()
{
try
{
assert(!input_file_.empty());
std::fstream file(input_file_);
if (!file.good())
throw std::runtime_error("Could not open parameter input file: " + input_file_);
std::string line;
while (std::getline(file, line))
{
assert(line.size() >= 3);
std::stringstream line_ss{ line };
line_ss >> std::skipws;
char name;
line_ss >> name;
assert(name >= 'a' && name <= 'z');
char delimiter;
ValueType value;
line_ss >> delimiter >> value;
assert(delimiter == delimiter_);
InsertParameter(name, value);
}
}
catch (const std::exception& e)
{
throw std::runtime_error("Something went wrong whilst trying to parse parameter input file: " + std::string(e.what()));
}
}
public:
Parameters() = delete;
Parameters(const std::string& input_file, const char delimiter)
: input_file_(input_file), delimiter_(delimiter)
{
ReadFile();
}
ValueType operator[](const KeyType& key) const { return parameters_.at(key); }
void InsertParameter(const KeyType& key, const ValueType& value)
{
const auto check = parameters_.insert(std::make_pair(key, value));
assert(check.second != false); // Tried to insert parameter: key which already exists
}
};
}
Example test data:
channel.txt
X, 0.814723686393179, 0.905791937075619, 0.126986816293506, 0.913375856139019, 0.63235924622541, 0.0975404049994095, 0.278498218867048, 0.546881519204984, 0.957506835434298
parameter.txt
m, 3
c, -1
I have used a small set for this example, but it will need to operate on much larger data sets.
-
5\$\begingroup\$ Can you provide some test cases so that we can get an idea of the syntax? \$\endgroup\$L. F.– L. F.2019年07月24日 03:14:14 +00:00Commented Jul 24, 2019 at 3:14
1 Answer 1
I don't see anything obviously slow, but neither do I see high performance code or multi-threaded compute. It's just straight forward loops.
If you care about performance you should use some math library that uses SSE/AVX/other types of SIMD- instructions to implement data level parallelism. Then you can shard your input data so that you can split the work out over many threads. The number of threads should be CPU threads + 1 in order to maximally exploit instruction level parallelism.
There are many good libraries out there. Personally, I use Eigen by default because it performs well, has a syntax that I like and is header only, no need to worry about linking.
In your case, your Channel class can be straight up replaced by the Eigen::VectorXd type that's a specialization of a Nx1 matrix. In general your application seems to translate well to matrix operations and will easily translate to using some linear algebra library.
I'm typing on a phone so I'll stop short of reviewing the code for other aspects as I believe that you should throw most of it away and use a library as noted above.