如何提高矢量插入时间

How to improve vector insert time

本文关键字:插入 时间 何提高      更新时间:2023-10-16

我有CSV文件。其中包含1086098日志行。我已经编写了c++代码。在那里我搜索一些文本,并根据创建情况向各自的所有者报告问题。

实施细节

  1. 将所有文件日志加载到std::vector。

  2. 在运行时搜索矢量。

问题:std::vector中的push_back花费1683087毫秒时间。这次我该怎么改进呢。欢迎任何其他建议。

std::vector<std::string> complete_log;
bool LoadFileToVector(const std::string& str) {
std::string needle = str;
std::ifstream fin(needle.c_str());
std::string line;
bool found = false;
if (fin.is_open()) {
if (!is_empty(fin)) {
fin.exceptions(std::ifstream::badbit);
try {
while (getline(fin, line)) {
complete_log.push_back(line);
}
for (const auto& text : start_check) {
found = false;
for (auto elem : complete_log) {
if (elem.find(text) != std::string::npos) {
basic_result[text] = true;
found = true;
}
}
if (!found)
basic_result[text] = false;
}
} catch (std::ifstream::failure& FileExcep) {
std::cout << "Caught an exception = " << FileExcep.what() << std::endl;
fin.close();
return false;
} catch (...) {
std::cout << "Unkown Exception/n";
}
} else {
std::cout << "Input file "<<needle<<" is Empty" << std::endl;
fin.close();
return false;
}
fin.close();
return true;
} else {
std::cout << "Cannot open file to update map" << std::endl;
return false;
}
return true;
}

伊迪丝:我的坏我没提。我正在测量整个函数LoadFileToVector()。

我建议您:

  1. 将所有数据加载到字符串中并用于搜索。如果您将push_back与vector和字符串一起使用,则每次vector都会在内存不足时分配内存,而字符串总是分配内存。每次分配都是"新"调用,这是系统调用,需要切换到内核模式(损失了很多时间,尤其是在日志很大的情况下)
  2. 单独的下载和搜索过程只为获得良好的代码风格

由于您的问题是关于读取文件的性能,我没有改进搜索过程。但是,如果它对您来说不够好,您可以使用线程。只要不更改日志字符串,它是安全的。

#include <string>
#include <array>
#include <fstream>
#include <iostream>
/*
logs.txt
error
error1
no error
you are breathtaking
error 3
*/
std::array<std::string, 3>  start_check = { "error1", "error2", "error3" };
std::array<bool, 3>         basic_result;
std::string LoadLogs(const std::string& sFileName)
{
std::ifstream fin(sFileName.c_str());
fin >> std::noskipws;
std::string sRet = "";
if (!fin.is_open())
std::cout << "Cannot open file to update map" << std::endl;
else
sRet = std::string(std::istream_iterator<char>(fin), std::istream_iterator<char>());
if (sRet.empty())
std::cout << "Input file " << sFileName << " is Empty" << std::endl;
return sRet;
}
int main()
{
std::string sLogs = LoadLogs("logs.txt");
if (!sLogs.empty())
for (int i = 0; i < start_check.size(); i++)
basic_result[i] = sLogs.find(start_check[i]) != std::string::npos;
/*
basic_result
true
false
false
*/
return 0;
}

我为您做了一个简短的评估。

我写了一个测试程序,它首先创建一个测试文件。我把搜索字符串(整数计数器)放在这行的末尾,这样find函数的速度就最慢了。

然后我做了几个改进方法:

  1. 我打开所有编译器优化
  2. 对于矢量,我使用reserve来避免重新定位
  3. 通过设置更大的输入缓冲区,从流中读取得到了显著改善
  4. 我更改了搜索算法以避免重复迭代

有了这些,我读取了所有的行,并在800毫秒内搜索搜索字符串,所以在1秒以下。

请查看并检查,如果你能为你的解决方案实现我的一个想法

