上周我在C#中写了几行代码,将一个大文本文件(300,000行)激活到一个Dictionary中。编写花了十分钟,并在不到一秒的时间内执行。
现在我将这段代码转换为C ++(因为我需要在旧的C ++ COM对象中)。到目前为止,我已经花了两天时间。 :-(虽然生产力差异本身令人震惊,但我需要一些建议。
加载需要7秒钟,更糟糕的是:之后需要花费很多时间来释放所有CStringW。这是不可接受的,我必须找到一种方法来提高性能。
我是否有可能在没有看到这种可怕的性能下降的情况下分配这么多字符串?
我现在的猜测是,我必须将所有文本填充到一个大型数组中,然后让我的哈希表指向此数组中每个字符串的开头并删除CStringW内容。
但在那之前,C ++专家给你的建议是什么?
编辑:我的答案如下。我意识到这对我来说是最快捷的路径,并且还会介入我认为正确的方向 - 更多托管代码。
答案 0 :(得分:12)
这听起来非常像Raymond Chen和Rico Mariani的C ++ vs C#中文/英文词典表演。雷蒙德用了几次迭代来击败C#。
也许那里的想法会有所帮助。
答案 1 :(得分:11)
你正踩着Raymond Chen的鞋子。他做了完全相同的事情,用非托管C ++编写中文字典。 Rico Mariani也做了,用C#编写。玛丽安先生制作了一个版本。陈先生写了6个版本,试图匹配Mariani版本的性能。他几乎重写了C / C ++运行时库的重要部分来实现目标。
之后,托管代码得到了更多的尊重。 GC分配器是不可能击败的。查看this blog帖子以获取链接。这个blog post也可能对您感兴趣,有助于了解STL值语义是如何成为问题的一部分。
答案 2 :(得分:10)
让人惊讶。摆脱CStrings ......
尝试一个分析器。 你确定你不只是运行调试代码吗?
使用std :: string代替。
编辑:
我刚做了一个关于ctor和dtor比较的简单测试。
CStringW似乎需要花费2到3倍的时间来进行新的/删除。
迭代1000000次为每种类型执行new / delete。没有别的 - 和每个循环之前和之后的GetTickCount()调用。一直是CStringW的两倍。
虽然我怀疑,但这并没有解决你的整个问题。
编辑: 我也不认为使用字符串或CStringW是真正的问题 - 还有其他事情会导致你的问题。
(但为了上帝的缘故,无论如何都要使用stl!)
您需要对其进行分析。那是一场灾难。
答案 3 :(得分:4)
如果它是只读字典,则以下内容适合您。
Use fseek/ftell functionality, to find the size of the text file.
Allocate a chunk of memory of that size + 1 to hold it.
fread the entire text file, into your memory chunk.
Iterate though the chunk.
push_back into a vector<const char *> the starting address of each line.
search for the line terminator using strchr.
when you find it, deposit a NUL, which turns it into a string.
the next character is the start of the next line
until you do not find a line terminator.
Insert a final NUL character.
你现在可以使用向量来获取指针,它将让你 访问相应的值。
完成字典后,取消分配内存,让矢量 超出范围时死亡。
[编辑] 这在dos平台上可能会稍微复杂一些,因为行终止符是CRLF。
在这种情况下,使用strstr找到它,然后递增2以找到下一行的开头。
答案 4 :(得分:3)
您将字符串存储在什么类型的容器中?如果它是std::vector
CStringW
并且如果你没有reserve
- 预先记录足够的内存,那么你一定会受到打击。一个vector
通常会在达到它的极限(不是很高)时调整大小,然后将整个副本复制到新的内存位置,这会给你带来很大的打击。当你的vector
呈指数增长时(即如果初始大小为1,则下次下次分配2,4时,命中变得越来越不频繁)。
它还有助于了解各个字符串的长度。 (有时:)
答案 5 :(得分:3)
感谢大家的深刻见解。为你投票! : - )
我必须承认我根本没有为此做好准备 - C#会用这种方式击败好老C ++的生活垃圾。请不要将其视为对C ++的攻击,而是将其作为一个非常好的内存管理器放在.NET Framework中。
我决定退后一步,在InterOp竞技场中争夺这场战斗!也就是说,我将保留我的C#代码,让我的旧C ++代码通过COM接口与C#代码通信。
有很多关于我的代码的问题,我会尝试回答其中的一些问题:
编译器是Visual Studio 2008,不,我没有运行调试版本。
该文件是使用UTF8文件阅读器读取的,该文件阅读器是从在其网站上发布的Microsoft员工下载的。它返回了CStringW,大约30%的时间实际上只是在那里阅读文件。
我存储字符串的容器只是一个固定大小的CStringW指针向量,它从未调整过大小。
答案 6 :(得分:3)
问题不在于CString,而在于您分配了许多小对象 - 默认的内存分配器并未针对此进行优化。
编写自己的分配器 - 分配一大块内存,然后在分配时在其中前进一个指针。这就是.NET分配器的功能。准备好后删除整个缓冲区。
我认为在(更多)有效的C ++
中编写自定义新/删除运算符的示例答案 7 :(得分:2)
将字符串加载到单个缓冲区,解析文本以使用字符串终止符('\ 0')替换换行符,并使用指向该缓冲区的指针添加到集合中。
或者 - 例如如果你必须在加载期间进行ANSI / UNICODE转换 - 使用块分配器,这会牺牲删除单个元素。
class ChunkAlloc
{
std::vector<BYTE> m_data;
size_t m_fill;
public:
ChunkAlloc(size_t chunkSize) : m_data(size), m_fill(0) {}
void * Alloc(size_t size)
{
if (m_data.size() - m_fill < size)
{
// normally, you'd reserve a new chunk here
return 0;
}
void * result = &(m_data[m_fill]);
m_fill += size;
return m_fill;
}
}
// all allocations from chuunk are freed when chung is destroyed.
不会在十分钟内一起破解,但是30分钟并且一些测试听起来很好:)
答案 8 :(得分:1)
使用字符串类时,应始终查看不必要的操作,例如,不要经常使用构造函数,连接和此类操作,尤其要避免它们在循环中。我想你使用CStringW有一些字符编码的原因,所以你可能不能使用不同的东西,这将是优化代码的另一种方法。
答案 9 :(得分:0)
毫无疑问,CLR的内存管理比MFC基于的一堆旧的和肮脏的技巧要好:它至少比MFC本身小两倍,而且它是基于池的。当我不得不使用字符串数组和WinAPI / MFC处理类似的项目时,我只使用了使用WinAPI的TCHAR实例化的std :: basic_string以及基于Loki :: SmallObjAllocator的我自己的分配器。在这种情况下,您还可以查看boost :: pool(如果您希望它具有&#34; std feel&#34;或者必须使用早于7.1的VC ++编译器版本。)