编译字符串连接的时间优化

时间:2018-02-10 02:15:11

标签: java optimization string-concatenation

我很好奇,以下代码段的优化程度将会有多远。

据我所知,无论何时扩展StringBuffer的容量,都需要一些CPU工作,因为需要重新分配其内容。但是,我猜Java编译器优化可以预先计算所需的容量,而不是进行多次重新分配。

问题是:是否会优化以下代码片段?

public static String getGetRequestURL(String baseURL, Map<String, String> parameters) {
    StringBuilder stringBuilder = new StringBuilder();
    parameters.forEach(
            (key, value) -> stringBuilder.append(key).append("=").append(value).append("&"));
    return baseURL + "?" + stringBuilder.delete(stringBuilder.length(),1);
}

3 个答案:

答案 0 :(得分:2)

在Java中,大多数优化都是由运行时的编译器执行的,所以javac优化通常不重要。

因此,不需要Java编译器来优化字符串连接,尽管只要不涉及循环,所有人都倾向于这样做。您可以使用javap(JDK附带的java反编译器)来检查这种编译时优化的程度。

那么,javac可以想象优化这个吗?要确定字符串构建器的长度,必须迭代映射两次。由于java不具有const引用,并且编译器对Map没有特殊处理,因此编译器无法确定此重写是否会保留代码的含义。即使可能,但根本不清楚增益是否值得迭代两次的成本。毕竟,现代处理器可以在单个cpu指令中复制4到8个字符。由于内存访问是顺序的,因此在增长缓冲区时不会丢失任何缓存。另一方面,第二次迭代地图可能会导致额外的缓存未命中,因为Map条目(以及它们引用的字符串)可以分散在整个主存储器中。

无论如何,我不担心这段代码的效率。即使您的URL长度为1000个字符,调整缓冲区大小也需要0.1微秒。除非你有证据证明这确实是一个性能热点,否则你的时间可能会更好地花在其他地方。

答案 1 :(得分:2)

首先:

  • 您可以通过使用javap工具查看字节码来了解(javac)编译时优化的发生。

  • 您可以通过让JVM转储本机代码来了解执行哪些JIT编译器优化。

因此,如果您出于实际原因需要知道您的代码是如何优化的(在特定平台上),那么您应该检查。

实际上,javac的优化非常简单,并且不会达到预先计算缓冲区大小的程度。我还没有检查过,但我希望JIT编译器也是如此。我怀疑是否会尝试使用&#34;最佳&#34;来预先分配StringBuilder。大小

为什么?

原因如下:

  • 不准确的预先计算(平均而言)并没有帮助,可能比什么都不做更糟糕。
  • 准确的预先计算通常涉及测量要连接的实际弦的(动态)长度。
  • 实现优化逻辑会很复杂,并且会使优化器更慢,维护更多。
  • 在运行时,字符串测量会引入开销。你是否会经常提前出现以产生影响很难确定。 (人们不喜欢使他们的代码运行得更慢的优化......)
  • 与使用字符串连接相比,有更好(更有效)的方法来进行大规模文本组装。程序员(对问题域和应用程序逻辑有更多了解)可以比编译器更好地优化它。如果重要的是花费开发人员的努力。

答案 2 :(得分:0)

一种优化方法是在stringBuilder中设置baseURL和&符号,而不是使用结尾处的String串联,例如:

public static String getGetRequestURL(String baseURL, Map<String, String> parameters) {
  StringBuilder stringBuilder = new StringBuilder(baseURL);

  stringBuilder.append("&");

  parameters.forEach((key, value) -> stringBuilder.append(key).append("=").append(value).append("&"));

  stringBuilder.setLength(stringBuilder.length() - 1);
  return stringBuilder.toString();
}

如果您想提高速度,并且由于javac或JIT不会根据潜在的字符串大小进行优化,则可以自己跟踪它而不会产生太多开销,但是可以添加一个最大大小的跟踪器,例如:

protected static URL_SIZE = 256;

public static String getGetRequestURL(String baseURL, Map<String, String> parameters) {
  StringBuilder stringBuilder = new StringBuilder(URL_SIZE);

  stringBuilder.append(baseURL);
  stringBuilder.append("&");

  parameters.forEach((key, value) -> stringBuilder.append(key).append("=").append(value).append("&"));

  int size = stringBuilder.length();
  if (size > URL_SIZE) {
    URL_SIZE = size;
  }

  stringBuilder.setLength(size - 1);
  return stringBuilder.toString();
}

也就是说,在对100万个呼叫进行了一些测试之后,我发现不同版本的执行时间为(以毫秒为单位):

  • 您的版本:总数= 1151,平均值= 230
  • 版本1以上:总计= 936,平均值= 187
  • 版本2以上:总计= 839,平均值= 167