这种方法的问题是,
我可以使用流读取文件,但想使用线程读取文件的某些部分。例如,使用单独的线程读取0-1024字节(至少有4-8个线程,根据盒子中存在的处理器数量而定)。这可行吗?还是我在做梦?
是否有更好的方法?
注意:它应该是纯C++或C语言解决方案。不允许使用数据库等。
x
和x,
被视为两个不同的单词。 - Jerry Coffinstd::map<std::string, T>
,但是一旦我解决了错误,我将其更改为 std::map<StringShim, T>
。StringShim
是一个简单的 4 字节类,包装了一个 char*
;实际的字符串由单个 StringPool
管理。这样效率显著提高了。 - MSalters我认为使用多个线程并行读取文件的部分内容并不会有太大帮助。我预计这个应用程序受限于硬盘的带宽和延迟,而不是实际的单词计数。这样一个多线程版本可能表现得更差,因为“准随机”文件访问通常比“线性文件”访问慢。
如果CPU在单线程版本中确实很忙,可能会有潜在的加速效果。一个线程可以读取大块数据并将它们放入有限容量的队列中。一堆其他工作线程可以操作各自的块并计算单词。在计数工作线程完成后,您需要合并单词计数器。
首先 - 决定用于保存单词的数据结构。
显而易见的选择是map。但也许Trie会更好地为您服务。在每个节点中,您保存单词的计数。 0意味着它只是一个单词的一部分。您可以使用流并基于字符读取文件将其插入到trie中。
其次 - 多线程是好还是不好? 这个问题不容易回答。根据数据结构增长的大小以及您如何并行化,答案可能会有所不同。
你必须考虑一件事-你必须为每个线程找到一个单词边界来开始,但这不应该是一个大问题(例如,每个线程在其起点行走直到第一个单词边界并从那里开始,在结束时每个线程完成它正在处理的单词)。
虽然您可以使用第二个线程在读取数据后分析数据,但这样做可能不会带来太大的收益。尝试使用多个线程读取数据几乎肯定会降低速度而不是提高速度。使用多个线程处理数据是没有意义的--处理速度将比读取速度快得多,因此即使只有一个额外的线程,限制也将是磁盘速度。
获得显着速度的一种(可能的)方法是绕过通常的iostreams--虽然有些iostreams几乎与使用C FILE*一样快,但我不知道有什么东西真正更快,有些则慢得多。如果您在运行具有明显不同于C的I/O模型的系统(例如Windows),则可以通过小心处理获得更多的收益。
问题很简单:您正在阅读的文件(可能)比您可用的缓存空间大,但如果您不打算再次读取文件的块(至少如果您做得明智),则不会从缓存中获得任何好处。因此,您想告诉系统绕过任何缓存,并尽可能直接从磁盘驱动器传输数据到您可以处理它的内存中。在类Unix系统中,这可能是通过使用open()
和read()
实现的(并不会带来太大好处)。在Windows中,这是通过CreateFile
和ReadFile
实现的,向CreateFile
传递FILE_FLAG_NO_BUFFERING
标志-如果正确使用,则速度可能增加约一倍。正如其他人所指出的,瓶颈将是磁盘 I/O。因此,我建议您使用重叠 I/O。这基本上倒转了程序逻辑。您不必在代码中决定何时进行 I/O,而是告诉操作系统在完成一些I/O时调用您的代码。如果您使用I/O完成端口,甚至可以告诉操作系统为处理文件块使用多个线程。
首先,我相信C/C++不是处理这个问题的最佳方法。 理想情况下,您可以使用一些map/reduce来进行并行处理。
但是,假设您的约束条件,以下是我的建议。
1)将文本文件拆分为较小的块。 您不必按单词的第一个字母进行拆分。 只需将它们分成5000字的块即可。 在伪代码中,您可以执行以下操作:
index = 0
numwords = 0
mysplitfile = openfile(index-split.txt)
while (bigfile >> word)
mysplitfile << word
numwords ++
if (numwords > 5000)
mysplitfile.close()
index++
mysplitfile = openfile(index-split.txt)
2) 使用共享的映射数据结构和pthread来生成新线程以读取每个子文件。再次提供伪代码:
maplock = create_pthread_lock()
sharedmap = std::map()
对于每个index-split.txt文件:
spawn-new-thread(myfunction, filename, sharedmap, lock)
转换为中文:
dump_map(sharedmap)
void myfunction(filename, sharedmap) {
localmap = std::map<string, size_t>();
file = openfile(filename)
while (file >> word)
if !localmap.contains(word)
localmap[word] = 0
localmap[word]++
acquire(lock)
for key,value in localmap
if !sharedmap.contains(key)
sharedmap[key] = 0
sharedmap[key] += value
release(lock)
}
很抱歉语法错误,最近一直在写Python。
不是C语言,看起来有点丑陋,但只需要2分钟就能完成:
perl -lane '$h{$_}++ for @F; END{for $w (sort {$h{$b}<=>$h{$a} || $a cmp $b} keys %h) {print "$h{$w}\t$w"}}' file > freq
使用-n
循环遍历每一行
使用-a
将每一行分割成@F
单词
每个$_
单词都会增加哈希表%h
一旦到达file
的END
,
按频率$h{$b}<=>$h{$a}
对哈希表进行sort
如果两个频率相同,则按字母顺序排序$a cmp $b
打印频率$h{$w}
和单词$w
将结果重定向到文件'freq'
我在一个大小为3.3GB,包含580,000,000个单词的文本文件上运行了这段代码。
Perl 5.22在173秒内完成。
我的输入文件已经去除了标点符号,并将大写字母转换为小写字母,使用了以下代码:
perl -pe "s/[^a-zA-Z \t\n']/ /g; tr/A-Z/a-z/" file_raw > file
(运行时间为144秒)
awk '{for (i=1; i<=NF; i++){h[$i]++}} END{for (w in h){printf("%s\t%s\n", h[w], w)}}' file | sort -rn > freq
C语言的解决方案?
我认为Perl就是为了这个目的而生的。
流只有一个光标。如果您同时使用多个线程访问流,则无法确定要读取的位置。读取是从光标位置完成的。
我会做的是只有一个线程(可能是主线程)读取流并将读取的字节分派给其他线程。
例如:
通过这种方式,您可以将流的读取与流的分析分开。