以下是一些代码,在进行一些测量后是一个相当大的瓶颈:
//-----------------------------------------------------------------------------
// Construct dictionary hash set from dictionary file
//-----------------------------------------------------------------------------
void constructDictionary(unordered_set<string> &dict)
{
ifstream wordListFile;
wordListFile.open("dictionary.txt");
std::string word;
while( wordListFile >> word )
{
if( !word.empty() )
{
dict.insert(word);
}
}
wordListFile.close();
}
我正在阅读约200,000字,这在我的机器上大约需要240毫秒。这里使用ifstream
是否有效?我可以做得更好吗?我正在阅读mmap()
实现,但我不是100%理解它们。输入文件只是带有* nix行终止的文本字符串。
编辑:后续问题,了解建议的替代方案: 任何替代方案(减去增加流缓冲区大小)是否意味着我编写了一个解析每个字符的解析器?我有点像流的简单语法,但如果我需要速度,我可以重新写一些更细节的东西。将整个文件读入内存是一个可行的选择,它只有2mb左右。
编辑#2:我发现对我来说减速是由于设置插入,但对于那些仍然有兴趣加快逐行文件IO的人,请阅读在这里回答并查看Matthieu M.'s continuation on the topic.
答案 0 :(得分:8)
在我的系统上快速分析(linux-2.6.37,gcc-4.5.2,用-O3编译)表明I / O不是瓶颈。无论是使用fscanf
进入char数组,然后使用dict.insert()还是operator>>
,如同您的确切代码一样,它需要大约相同的时间(读取240k字文件需要155 - 160 ms)。 / p>
在代码中用std::unordered_set
替换gcc的std::vector<std::string>
会将执行时间减少到45毫秒(fscanf
) - 55毫秒(operator>>
)。尝试分析IO并单独设置插入。
答案 1 :(得分:4)
通常,您可以通过增加缓冲区大小来获得更好的性能。
在构建ifstream
之后,您可以使用以下命令设置其内部缓冲区:
char LocalBuffer[4096]; // buffer
std::ifstream wordListFile("dictionary.txt");
wordListFile.rdbuf()->pubsetbuf(LocalBuffer, 4096);
注意:如果rdbuf
的构造成功
ifstream
的结果保证不为空
根据可用内存的不同,强烈建议您尽可能扩大缓冲区,以限制与HDD的交互和系统调用的数量。
我使用自己的一点基准进行了一些简单的测量,你可以找到下面的代码(我对评论家很感兴趣):
SLES 10(sp 3)上的gcc 3.4.2
C :9.52725e + 06
C ++ :1.11238e + 07
差异:1.59655e + 06
这使得 17%的速度减慢。
这考虑到了:
locale
所以,我们可以争辩说流很慢......但请不要丢弃随机代码并抱怨它很慢,优化很难。
对应代码,其中benchmark
是我自己的一个小实用工具,它使用gettimeofday
来衡量重复执行的时间(此处启动50次迭代)。
#include <fstream>
#include <iostream>
#include <iomanip>
#include <cmath>
#include <cstdio>
#include "benchmark.h"
struct CRead
{
CRead(char const* filename): _filename(filename) {}
void operator()()
{
FILE* file = fopen(_filename, "r");
int count = 0;
while ( fscanf(file,"%s", _buffer) == 1 ) { ++count; }
fclose(file);
}
char const* _filename;
char _buffer[1024];
};
struct CppRead
{
CppRead(char const* filename): _filename(filename), _buffer() {}
enum { BufferSize = 16184 };
void operator()()
{
std::ifstream file(_filename);
file.rdbuf()->pubsetbuf(_buffer, BufferSize);
int count = 0;
std::string s;
while ( file >> s ) { ++count; }
}
char const* _filename;
char _buffer[BufferSize];
};
int main(int argc, char* argv[])
{
size_t iterations = 1;
if (argc > 1) { iterations = atoi(argv[1]); }
char const* filename = "largefile.txt";
CRead cread(filename);
CppRead cppread(filename);
double ctime = benchmark(cread, iterations);
double cpptime = benchmark(cppread, iterations);
std::cout << "C : " << ctime << "\n"
"C++: " << cpptime << "\n";
return 0;
}
答案 2 :(得分:2)
将整个文件一次性读入内存然后对其进行操作可能会更快,因为它可以避免重复返回磁盘读取另一个块。
0.25秒实际上是个问题吗?如果你不打算加载更大的文件是否需要让它更快,如果它使它更不易读?
答案 3 :(得分:2)
C ++和C库同样快速地从磁盘读取内容并且已经缓冲以补偿磁盘I / O延迟。你不会通过添加更多缓冲来加快速度。
最大的区别在于C ++流基于语言环境进行大量操作。角色转换/标点符号等等。
因此,C库会更快。
由于某种原因,链接的问题已被删除。 所以我在这里提供相关信息。链接的问题是关于C ++中隐藏的功能。
虽然技术上不是STL的一部分 流库是标准C ++库的一部分。
对于溪流:
区域设置。
很少有人真正费心去学习如何正确设置和/或操纵流的区域设置。
第二个最酷的东西是迭代器模板 对我来说最特别的是流迭代器,它基本上将流转换为非常基本的容器,然后可以与标准算法结合使用。
示例:
等
示例:
答案 4 :(得分:2)
我的系统(3.2.0-52-generic,g ++ - 4.7(Ubuntu / Linaro 4.7.3-2ubuntu1~12.04)4.7.3,用-O2编译如果没有指定,CPU: i3-2125)
在我的测试用例中,我使用了295068个单词字典(因此,单词比你的单词多10万个):http://dl.dropboxusercontent.com/u/4076606/words.txt
从时间复杂的角度来看:
实用技巧:
注意:我没有刷新我的操作系统缓存&amp;硬盘缓存。最后一个我无法控制,但第一个可以控制:
sync; sudo sh -c 'echo 3 > /proc/sys/vm/drop_caches'
此外,我没有省略那些包含大量上下文切换等的测量。因此,有足够的空间来做更好的测量。
14-16 ms 从文件中读取&amp;将数据插入 2D字符数组(读取和插入)n次
65-75 ms 使用二进制搜索搜索所有字词(搜索n次):
总计= 79-91 ms
61-78 ms 从文件中读取&amp;将数据插入 unordered_set字符数组(读取和插入)n次
7-9 ms 到按哈希搜索n次
总计= 68-87 ms
如果您搜索的次数比插入的次数多,则选择哈希表(unordered_set),否则使用二进制搜索(使用简单数组)。
使用-O2编译: 157-182 ms
使用-O0编译(如果省略-O标志,默认情况下“-O”级别也为0): 223-248 ms
因此,编译器选项也很重要,在这种情况下,它意味着 66 ms的速度提升。你没有指定任何一个。所以,我最好的猜测是你没用过它。当我试着回答你的主要问题时。
使用-O2编译: 142-170 ms。与原始代码相比,速度提升~12-15 ms 。
使用-O0编译(如果省略-O标志,默认情况下“-O”级别也为0): 213-235 ms。与原始代码相比,速度提升~10-13 ms 。
使用-O2编译: 99-121- [137] ms。 〜33-43- [49] ms速度提升与原始代码相比。
为您的特定数据输入实现您自己的哈希函数。使用char数组而不是STL字符串。完成后,只能使用直接OS I / O编写代码。正如您所注意到的(从我的测量中也可以看出)数据结构是瓶颈。如果介质非常慢,但CPU速度很快,请压缩文件,在程序中解压缩。
我的代码并不完美,但仍然比上面的任何内容都要好:http://pastebin.com/gQdkmqt8(哈希函数来自网络,也可以做得更好)
您是否可以提供有关您计划优化的系统(一个或多个系列)的更多详细信息?
时间复杂性信息: 应该是链接......但是我没有这么多的声誉,因为我是stackoverflow的初学者。
我的回答是否与任何内容相关?请添加评论或投票,因为我看到没有PM。
答案 5 :(得分:1)
IO库的正确实现会为您缓存数据,避免过多的磁盘访问和系统调用。我建议您使用系统调用级别工具(例如,如果您在Linux下使用strace)来检查您的IO实际发生的情况。
显然,dict.insert(xxx)
如果不允许插入O(1),也可能会造成麻烦。
答案 6 :(得分:0)
信不信由你,stdlib流在读取数据时的性能远低于C库例程。如果您需要顶级IO读取性能,请不要使用c ++流。我在算法竞争网站上发现了这种困难 - 我的代码会使用c ++流来读取测试超时,但是使用普通的C FILE操作会在很长时间内完成。
编辑:只需在一些示例数据上试用这两个程序。我在Mac OS X 10.6.6上使用g ++ i686-apple-darwin10-g ++ - 4.2.1(GCC)4.2.1(Apple Inc. build 5664)在一个包含100万行“howdythere”的文件上运行它们, scanf版本的运行速度始终比cin版本快5倍:
#include <stdio.h>
int main()
{
int count = 0;
char buf[1024];
while ( scanf("%s", buf) == 1 )
++ count;
printf( "%d lines\n", count );
}
和
#include <iostream>
int main()
{
char buf[1024];
int count = 0;
while ( ! std::cin.eof() )
{
std::cin.getline( buf, 1023 );
if ( ! std::cin.eof() )
++count;
}
std::cout << count << " lines" << std::endl;
}
编辑:将数据文件更改为“howdythere”以消除两种情况之间的差异。时间结果没有改变。
编辑:我认为这个答案中的兴趣量(和downvotes)显示了与现实相反的流行观点。人们无法相信在C和流中读取输入的简单情况可能会如此不同。在你投票之前:去自己测量一下。关键不是设置大量的状态(没有人通常设置),而只是人们最常写的代码。意见在绩效上没有任何意义:衡量,衡量,衡量是最重要的。
答案 7 :(得分:0)
不幸的是,在使用fstream时,你无法提高性能。
通过读取更大的文件块然后解析单个单词,您可以获得非常轻微的速度提升,但这取决于您的fstream实现如何缓冲。
获得重大改进的唯一方法是使用操作系统的I / O功能。例如,在Windows上,使用FILE_FLAG_SEQUENTIAL_SCAN标志打开文件可能会加快读取速度,并使用异步读取从磁盘中获取数据并并行解析。
答案 8 :(得分:0)
如果你真的想快,ditch istream和string,并在const char*
附近创建一个简单的类Read_Only_Text&amp; size
,然后内存映射文件并插入unordered_set<Read_Only_Text>
并引用嵌入的字符串。它意味着你不必要地保留2mb文件,即使你的唯一键的数量可能要少得多,但填充它会非常非常快。我知道这是一种痛苦,但我已经多次完成各种任务,结果非常好。