Java和Python之间不可变字符串连接的性能比较

时间:2010-10-10 16:29:12

标签: java python performance string concatenation

更新:非常感谢Gabe和Glenn的详细解释。该测试不是用于语言比较基准,而是为了研究VM优化技术。

我做了一个简单的测试来理解Java和Python之间字符串连接的性能。

测试是两种语言中默认不可变String对象/类型的目标。所以我在Java测试中不使用StringBuilder / StringBuffer。

测试只需添加100k次字符串。 Java消耗约32秒完成,而Python仅使用~13秒的Unicode字符串和0.042秒的非Unicode字符串。

我对结果感到有些惊讶。我认为Java应该比Python更快。 Python利用什么优化技术来实现更好的性能?或者String对象在Java中设计得太重了吗?

操作系统:Ubuntu 10.04 x64 JDK:Sun 1.6.0_21 Python:2.6.5

Java测试确实使用-Xms1024m来最小化GC活动。

Java代码:

public class StringConcateTest {
public static void test(int n) {
    long start = System.currentTimeMillis();
    String a = "";
    for (int i = 0; i < n; i++) {
        a = a.concat(String.valueOf(i));
    }
    long end = System.currentTimeMillis();
    System.out.println(a.length() + ", time:" + (end - start));
}

public static void main(String[] args) {
    for (int i = 0; i < 10; i++) {
        test(1000 * 100);           
    }
}

}

Python代码:

import time
def f(n):
    start = time.time()
    a = u'' #remove u to use non Unicode string
    for i in xrange(n):
        a = a + str(i)
    print len(a), 'time', (time.time() - start)*1000.0
for j in xrange(10):
    f(1000 * 100)

5 个答案:

答案 0 :(得分:5)

@ Gabe的回答是正确的,但需要清楚地展示而不是假设。

CPython(可能只有CPython)会在可能的情况下附加就地字符串。它何时可以做到这一点有局限性。

首先,它不能用于实习字符串。这就是为什么如果用a = "testing"; a = a + "testing"进行测试,你永远不会看到这个,因为分配一个字符串文字导致一个实习字符串。您必须动态创建字符串,因为此代码与str(12345)一起使用。 (这不是一个限制;一旦你以这种方式附加一次,结果是一个未处理的字符串,所以如果你在循环中追加字符串文字,这只会在第一次发生。 )

其次,Python 2.x仅对str执行此操作,而不是unicode执行此操作。 Python 3.x确实为Unicode字符串执行此操作。这很奇怪:它是一个主要的性能差异 - 复杂性的差异。这不鼓励在2.x中使用Unicode字符串,因为它们应该鼓励它来帮助过渡到3.x。

最后,没有其他对字符串的引用。

>>> a = str(12345)
>>> id(a)
3082418720
>>> a += str(67890)
>>> id(a)
3082418720

这解释了为什么非Unicode版本在测试中比Unicode版本快得多。

string_concatenate中的实际代码为Python/ceval.c,适用于s1 = s1 + s2s1 += s2_PyString_Resize中的函数Objects/stringobject.c也明确说明:以下函数打破了字符串不可变的概念。另请参阅http://bugs.python.org/issue980695

答案 1 :(得分:3)

我的猜测是Python只对字符串执行realloc而不是创建一个带有旧字符串副本的新字符串。 realloc没有时间,因此非常快。

那么为什么Python可以调用realloc而Java不能呢? Python的垃圾收集器使用引用计数,因此它可以告诉其他人没有使用该字符串,如果字符串更改则无关紧要。 Java的垃圾收集器不维护引用计数,因此无法判断是否存在对该字符串的任何其他引用,这意味着它除了在每个连接上创建字符串的全新副本之外别无选择。

编辑:虽然我不知道Python实际上是在concat上调用realloc,但这是stringobject.c中_PyString_Resize的注释,说明它为什么可以:

       The following function breaks the notion that strings are immutable:
       it changes the size of a string.  We get away with this only if there
       is only one module referencing the object.  You can also think of it
       as creating a new string object and destroying the old one, only
       more efficiently.  In any case, don't use this if the string may
       already be known to some other part of the code...

答案 2 :(得分:1)

我不认为你的测试意味着很多,因为Java和Python处理不同的字符串(我不是Python的专家,但我确实用Java的方式)。 StringBuilder使用StringBuilders / Buffers是有原因的。由于这个原因,语言设计者没有做任何更有效的内存管理/操作:还有其他工具而不是“String”对象来进行这种操作,他们希望你在编码时使用它们。

当你按照Java的方式完成工作时,你会惊讶于平台的速度有多快......但我不得不承认我对一些Python应用程序的性能印象非常深刻最近尝试过。

答案 3 :(得分:0)

我肯定不知道答案。但这里有一些想法。首先,Java在内部将字符串存储为包含字符串的UTF-16编码的char []数组。这意味着字符串中的每个字符至少需要两个字节。因此,就原始存储而言,Java必须复制两倍于python字符串的数据。因此,Python unicode字符串是更好的测试,因为它们具有类似的功能。也许python将unicode字符串存储为UTF-8编码的字节。在这种情况下,如果您存储的所有内容都是ASCII字符,那么您再次使用Java的空间是两倍,因此复制的次数是原来的两倍。为了更好地进行比较,您应该连接包含更多有趣字符的字符串,这些字符在UTF-8编码中需要两个或更多字节。

答案 4 :(得分:0)

我用StringBuilder代替字符串运行Java代码,平均完成时间为10毫秒(高34毫秒,低5毫秒)。

对于Python代码,使用“方法6”here(发现是最快的方法),我能够使用unicode字符串平均达到84ms(高91ms,低81ms)。使用非unicode字符串可将这些数字减少约25ms。

因此,可以说基于这些高度不科学的测试,使用最快的字符串连接方法,Java比Python快大约一个数量级。

但我仍然&lt; 3 Python;)