【问题标题】:getting better performance appending strings than going through standard Java stringbuilder.append附加字符串比通过标准 Java stringbuilder.append 获得更好的性能
【发布时间】:2010-12-17 13:28:08
【问题描述】:

作为填充搜索引擎过程的一部分,我还填充了 Berekely-DB 值存储。这个过程每晚重复一次,目前每晚总运行时间的 +/- 60% 是由创建要插入值存储的值引起的(因此不包括实际插入 Berekely-DB 和导致的时间由 Berekely 客户提供

这些值是通过为每个键分配一个字符串生成器创建的,然后平均将大约 1000 个字符串附加到这样的字符串生成器。结果值平均约为 10k。 我想知道这是否可以更有效地完成,因为: - 附加到每个 Stringbuilders 的(平均)1000 个字符串具有固定长度:即:每个 String 具有相同的长度,并且这个长度是预先知道的) - 所有字符串都附加到末尾。

例如,将 stringbuilder 换成 char[] 或 characterStream / writer 会更高效吗?这样我就可以在 char[] 中保留并索引写入的位置。

谢谢, 吉尔特-扬

【问题讨论】:

  • 量一下看看;看起来您对要尝试的内容很清楚。
  • 是的,我想这就是我要做的。然而内存限制也是一个问题,所以我应该有一个自适应策略,当不需要它们时不会创建大字符数组。我会尝试看看我能想出什么
  • AFAIK,StringBuilders 用完时会加倍缓冲区。这是一个很好的策略,而且很难被击败。但是,了解合理的最小尺寸界限将帮助您预先确定尺寸,从而避免其中一些扩展。祝你好运!

标签: java performance stringbuilder


【解决方案1】:

您可以创建具有更高初始容量的字符串构建器以减少调整大小的数量,即有一个构造器可以让您说

int SIZE=10000;
StringBuilder b = new StringBuilder(SIZE);

我希望手动处理 char[] 和索引不会对此有太大改进,因为(我假设)这就是 StringBuilder 已经为您做的事情。

【讨论】:

  • 这就是你所需要的。如果你用 char 数组来做,你不会得到那么多的加速,因为这正是 StringBuilder 所做的。 +'ed
【解决方案2】:

这 1000 个字符串是从哪里来的?我很难相信这 1000 个对象的创建时间并不能完全使您的 StringBuilder 摊销扩展所需的时间相形见绌。

【讨论】:

  • 给定 1000 个字符串,下面的每个调用构成这些字符串的 1/3。 1. agg.sb.append(sDur); 2. agg.sb.append(sermap.get(key)); 3. agg.sb.append(doc.sbs[durIndexDCToZero]); 1. 基于整数创建每个内部循环 2. 在静态地图中查找 3. 在每个文档创建一次的地图中查找。我仅通过注释掉上面的这些调用来衡量差异,因此插入字符串本身的创建时间不会在差异中衡量。
  • 很公平,感谢您提供的信息!但是请看这里:您为 sDurs 创建了 333 个对象,而将 StringBuilder 从 16 个字节扩展到超过 10,000 个字节只需要 10 个对象创建和复制操作。因此,除非我遗漏了什么,否则仅创建 sDur 就应该花费大约 30 倍于字符串构建的时间。无论如何,我赞同 Steve B 的建议,肯定不会有什么坏处!
  • 感谢您的推理。我自己觉得有点奇怪。我会进一步调查。
【解决方案3】:

你应该试试ropes。该网站的细节很少,但有一篇很棒的文章here 有更好的描述,还有一些很好的benchmarks comparing append performance

我实际上并没有使用过绳索包,也没有足够好的借口。不过看起来很有希望。

编辑:附加基准信息

我从绳索文章中下载了PerformanceTest 类,除了StringBuffer 之外,还添加了StringBuilder 的测试。 StringBuilder 的性能提升似乎可以忽略不计。

