我正在编写一个基本的文本编辑器,它实际上是一个编辑控件框,我想为我的主程序编写代码,数值和表达式。
我目前的做法是将字符串输入编辑控件。在编辑控件中,我有一个类将字符串分解为“字形”,如单词,数字,换行符,制表符,格式标记等。例如,单词字形包含表示文字字符串的字符串和表示字母的短整数尾随空格的数量。字形还包含绘制文本和计算换行时所需的信息。
例如,文本行“我的名字是卡尔”将等于这样的字形的链接列表: NewLineGlyph→WordGlyph(“我的”,1个空白)→WordGlyph(“名称”,1个空格)→WordGlyph(“是”,1个空格)→WordGlyph(“Karl”,0个空格)→NULL。
因此,不是将字符串作为连续的字符串(或WCHAR)存储在内存中,而是将其存储在具有大量小分配和解除分配的小块中。
我的问题是;这样做时我应该关注堆碎片吗?你有任何提高这个效率的技巧吗?或者完全不同的方式呢? :)
PS。我在Win7上使用C ++。
答案 0 :(得分:2)
你是否应该关注碎片?答案可能取决于文档的大小(例如,单词数量),以及将进行多少编辑以及这些编辑的性质。您所概述的方法对于静态(只读)文档可能是合理的,您可以在其中“解析”文档一次,但我想在幕后需要进行大量工作以保持数据结构在用户进行任意编辑时处于正确的状态。此外,您必须决定“单词”是什么,在每种情况下都不一定是明显/一致的。例如,“努力工作”一两个字?如果它是一个,这是否意味着你永远不会在连字符上换字?或者,考虑“单词”不适合单行的情况。在这种情况下,你会简单地截断,还是想强行跨行突破这个词?
我的建议是将文本存储为块,并单独存储换行符(作为文本块中的偏移量),然后在每次更改时根据需要重新计算换行符。如果您担心碎片并最大限度地减少分配/解除分配的数量,您可以分配固定大小的块,然后自己管理这些块内的内存。这就是我过去所做的:
文本存储为一个字符块,但是我没有为整个文档创建一个连续的块,而是维护一个总是分配4KB的块的链接列表(即,4K单字节字符,或2K WCHARs)。换句话说,文本存储为数组的链接列表,其中每个数组都分配给一个恒定的大小。
每个区块都会跟踪该区块内使用/空闲的空间(即字符数)。
当插入一个或多个字符时,如果当前块中有空格,我可以简单地移动该块内的内存(不需要分配/释放)。如果当前块中没有可用空间,但相邻块中有空间可用,那么我可以再次在现有块之间移位内存(不需要分配/释放)。如果两个块都已满,那么我才会分配一个新的4KB块并添加到链表中的适当位置。
删除一个或多个字符时,我只需要移动内存(最多4KB)而不是整个文档文本。我也可能需要解除分配并删除任何完全空的块。
我还做了一些“垃圾收集”,以便在适当的时候合并自由空间。这是相当简单的,涉及将字符从一个块移动到另一个块,以便某些块变空并且可以删除。
从操作系统和/或运行时库的角度来看,所有分配/解除定位都是相同的大小(4KB),因此没有碎片。而且由于我管理了内存的内容,我可以通过改变内存内容来消除浪费的空间,从而避免在我分配的空间内出现碎片。另一个优点是它可以最大限度地减少alloc / dealloc调用的数量,这可能是一个性能问题,具体取决于您使用的分配器。因此,它是速度和大小的优化 - 的发生频率是多少? : - )
答案 1 :(得分:1)
我不担心堆碎片;现代堆经理非常善于处理。
但是,我可能会担心数据局部性不佳。将每个字形作为链接列表中的单独分配(特别是像std :: list这样的非侵入式列表),任何类型的文档传递都将以可能非缓存友好的方式跳过整个内存。 / p>文字编辑比乍一看时更难。有一个很多专门的数据结构用于表示文本块和结构化文档。它们各自针对不同类型的操作进行优化。我建议搜索它们的解释,然后考虑你最需要做的操作类型。
这篇论文陈旧,但它有很多好的信息:http://www.cs.unm.edu/~crowley/papers/sds.pdf