从字符数组合并键值列表的有效方法

时间:2011-10-16 06:11:26

标签: string algorithm delphi optimization sorting

在我们的一个应用程序的核心,我们必须合并键值列表。因为这个合并函数一直被调用,所以它必须尽可能快。交换记忆以获得额外的速度是可以接受的。

我们的应用程序是用Delphi编写的,所以我将引用一些特定于Delphi的例程,但我认为这个问题可能与用于解决它的语言无关。

要求

  • 两个输入键值列表(“原始”和“更新”)作为指向字符数组的指针传入,例如, 'Key1=Value1'#13#10'Key2=Value2'#10'Key3=Value3'#13#10#10'Key4=Value4'。请注意,键和值之间用'='分隔,键值对可以由字符#13#10的任意组合分隔。
  • 在输出键值对中,始终用#13#10分隔。
  • 输出中键值对的顺序无关紧要。
  • 如果其中一个输入包含重复键,则可以保留副本。但是,只保留一个密钥也是可以接受的,因为重复项不应该在那里。如果原始密钥和更新包含相同的密钥,则保留更新中的值。
  • 我只处理ASCII字符。

我的解决方案

我的解决方案的核心是一个字典,它将键(字符串)映射到指向的指针以及包含该值的内存块的长度。此地图按键排序。它可以在使用前重置,并在合并例程的多个调用之间共享,因此我们节省了映射及其条目的内存分配和释放。 对每个输入键值列表执行以下操作:

  • 迭代输入中的每个字符。
  • 遇到键值分隔符时,提取键并向前扫描到值的末尾。
  • 如果地图中存在该键,请更新我们通过向前扫描确定的值指针和长度。
  • 跳过值后的所有#13#10个字符,以转到下一个键的开头。
  • 重复直到输入结束。

填充地图后,通过迭代地图,连接键,键值分隔符,基于给定位置和长度的值的副本以及每个条目的“\ r \ n”来构建输出字符串。不要忘记最后的空终止符。

优化的想法

我尝试过以下操作,使用QueryPerformanceCounter Windows API函数测量性能。

  • 我原本以为当按键数量很少时,保持地图排序太多了。然而,事实证明,即使只有两三个键,保持地图排序也会产生几乎相同的性能。
  • 地图包含作为字符串的键,这意味着我必须从字符数组中提取键,并使用Delphi的SetString例程从中创建一个字符串。我理解Delphi strings的方式,这必须涉及一个内存副本,我想避免。但是,仅存储指针和密钥长度,然后使用CompareString routine from the Windows unit比较它们比将字符串提取为字符串并使用SysUtils中的CompareStr进行比较要慢得多。我认为这是因为CompareString实现较慢。是否可能有一个不同的例程来比较接受指针和长度作为输入的字符串?但是我还没有找到一个。
  • 为了保持地图排序,我正在使用Classes.TStringList中的排序算法,这是一个快速排序,如果我没有弄错的话。可能有更好的排序算法更适合这种情况吗?

您能想到哪些其他优化甚至是完全不同的算法?

2 个答案:

答案 0 :(得分:1)

据我所知,你的解决方案很好,很难改进。

我要做的唯一建议是对字典使用散列而不是键和二进制搜索的排序列表。假设其性能合理,您可以使用Delphi的TDictionary<TKey,TValue>。对于TKey,您将使用实现地图的自定义记录(位置和长度)。同样适用于TValue。您必须实现自己的比较器,这可以很容易地完成,而不会产生堆分配。

说完所有这些之后,你是否100%确定堆分配与你认为它们对于这个应用程序一样邪恶?您应该尝试使用TDictionary<string,string>进行简单的实现并对应用进行概要分析,以证明它在字典代码中花费了大量时间。这种方法的另一个好处是,如果确实堆分配是一个问题,您可以使用基于string的版本作为参考实现进行测试。您的指针偏移+基于长度的版本肯定是一个错误工厂。

答案 1 :(得分:0)

句子“这个地图按键排序”和短语“保持地图排序”和东西重新指针和长度使得听起来像是在每次插入数组后对指针数组进行排序。如果是这样,您可能会发现Timsort的运行速度比Quicksort快。

维护平衡搜索树可能是更好的方法。 AA tree很容易编码,其性能类似于红黑树, O(ln n)插入,查找和删除。如果您确实在每次插入后对数组进行排序,则使用搜索树会将插入时间从O(n ln n)减少到O(ln n)。

要按顺序读出密钥,请使用在最坏情况下运行时间为O(n ln n)的in-order traversal

已更新:已按顺序更正预订