我已经将这个简单的方法从C#转换为C ++。它读取路径表并填充整数列表(或整数向量的向量)。
路径表中的示例行类似于
0 12 5 16 n
我意识到有更好的方法可以做到这一点,但是现在我只是想知道为什么我的C ++代码更长时间地 所以 。例如10分钟而不是C#版本10秒。这是我的C ++代码。我猜我做错了一些事。
//Parses the text path vector into the engine
void Level::PopulatePathVectors(string pathTable)
{
// Read the file line by line.
ifstream myFile(pathTable);
for (unsigned int i = 0; i < nodes.size(); i++)
{
pathLookupVectors.push_back(vector<vector<int>>());
for (unsigned int j = 0; j < nodes.size(); j++)
{
string line;
if (getline(myFile, line)) //Enter if a line is read successfully
{
stringstream ss(line);
istream_iterator<int> begin(ss), end;
pathLookupVectors[i].push_back(vector<int>(begin, end));
}
}
}
myFile.close();
}
这是C#版本:
private void PopulatePathLists(string pathList)
{
// Read the file and display it line by line.
StreamReader streamReader = new StreamReader(pathList);
for (int i = 0; i < nodes.Count; i++)
{
pathLookupLists.Add(new List<List<int>>());
for (int j = 0; j < nodes.Count; j++)
{
string str = streamReader.ReadLine();
pathLookupLists[i].Add(new List<int>());
//For every string (list of ints) - put each one into these lists
int count = 0;
string tempString = "";
while (str[count].ToString() != "n") //While character does not equal null terminator
{
if (str[count].ToString() == " ") //Character equals space, set the temp string
//as the node index, and move on
{
pathLookupLists[i][j].Add(Convert.ToInt32(tempString));
tempString = "";
}
else //If characters are adjacent, put them together
{
tempString = tempString + str[count];
}
count++;
}
}
}
streamReader.Close();
}
对不起,这是如此具体,但我很难过。
编辑 - 很多人都说过他们已经测试过这段代码了,它们只需要几秒钟。我所知道的是,如果我注释掉对此函数的调用,程序会在几秒钟内加载。使用函数调用需要5分钟。几乎一模一样。我真的很难过。问题是什么?
以下是它正在使用的PathTable。
编辑 - 我尝试在程序中自行运行该函数,花了几秒钟,但我恐怕不知道如何解决这个问题。显然这不是代码。会是什么呢?我检查了它被调用的地方,看看是否有多个电话,但没有。它位于游戏关卡的构造函数中,只调用一次。编辑 - 我知道代码不是最好的,但这不是重点。它可以自行运行 - 大约3秒钟,这对我来说很好。我试图解决的问题是为什么在项目中需要这么长时间。
编辑 - 除了主游戏循环之外,我评论了所有游戏代码。我将该方法放入代码的初始化部分,该部分在启动时运行一次。除了设置一个窗口的几个方法之外,它现在与只有方法的程序几乎相同,只有它仍然需要大约5分钟才能运行。现在我知道它与pathLookupVectors的依赖关系无关。此外,我知道这不是计算机开始写入硬盘驱动器的内存,因为当慢速程序正在运行该方法时,我可以打开另一个Visual Studio实例并在完成的同时运行单个方法程序很快。我意识到问题可能是一些基本的设置,但我没有经历过这样的道歉,如果这确实令人失望地成为原因。我仍然不知道为什么花了这么长时间。
答案 0 :(得分:9)
我使用Very Sleepy(Visual C++ 2010,32位Windows XP)分析了代码。我不知道我的输入数据有多相似,但无论如何都是结果:
39%的时间花在basic_istream :: operator&gt;&gt;上
12%basic_iostream :: basic_iostream
9%运营商+
8%_Mutex :: Mutex
5%的getline
5%basic_stringbuf :: _ Init
4%locale :: _ Locimp :: _ Addfac
4%vector :: reserve
4%basic_string :: assign
3%运营商删除
2%basic_Streambuf :: basic_streambuf
1%Wcsxfrm
5%的其他功能
有些东西似乎来自内联调用,因此有点难以说出它实际来自哪里。但你仍然可以得到这个想法。这里应该做I / O的唯一事情是getline,只需要5%。其余的是流和字符串操作的开销。 C ++流很慢。
答案 1 :(得分:7)
代码中的while
循环似乎非常混乱且很长,因为它以不需要的方式执行操作:
一个简单而快速的等效代码就是:
int result;
stringstream ss(line);
while ( ss >> result ) //reads all ints untill it encounters non-int
{
pathLookupVectors[i][j].push_back(result);
}
在C ++中,这样的循环也是惯用的。或者代替这个手动循环,您可以编写使用std::copy
1 :
std::copy(std::istream_iterator<int>( ss ),
std::istream_iterator<int>(),
std::back_inserter(pathLookupVectors[i][j]));
1。它取自@ David的评论。
如果这样做,甚至更好,当你push_back
向量本身时:
if (getline(myFile, line)) //enter if a line is read successfully
{
stringstream ss(line);
std::istream_iterator<int> begin(ss), end;
pathLookupVectors[i].push_back(vector<int>(begin, end));
}
完成!
答案 2 :(得分:7)
根据您的更新,很明显您自己发布的功能不会导致性能问题,因此虽然您可以通过多种方式对其进行优化,但这似乎无济于事。
我认为每次运行代码时都可以重现这个性能问题,对吗?然后我建议您进行以下测试:
如果您正在以调试模式编译程序(即没有优化),则重新编译以进行发布(完全优化,支持速度),看看是否有所作为。
要检查是否在此可疑功能上花费了额外时间,您可以在包含时间戳的函数的开头和结尾添加printf语句。如果这不是一个控制台应用程序,但GUI应用程序和printfs不会去任何地方,那么写入日志文件。如果您使用的是Windows,则可以使用OutputDebugString并使用调试程序捕获printfs。如果您使用的是Linux,则可以使用syslog写入系统日志。
使用源代码分析器确定所花费的时间。如果调用此函数与否之间的差异是几分钟,那么分析器肯定会给出发生了什么的线索。如果你在Windows上,那么Very Sleepy是一个不错的选择,如果你在Linux上,你可以使用OProfile。
更新:所以你说发布版本很快。这可能意味着您在此函数中使用的库函数具有较慢的调试实现。 STL就是这样知道的。
我确定您需要调试应用程序的其他部分,并且您不希望等待所有这些时间以便在调试模式下完成此功能。此问题的解决方案是在发布模式下构建项目,但以下列方式更改发布配置:
仅对要调试的文件禁用优化(确保优化至少对具有慢速功能的文件保持启用状态)。要禁用文件的优化,请在Solution Explorer中选择该文件,右键单击,选择Properties,然后转到Configuration Properties | C / C ++ / Optimization。查看该页面中的所有项目是如何为Debug构建设置的,并复制Release版本中的所有项目。对您希望调试器可用的所有文件重复此操作。
启用要生成的调试信息(pdb文件)。为此,请选择Solution Explorer顶部的Project,右键单击,选择Properties。然后转到Configuration Properties | Linker | Debugging并将Debug构建中的所有设置复制到Release版本中。
通过上述更改,您将能够调试上面配置的发布二进制文件的部分,就像在调试版本中一样。
完成调试后,您需要重新设置所有这些设置。
我希望这会有所帮助。
答案 3 :(得分:4)
我不确定这里发生了什么,但我看到了一些可以优化代码的方法。如果这不能帮助你,那么可能会有其他事情发生。
你的琴弦有多大?当您在C ++版本中传递它们时,您正在制作副本,因为您正在“按值传递”。尝试通过常量引用传递它:
void Level::PopulatePathVectors(const string &pathTable)
这会通过引用传递对象,这意味着它不会复制。然后,习惯上将其设为const
以确保它不会在您的函数中被修改。
使用.append
或+=
扩展tempString
。我相信你正在创建一个新的字符串对象,然后只用+
替换旧的字符串对象,而+=
和.append
将修改当前的字符串对象:
tempString.append(line[count]);
您还可以通过在顶部声明变量然后重新分配它们来调整性能。这将阻止它们每次都重新创建。例如,将string line;
放在for循环之前,因为它会被覆盖。
您可以执行此操作,例如使用tempString
。
答案 4 :(得分:4)
以下是我没见过的其他人提及的一些事情。它们有点含糊不清,但由于无法重现事物,因此很难详细说明所有内容。
穷人的分析。
代码运行时,只需继续中断即可。通常你会一遍又一遍地看到相同的堆栈帧。
开始评论内容。如果你注释掉你的分裂并立即完成,那么它非常清楚从哪里开始。
有些代码是依赖的,但你可以将完整的文件读入内存然后进行解析,以便在花费时间的地方创建一个明显的分离。如果两者都是独立完成的,那么它可能就是互动。
<强>缓冲强>
我没有看到你的阅读缓冲。如果要将任何内容写入磁盘,这一点就变得尤为重要。磁盘上的手臂将在您的读取位置之间来回跳转,然后写入位置等。
虽然它看起来不像你在这里写的,但你的主程序可能有更多的内存被使用。在达到最高水位后,操作系统可能会开始将部分内存分页到磁盘。当你正在进行分页时,你会逐行阅读。
通常,我会设置一个简单的迭代器接口来验证一切正常。然后在它周围写一个装饰器,一次读500行。标准流还内置了一些缓冲选项,这些选项可能更好用。我猜他们的缓冲默认值非常保守。
<强>预订。强>
当您还使用std::vector::push_back
时, std::vector::reserve
效果最佳。如果你可以在进入紧密循环之前使大部分内存可用,那么你就赢了。你甚至不必确切知道多少,只是猜测。
您也可以通过此方式获得 std::vector::resize
的效果,因为std::vector::resize
使用alloc
而std::vector::push_back
将使用realloc
最后一点是有争议的,尽管我已经读过了。我没有理由怀疑我错了,但我必须做更多的研究来确认或否认。
然而,如果你使用reserve,push_back可以更快地运行。
字符串拆分。
在处理gb +文件时,我从未见过一个高性能的C ++迭代器解决方案。不过,我还没有特别试过那个。我猜测为什么他们倾向于做很多小的分配。
以下是我通常使用的参考资料。
Split array of chars into two arrays of chars
std::vector::reserve
的建议适用于此处。
我更喜欢boost::lexical_cast
来解决维护问题的实现,但我不能说它的性能或多或少比流实现。我会说实际上看到对流使用的错误检查是非常罕见的。
STL shenanigans。
我故意对这些模糊不清,抱歉。我经常编写避免这些条件的代码,尽管我确实记得同事告诉我的一些试验和磨难。使用STLPort可以完全避免这些问题。
在某些平台上,默认情况下,使用流操作会启用一些奇怪的线程安全性。所以我发现轻微的std :: cout用法绝对会破坏算法的性能。你在这里没有任何东西,但如果你在另一个可能造成问题的线程中进行登录。我在另一条评论中看到8% _Mutex::Mutex
,这可能与其存在有关。
退化的STL实现甚至可能在词法解析流操作中遇到上述问题,这似乎是合理的。
某些容器存在奇怪的性能特征。我没有遇到过矢量问题,但我真的不知道istream_iterator内部使用了什么。过去,我通过一个行为不当的算法来追踪一个std::list::size
调用,例如使用GCC对列表进行完全遍历。我不知道新版本是否不那么疯狂。
通常愚蠢的SECURE_CRT愚蠢应该被愚蠢地照顾。我想知道这是微软认为我们想花时间做的事情吗?
答案 5 :(得分:3)
随着容器的增长,List.Add
和vector::push_back
都会不时地重新分配内存。 C ++向量按值存储子向量,因此所有数据(在您的情况下看起来都很大)会一次又一次地被复制。相反,C#list通过引用存储子列表,因此在重新分配期间不会复制子列表的数据。
典型的矢量实现在重新分配期间将其容量加倍。因此,如果您有100万行,则子向量将被复制 log(2,1000000)≈10次。
C ++ 11中引入的 移动语义应该可以消除这种影响。在此之前,请尝试vector< shared_ptr< vector<int> > >
,list< vector<int> >
,或者,如果您事先了解未来的尺寸,请使用vector::reserve()
以避免重新分配。
答案 6 :(得分:1)
尚未测试代码,但通常会加载多少ints
?考虑每个vectors
到达其capacity
时会发生什么。 vector
效率低下 - 我相信O(n)。 C#的List
没有这种行为。
考虑使用std::deque
,std::list
或其他具有更好增长行为的容器。有关详细信息,请参阅此article。
答案 7 :(得分:1)
如果你有非常多的元素,每次向量被推回时你都会受到重新分配和复制的惩罚。尝试在C ++中使用不同的容器。
答案 8 :(得分:1)
由于你的函数本身并不慢 1 ,程序运行缓慢的原因必须是在填充pathLookupVectors
时使用此函数乘积的某些代码会变慢。
我认为在您的程序上运行分析器是查找的最佳方式,但您也可以查看代码并查找依赖于pathLookupVectors
的每段代码,并考虑它是否可能成为瓶颈你正在寻找。
<子> 1。建立在您最新的编辑中。