#include <vector>
#include <string>
#include <iterator>
#include <regex>
#include <fstream>
#include <iostream>
#include <fstream>
#include <chrono>
#include <map>
#include <algorithm>
constexpr size_t NumberOfExpectedLines = 1'086'098;
constexpr size_t SizeOfIOStreamBuffer = 1'000'000;
static char ioBuffer[SizeOfIOStreamBuffer];
const std::string fileName{ "r:\log.txt" };
void writeTestFile() {
if (std::ofstream ofs(fileName); ofs) {
for (size_t i = 0; i < NumberOfExpectedLines; ++i)
ofs << "text,text,text,text,text,text," << i << "n";
}
}
bool LoadFileToVector(
const std::string& fName, 
const std::vector<std::string>& searchStrings, 
std::vector<std::string>& completeLog,
std::map<std::string,bool>& basicResult) {
if (std::ifstream ifs(fName); ifs) {
// Speed up things
completeLog.reserve(NumberOfExpectedLines);
ifs.rdbuf()->pubsetbuf(ioBuffer, SizeOfIOStreamBuffer);
// Read all files and search. Terminate, if we found all search strings
for (std::string line{}; std::getline(ifs, line); ) {
// Search for at least one occurence of a search string in the line
if (auto search = std::find_if(searchStrings.begin(), searchStrings.end(),
[&line](const std::string& s) {return line.find(s) != std::string::npos; });
search != searchStrings.end()) {
// If found, save result
basicResult[*search] = true;
}
// Store read line
completeLog.push_back(std::move(line));
}
}
return  basicResult.size() > 0;
}
int main() {
// writeTestFile();
std::vector<std::string> searchStrings{"100000","500000","800000"};
std::vector<std::string> completeLog{};
std::map<std::string, bool> basicResult{};
// TIme measurement start
auto start = std::chrono::system_clock::now();
LoadFileToVector(fileName, searchStrings, completeLog, basicResult);
// Time measurement evaluation
auto end = std::chrono::system_clock::now();
auto elapsed = std::chrono::duration_cast<std::chrono::milliseconds>(end - start);
// How long did it take?
std::cout << "Elapsed time:       " << elapsed.count() << " msn";
return 0;
}

也许它会在重构时对您有所帮助

通过在指定位置的元素之前插入新元素来扩展向量,从而有效地将容器大小增加插入的元素数量。

如果并且仅当新矢量大小超过当前矢量容量时,这会导致自动重新分配分配的存储空间。

因为矢量使用数组作为其底层存储,所以在矢量末端以外的位置插入元素会导致容器将定位之后的所有元素重新定位到其新位置。与其他类型的序列容器(如list或forward_list)对同一操作执行的操作相比,这通常是一种低效的操作。

这些参数决定了插入了多少个元素以及它们被初始化为哪些值:

#include <bits/stdc++.h> 
using namespace std; 
int main() 
{ 
// initialising the vector 
vector<int> vec = { 10, 20, 30, 40 }; 

// inserts 3 at front   
auto it = vec.insert(vec.begin(), 3); 
// inserts 2 at front  
vec.insert(it, 2); 
cout << "The vector elements are: ";  
for (auto it = vec.begin(); it != vec.end(); ++it) 
cout << *it << " "; 
return 0; 
}

Shamless self-plug:我是一个兼容C++11的CSV解析器的维护者,您可以在这里找到它。它使用单独的线程从磁盘读取和解析,并进行优化以减少内存分配量。

如果你不想使用我的解析器

看起来您实际上并没有在CSV中使用分隔符,所以您可能不会从我的解析器中受益。但从我的经验教训来看,我可以看到你使用两种途径。

解决方案1:避免存储整个文件

正如其他人所提到的,您似乎正在进行逐行处理,而不需要将整个文件存储在内存中。如果是这样的话,我将把getline()放入一个字符串中(就像你在循环顶部所做的那样),处理它,然后在不使用push_back()的情况下重复。

为什么不呢

