如何在 C++ 中使 csv 文件运行得更快
Posted
技术标签:
【中文标题】如何在 C++ 中使 csv 文件运行得更快【英文标题】:how to make csv file run faster in c++ 【发布时间】:2021-12-29 05:42:12 【问题描述】:我在 CSV 文件中有一百万个条目,需要加载它。但是,完成加载大约需要 2 分钟。我需要解决这个问题以使数据加载更快。有没有更好的方法可以解决?所以我可以研究并尝试修复它。感谢您的帮助。
CSVReader.h
#pragma once
#include "OrderBookEntry.h"
#include <vector>
#include <string>
class CSVReader
public:
CSVReader();
static std::vector<OrderBookEntry> readCSV (std::string csvFile);
;
CSVReader.cpp
#include "CSVReader.h"
#include <iostream>
#include <fstream>
CSVReader::CSVReader()
std::vector<OrderBookEntry> CSVReader::readCSV(std::string csvFilename)
std::vector<OrderBookEntry> entries;
std::ifstream csvFilecsvFilename;
std::string line;
if (csvFile.is_open())
while (std::getline(csvFile, line))
try
OrderBookEntry obe = stringsToOBE(tokenise(line, ','));
entries.push_back(obe);
catch(const std::exception& e)
std::cout << "CSVReader::readCSV bad data" << std::endl;
//end of while
std::cout << "Successfully read " << entries.size() << " entries" << std::endl;
return entries;
std::vector<std::string> CSVReader::tokenise(std::string csvLine, char separator)
std::vector<std::string>tokens;
signed int start, end;
std::string token;
start = csvLine.find_first_not_of(separator, 0);
do
end = csvLine.find_first_of(separator, start);
if (start == csvLine.length() || start == end) break;
if (end >= 0) token = csvLine.substr(start, end - start);
else token = csvLine.substr(start, csvLine.length() - start);
tokens.push_back(token);
start = end + 1;
while (end > 0);
return tokens;
OrderBookEntry CSVReader::stringsToOBE(std::vector<std::string>tokens)
double price, amount;
if (tokens.size() != 5)
std::cout << "Bad Input" << std::endl;
throw std::exception;
try
//we have 5 tokens
price = std::stod(tokens[3]);
amount = std::stod(tokens[4]);
catch(const std::exception& e)
std::cout << "Bad Float!" << tokens[3] << std::endl;
std::cout << "Bad Float!" << tokens[4] << std::endl;
throw;
OrderBookEntry
obeprice,amount,tokens[0],tokens[1],OrderBookEntry::stringToOrderBookType(tokens[2]);
return obe;
【问题讨论】:
您需要一次将所有条目加载到主内存中吗? “一百万个条目”听起来您应该使用数据库,而不是电子表格。 了解如何将变量作为引用传递给函数,而不是在它们很大时复制它们。stringsToOBE
应该是 stringsToOBE(const std::vector<string>& tokens)
。
1.重新考虑程序的架构。 2. 将复制成本高昂的变量作为 const 引用传递。
1.你写的并不理想,但在现代机器上它应该更快。也许你可以提供一个测试文件。 2. 确保在发布配置中测试您的程序。 Debug 和 Release 配置之间的差异可能很大。 3.你没看懂throw/catch. 4.
strtod`不扔。`
如果您知道文件的确切大小以及 std::vector 的大小,据我了解,最有效的方法是在您的 std::vector 上使用保留。因此,如果您有 1,000,000 行“条目”,请执行 entries.reserve(1000000);预分配内存。根据我的经验,这比使用 std::vector.push_back(); 更快。因为 push_back() 必须添加内存 1,000,000 次,而不是使用 reserve() 一次完成所有操作。您还需要一个虚拟计数器,以便每次读取一行并向其中添加数据时都可以通过向量进行索引。
【参考方案1】:
正如 cmets 中提到的,首先应该优化的是按值传递参数。即,应该有const std::string& arg_name
而不是std::string arg_name
等。
那么,std::vector
的使用也不是那么理想。这是因为vector
的性质:它连续存储其元素。这意味着如果在某些时候没有内存可以以这种方式(连续地)附加下一个元素,那么所有现有元素都应该重新分配到一个新位置。因此,如果 vector
的使用不是非常需要,请考虑将其替换为 std::list
或其他内容。
然后,我会像这样简化CSVReader::tokenise
方法:
std::list<std::string> CSVReader::tokenise(const std::string& str, char sep)
std::list<string> result;
std::string::size_type cur = 0;
for(auto pos = str.find_first_of(sep); pos != string::npos; cur = pos + 1, pos = str.find_first_of(sep, cur))
result.emplace_back(str, cur, pos - cur);
result.emplace_back(str, cur);
return result;
然后,我将结合 tokenise
和 stringsToOBE
方法。
一般来说,查看循环中执行的代码。
【讨论】:
std::list
很少比std::vector
好,在向量上调用reserve
会是更好的方法
@AlanBirtles,我建议以list
为例。应根据项目的需要选择容器。【参考方案2】:
想想你在解析一行的时候做了什么样的操作,效率低下的原因是什么。
例如,
std::vector<std::string> CSVReader::tokenise(std::string csvLine, char separator)
它通过复制接受csvLine
。复制字符串可能会很慢,因为它可能涉及内存分配。
然后你从一个字符串创建一个字符串向量......创建一个std::string_view
的向量不是更好吗?所以你不要复制字符串的每个元素。为了使函数更安全,要求标记化也接受 std::string_view
而不是 std::string
或 const std::string&
接下来,通过审核CSVReader::stringsToOBE(std::vector<std::string>tokens)
,
显然我看到你实际上需要大小为 5。然后将一些元素解析为浮点数并将一些元素复制为字符串,另一个转换为枚举。
要求输入为std::array<std::string_view,5>
不是更有意义吗?编译时强制大小为 5 而不是测试它?为了适应这种变化,您可以将tokenize
转换为接受数字n
作为模板参数的模板,并根据分隔符将std::string_view
转换为std::array<std::string_view,n>
,最后验证它是字符串视图。
由于消除了不必要的std::vector
,这节省了另一个动态分配。
但是为什么你从ifstream
得到一个字符串然后解析它并分别转换每个元素呢?
为什么不直接从流中直接提取元素而不创建中间字符串呢?说,添加函数OrderBookEntry CSVReader::extractOBEElement(std::istream& stream)
?并且只需使用流的原生函数,如>>
?
它可能会工作并且效率更高,但众所周知流很慢,因此首先生成字符串可能确实更可取。
【讨论】:
以上是关于如何在 C++ 中使 csv 文件运行得更快的主要内容,如果未能解决你的问题,请参考以下文章