我从ropes文章中下载了测试代码并将测试更改为包含StringBuilderStringBuffer

追加计划长度:260 [StringBuilder] 平均值 = 117,146,000 ns 中值 = 114,717,000ns [StringBuffer] 平均值 = 117,624,400 ns 中值 = 115,552,000ns [绳索] 平均 = 484,600 ns 中值 = 483,000ns 追加计划长度:300 [StringBuilder] 平均值 = 178,329,000 ns 中值 = 178,009,000ns [StringBuffer] 平均值 = 217,147,800 ns 中值 = 216,819,000ns [绳索] 平均 = 252,800 ns 中值 = 253,000ns 追加计划长度:500 [StringBuilder] 平均值 = 221,356,200 ns 中值 = 214,435,000ns [StringBuffer] 平均值 = 227,432,200 ns 中值 = 219,650,000ns [绳索] 平均值 = 510,000 ns 中值 = 507,000ns

StringBuilder 和 StringBuffer 之间的区别并没有那么大。对于手头的任务,绳索在这里似乎是一个明显的胜利。

【讨论】:

  • Ropes 与 String 连接相比表现良好,这一点也不奇怪;并且比 StringBuffer 更好,后者的操作是同步的。 IBM 页面没有显示与专门为解决此问题而创建的 StringBuilder 的比较。
  • 根据文章,Ropes 对 StringBuffer 的性能提升主要是由于在 StringBuffer 增长时调整/复制支持 char[] 数组的开销。同样的开销也适用于 StringBuilder。
  • @Carl Smotricz:如果您遵循 Steve B 为您的 StringBuilders 设置高初始容量的建议,我认为 StringBuilder 可能会比 Ropes 更快。如果您有许多缓冲区对象并且不确定它们可能会增长到多大,那么 Ropes 听起来是此类工作的理想人选。
  • 还有另一篇关于 Ropes 的 IBM Developer 文章,由 Ropes 的作者撰写。它的日期是 2008 年,但 仍然 不包括与 StringBuilder 的性能比较。我想知道这是不是故意的?我本来很想看看结果,但无法对此发表评论。
【解决方案4】:

修订版 III:

如果 StringBuilders 中的字符串连接花费的时间过长,也许你的 内存很满。所以我们的目标是在不咀嚼的情况下实现字符串连接 占了很多内存。希望 CPU 时间的节省会自动随之而来。

我的计划是这样的:而不是将这些子字符串连接成一个长 StringBuilder,您可以构建对那些(预先存在的)的引用列表 字符串。引用列表应短于子字符串的总和 从而消耗更少的内存。

只有当需要存储那个大字符串时,我们才会连接各个部分 在一个大的 StringBuilder 中,拉出 String,存储 String,扔掉 引用String,清除StringBuilder,重复。我觉得这是一个 绝妙的解决方案!

但是,从 this article from 2002,数组中的字符串引用,可能同样在 一个 ArrayList,占用 8 个字节! A recent StackOverflow post 确认仍然如此。因此,一个列表 对 10 字节字符串的引用只为每个字符串节省 2 个字节。因此,我提出我的 “解决方案”作为类似问题的可能性,但我没有看到这个特别的 无法从中受益的问题。

【讨论】:

  • 刚刚注意到这一点,您绝对解决了我的问题。整个地方的 GC 都导致了问题...实际上我已经实现了您建议的完全相同的东西,在这种情况下节省了很多,因为保存子字符串的映射已经存在(并且应该作为不同例程的一部分)现在只有在我构建字符串的时候。谢谢
  • 我对这个问题进行了很多思考,我很高兴这不是徒劳的。 (擦掉眼角的泪水)我喜欢幸福的结局!
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2016-06-07
  • 1970-01-01
  • 2015-04-27
  • 2011-11-27
  • 1970-01-01
  • 2012-01-17
  • 2011-01-30
相关资源
最近更新 更多