我有一个文本文件,可以读取和处理2万行。在文本文件中,我想读取点坐标并分配给DirectX进行渲染。Snapshot of Text file
我使用了std :: ifstream,getline,stringstream来获取点坐标。生成win32程序后,开始运行,读取和将点坐标存储在数组中花费的时间太长。 (5分钟遍历20000行文本文件)。代码如下:
struct PointCoord { std::string PtName; float PtX = 0.0; float PtY = 0.0;}
PointCoord *PointPtr = NULL;
PointCoord pointcoord;
std::ifstream File_read(FileNameTXT);
while (getline(File_read, TextHandler))
{
std::istringstream iss;
std::string skip;
if (TextHandler.find(" POINT ") != std::string::npos)
{
iss.str(TextHandler);
std::string TempX, TempY;
iss >> skip;
iss >> pointcoord.PtName;
//pointcoord pass value to PointCoord
iss >> TempX;
iss >> TempY;
pointcoord.PtX = std::stof(TempX.c_str());
pointcoord.PtY = std::stof(TempY.c_str());
//dynamically store the points coordiantes
if (PointPtr == NULL)
{
PointPtr = new PointCoord[1];
//PointCoord pass value to PointPtr
PointPtr[0] = pointcoord;
Pt_Count++;
}
else
{
PointCoord *Temp = PointPtr;
PointPtr = new PointCoord[Pt_Count + 1];
for (UINT i = 0; i < Pt_Count; i++)
{
PointPtr[i] = Temp[i];
}
PointPtr[Pt_Count] = pointcoord;
Pt_Count++;
delete[]Temp;
}
}//end of loading points
}//end of getline
我还使用std :: fread在字符串缓冲区中一次读取整个文本文件,这是快速的(读取在几秒钟内完成)。然后使用类似代码的stringstream将点坐标存储在动态数组中,这也太慢了。
欢迎任何建议。非常感谢。
答案 0 :(得分:11)
此代码中最令人讨厌的不是字符串解析;而是字符串解析。对于每个新读取的点,调整目标数组的大小。您读得越多,就越糟。最终,它成为O(n ^ 2)量级的复制操作。
要让您知道这有多糟,请考虑一下basic summation of n
natural numbers,因为这就是您正在执行的对象构造,破坏和复制数量:
n(n + 1)/ 2 =(20000 * 20001)/ 2 = 200010000个对象已创建,复制和销毁
因此,字符串解析不是问题。超过2亿个对象的构造,销毁和复制,使被解析的20000行文本相形见。
您无需执行任何操作。使用适当的容器,例如std::vector
,并根据文件大小估算初始保留量。然后,只需生成点并将其移入容器即可。
执行此操作的示例,包括生成100000点(您要求的大小的5倍)的测试文件,如下所示:
代码
#include <iostream>
#include <fstream>
#include <sstream>
#include <vector>
#include <string>
#include <random>
#include <chrono>
struct Point
{
std::string name;
float x;
float y;
};
std::vector<Point> readFile(std::string const& fname)
{
std::vector<Point> res;
std::ifstream inp(fname);
if (inp.is_open())
{
// gather file size for a rough approximation of reserve
inp.seekg(0, std::ios::end);
auto flen = inp.tellg();
inp.seekg(0, std::ios::beg);
res.reserve(flen/40);
std::string line;
while (std::getline(inp, line))
{
auto pos = line.find("POINT");
if (pos != std::string::npos)
{
std::istringstream iss(line.substr(pos+5));
Point pt;
if (iss >> pt.name >> pt.x >> pt.y)
res.emplace_back(std::move(pt));
}
}
}
return res;
}
int main()
{
using namespace std::chrono;
std::mt19937 prng(std::random_device{}());
std::uniform_real_distribution<float> dist(-100.0, 100.0);
// generate test data
std::ofstream outf("testpoints.txt");
for (int i=1; i<=100000; ++i)
outf << "POINT \"" << i << "\" " << dist(prng) << ' ' << dist(prng) << '\n';
outf.close();
// rough benchmark
auto tp0 = steady_clock::now();
auto v = readFile("testpoints.txt");
auto tp1 = steady_clock::now();
std::cout << "Read " << v.size() << " points in ";
std::cout << duration_cast<milliseconds>(tp1-tp0).count() << "ms\n";
v.clear();
}
输出
在2015年双核i7 MacBook Air笔记本电脑上运行时,发布模式版本会产生以下结果:
Read 100000 points in 164ms
一个可能更合适的容器:std::deque
最后,您真正需要的是一个容器,该容器允许在末端快速插入,同时在调整大小的过程中最小化(或消除)元素复制。当然,如上面的代码所示,针对std::vector
设置储备金是这样做的一种方法。另一种选择是使用实际上专门用于最终插入的容器,同时仍然对缓存非常友好(不像std::vector
那样完美,但肯定比链接列表之类的东西更好,后者对于插入非常有用,但是 dreadful 进行枚举)。
这正是std::deque
会做的。上面的代码已更改为std::deque
,使您可以消除保留猜测,而只需在序列末尾开始猛击节点,随着序列的增长,将自动添加更多页面:
代码
#include <iostream>
#include <fstream>
#include <sstream>
#include <deque>
#include <string>
#include <random>
#include <chrono>
struct Point
{
std::string name;
float x;
float y;
};
std::deque<Point> readFile(std::string const& fname)
{
std::deque<Point> res;
std::ifstream inp(fname);
if (inp.is_open())
{
std::string line;
while (std::getline(inp, line))
{
auto pos = line.find("POINT");
if (pos != std::string::npos)
{
std::istringstream iss(line.substr(pos+5));
Point pt;
if (iss >> pt.name >> pt.x >> pt.y)
res.emplace_back(std::move(pt));
}
}
}
return res;
}
int main()
{
using namespace std::chrono;
std::mt19937 prng(std::random_device{}());
std::uniform_real_distribution<float> dist(-100.0, 100.0);
// generate test data
std::ofstream outf("testpoints.txt");
for (int i=1; i<=100000; ++i)
outf << "POINT \"" << i << "\" " << dist(prng) << ' ' << dist(prng) << '\n';
outf.close();
// rough benchmark
auto tp0 = steady_clock::now();
auto v = readFile("testpoints.txt");
auto tp1 = steady_clock::now();
std::cout << "Read " << v.size() << " points in ";
std::cout << duration_cast<milliseconds>(tp1-tp0).count() << "ms\n";
v.clear();
}
输出
Read 100000 points in 160ms
如果您的需求需要连续的 序列,则可以使用std::vector
方法。如果您只需要随机访问元素并想要快速插入末端,则std::deque
可能更合适。考虑一下,然后选择最适合您的东西。
摘要
摆脱那种可怕的扩展算法。这是代码中的痛点。将其替换为几何尺寸调整算法,并从一开始就大致估算出所需元素的数量。或使用适合于最佳末端插入的容器。无论哪种方式,它都比您现在拥有的更好。
答案 1 :(得分:1)
如果您想进一步改进,可以做两件事。
您可以逐行读取文件,而不是逐行读取文件,而是使用基于指针的C样式代码将其拆分为几行,然后将每一行拆分为多个字段。就像您现在所做的那样,可以在一个迭代中完成两个步骤。这样,您可以编写除了名称之外不会复制字符串的代码。如果使用Visual C ++,请参见CAtlFileMapping<char>
模板类。
标准的浮点解析器代码,std :: strtof中的一个,非常通用。如果您的文本为ASCII,并且数字以常规格式存储,例如-12.3456
,即您没有INF或NAN,并且没有科学记数形式的数字(如-1.23E+1
),则可以编写自己的数字strtof
版本将比标准版本快2-3倍。