std::vector是一个围绕数组的奇特包装器,它可以做一些有用的事情,比如当数组的容量超过时进行扩展。这种方便的代价是必须复制旧数组的内容,并且必须调用malloc()才能创建更大的数组。数组越大,malloc()请求就越大。

std::string是C风格字符数组的一个奇特包装器,其工作原理与std::vector类似。正如您所看到的,创建std::vector<std::string>只是内存分配之上的内存分配,这是有问题的,因为malloc()没有针对一堆小的分配进行优化。

解决方案2:使用巨型std::string

如果出于某种原因,您必须存储整个日志并单独处理每一行,我会将所有内容连接到一个巨大的std::string中,并使用一个单独的数组来存储每一新行开始的索引。通过这种方式,您可以在每一行上创建std::string_view。我会调用std::string::reserve()来防止任何不必要的重新分配。

根据我的经验,这种方法比使用具有大量std::strings的std::vector要快得多。

解决方案2+:线程

您的问题可能会被重新安排为生产者/消费者模式,即一个线程将字符串推入缓冲区,另一个线程对其执行搜索。如果其他建议不起作用,我个人只会使用多个线程,因为减少动态内存分配会对性能产生巨大的影响。

其他注意事项

  • 由于您似乎只关心某个搜索词是否在文本中出现一次,因此一旦找到该词,我就会将其从搜索列表中删除。例如,如果您在前100行文本中找到10/15个搜索词,并且您有价值1000000行的日志,那么您将不必执行大约10*1000000=100000个find()操作
  • 避免在C++中使用全局变量。它们只有少数几个有效的用例,而这不是其中之一

如果您想保持现有的格式,并且始终知道矢量的行数,可以使用.reserve()

示例:

std::vector<std::string> complete_log;
bool LoadFileToVector(const std::string& str) {
std::string needle = str;
std::ifstream fin(needle.c_str());
std::string line;
bool found = false;
if (fin.is_open()) {
if (!is_empty(fin)) {
fin.exceptions(std::ifstream::badbit);
complete_log.reserve(1086098);
try {
while (getline(fin, line)) {
complete_log.push_back(line);
}
for (const auto& text : start_check) {
found = false;
for (auto elem : complete_log) {
if (elem.find(text) != std::string::npos) {
basic_result[text] = true;
found = true;
}
}
if (!found)
basic_result[text] = false;
}
} catch (std::ifstream::failure& FileExcep) {
std::cout << "Caught an exception = " << FileExcep.what() << std::endl;
fin.close();
return false;
} catch (...) {
std::cout << "Unkown Exception/n";
}
} else {
std::cout << "Input file "<<needle<<" is Empty" << std::endl;
fin.close();
return false;
}
fin.close();
return true;
} else {
std::cout << "Cannot open file to update map" << std::endl;
return false;
}
return true;
}

注意添加complete_log.reserve(1086098);

然而,其他建议正确地指出,您可以通过简单地处理每一行来避免存储所有行,而存储它。

这可能涉及到翻转底部的两个循环。这可能不正确(我没有编译它),但它看起来像这样:

std::vector<std::string> complete_log;
bool LoadFileToVector(const std::string& str) {
std::string needle = str;
std::ifstream fin(needle.c_str());
std::string line;
bool found = false;
if (fin.is_open()) {
if (!is_empty(fin)) {
fin.exceptions(std::ifstream::badbit);
for (const auto& text : start_check) {
basic_result[text] = false;
}
try {
while (getline(fin, line)) {
for (const auto& text : start_check) {    
if (line.find(text) != std::string::npos) {
basic_result[text] = true;
}
}
}

} catch (std::ifstream::failure& FileExcep) {
std::cout << "Caught an exception = " << FileExcep.what() << std::endl;
fin.close();
return false;
} catch (...) {
std::cout << "Unkown Exception/n";
}
} else {
std::cout << "Input file "<<needle<<" is Empty" << std::endl;
fin.close();
return false;
}
fin.close();
return true;
} else {
std::cout << "Cannot open file to update map" << std::endl;
return false;
}
return true;
}