There are a number of things you can do to improve your code.
Use const references where practical
The parameters passed to the functions can be sped up by passing them as const
references instead of by value. Doing so tells both the compiler and other readers of the code that the passed parameter will not be altered, and allows for additional optimizations by the compiler.
Use reserve
to improve speed
Since we know that the size of the vector must be at least 33 fields, it makes sense to use reserve
to preallocate space.
Avoid constructing temporary variables
Rather than creating a std::string
temporarily to print the output, an alternative approach would be to create a function that outputs them directly to the output.
Avoid work if possible
While it sounds like it might be the life philosophy of Tom Sawyer, it's also a good idea for optimizing software for performance. For instance, since the code is looking for something specific in the fourth field, if that criterion is not met by the time the fourth field is parsed, there's no reason to continue to parse the line. One way to convey a value that may or not be there is via std::optional
which was introduced in C++17.
Results
csv.cpp
#include <fstream>
#include <string>
#include <vector>
#include <sstream>
#include <optional>
constexpr std::size_t minfields{33};
std::optional<std::vector<std::string>> splitStr(const std::string& line, const char delimiter = ',')
{
std::vector<std::string> splitLine;
splitLine.reserve(minfields);
std::istringstream ss(line);
std::string buf;
unsigned field{0};
while (std::getline(ss, buf, delimiter)) {
splitLine.push_back(buf);
if (field == 3 && buf[0] != 'E' && buf[0] != 'T') {
return std::nullopt;
}
++field;
}
if (splitLine.size() < minfields)
return std::nullopt;
return splitLine;
}
std::ostream& writeLine(std::ostream& out, const std::vector<std::string>& splitLine)
{
return out <<
splitLine.at(0) << ',' <<
splitLine.at(1) << ',' <<
splitLine.at(3) << ',' <<
splitLine.at(4) << ',' <<
splitLine.at(5) << ',' <<
splitLine.at(6) << ',' <<
splitLine.at(10) << ',' <<
splitLine.at(9) << ',' <<
splitLine.at(11) << ',' <<
splitLine.at(7) << ',' <<
splitLine.at(32) << '\n';
}
void copy_selective(std::istream& in, std::ostream& out) {
std::string line;
while(std::getline(in,line))
{
auto split = splitStr(line, ';');
if (split) {
writeLine(out, split.value());
}
}
}
int main(int argc, char* argv[])
{
if(argc >= 3) {
std::ifstream inFile(argv[1]);
std::ofstream outFile(argv[2]);
copy_selective(inFile, outFile);
}
}
I created a file with one million lines, of which 499980, or just under half, were lines meeting the criteria from the original code. Here are the timings for a million-line file on my machine (Fedora Linux, using GCC 10.1 with -O2
optimization):
$$ \begin{array}{l|c} \text{version} & \text{time (s)} \\ \hline \text{original} & 2.161 \\ \text{akki} & 1.955 \\ \text{akki w/ writeLine} & 1.898 \\ \text{Edward} & 1.456 \end{array} $$