hal*_*lex 33 python performance python-2.7 python-internals
尝试一些代码并做一些微基准测试我发现float
在包含整数的字符串上使用函数比int
在同一个字符串上使用快2倍.
>>> python -m timeit int('1')
1000000 loops, best of 3: 0.548 usec per loop
>>> python -m timeit float('1')
1000000 loops, best of 3: 0.273 usec per loop
Run Code Online (Sandbox Code Playgroud)
在测试int(float('1'))
哪个运行时比裸机短时,它变得更加奇怪int('1')
.
>>> python -m timeit int(float('1'))
1000000 loops, best of 3: 0.457 usec per loop
Run Code Online (Sandbox Code Playgroud)
我在运行cPython 2.7.6的Windows 7和使用cPython 2.7.6的Linux Mint 16下测试了代码.
我必须补充一点,只有Python 2受到影响,Python 3显示了运行时之间的差异(不显着)差异.
我知道这些微基准测试得到的信息很容易被滥用,但我很好奇为什么函数的运行时存在这样的差异.
我试图找到的实现int
和float
而不同的资料来源,我不能找到它.
mic*_*ang 15
int
有很多基地.
*,0*,0x*,0b*,0**它可能很长,需要时间来确定基数和其他东西
如果设置了基数,则可以节省大量时间
python -m timeit "int('1',10)"
1000000 loops, best of 3: 0.252 usec per loop
python -m timeit "int('1')"
1000000 loops, best of 3: 0.594 usec per loop
Run Code Online (Sandbox Code Playgroud)
作为@Martijn Pieters提到代码Object/intobject.c(int_new)
和Object/floatobject.c(float_new)
int()
必须考虑更多可能的类型来转换float()
.当你传递一个对象int()
并且它不是一个整数时,那么测试各种各样的东西:
__int__
方法,则调用它并使用结果int
,则将结构中的C整数值转换为int()
对象.__trunc__
方法,则调用它并使用结果传入基本参数时,这些测试都不执行,然后代码直接跳转到使用选定的基础将字符串转换为int.那是因为没有其他可接受的类型,而不是在有基数的情况下.
因此,当您传入基础时,突然从字符串创建一个整数要快得多:
$ bin/python -m timeit "int('1')"
1000000 loops, best of 3: 0.469 usec per loop
$ bin/python -m timeit "int('1', 10)"
1000000 loops, best of 3: 0.277 usec per loop
$ bin/python -m timeit "float('1')"
1000000 loops, best of 3: 0.206 usec per loop
Run Code Online (Sandbox Code Playgroud)
当你传递一个字符串时float()
,第一个测试是查看参数是否是一个字符串对象(而不是一个子类),此时它正在被解析.没有必要测试其他类型.
所以这个int('1')
电话比int('1', 10)
或者做了更多的测试float('1')
.在那些测试中,测试1,2和3非常快; 它们只是指针检查.但第四测试使用C等效的getattr(obj, '__trunc__')
,这是相对昂贵的.这必须测试实例,以及字符串的完整MRO,并且没有缓存,最后它会引发一个AttributeError()
格式化错误消息,没有人会看到.所有在这里都没用的工作.
在Python 3中,该getattr()
调用已被替换为快得多的代码.这是因为在Python 3中,不需要考虑旧式类,因此可以直接在实例的类型(类,结果type(instance)
)上查找属性,并且在MRO上的类属性查找被缓存在这点.不需要创建任何例外.
float()
对象实现__int__
方法,这就是为什么int(float('1'))
更快; 你从未__trunc__
在第4步触及属性测试,因为第2步产生了结果.
如果你想查看C代码,对于Python 2,首先看一下int_new()
方法.解析参数后,代码实际上是这样做的:
if (base == -909) // no base argument given, the default is -909
return PyNumber_Int(x); // parse an integer from x, an arbitrary type.
if (PyString_Check(x)) {
// do some error handling; there is a base, so parse the string with the base
return PyInt_FromString(string, NULL, base);
}
Run Code Online (Sandbox Code Playgroud)
无基本情况调用PyNumber_Int()
函数,它执行此操作:
if (PyInt_CheckExact(o)) {
// 1. it's an integer already
// ...
}
m = o->ob_type->tp_as_number;
if (m && m->nb_int) { /* This should include subclasses of int */
// 2. it has an __int__ method, return the result
// ...
}
if (PyInt_Check(o)) { /* An int subclass without nb_int */
// 3. it's an int subclass, extract the value
// ...
}
trunc_func = PyObject_GetAttr(o, trunc_name);
if (trunc_func) {
// 4. it has a __trunc__ method, call it and process the result
// ...
}
if (PyString_Check(o))
// 5. it's a string, lets parse!
return int_from_string(PyString_AS_STRING(o),
PyString_GET_SIZE(o));
Run Code Online (Sandbox Code Playgroud)
where int_from_string()
本质上是一个包装器PyInt_FromString(string, length, 10)
,所以用基数10解析字符串.
在Python 3中,intobject
被删除,只留下longobject
,int()
在Python端重命名.同样,unicode
已经取代了str
.所以我们现在看一下long_new()
,测试一个字符串是用PyUnicode_Check()
而不是PyString_Check()
:
if (obase == NULL)
return PyNumber_Long(x);
// bounds checks on the obase argument, storing a conversion in base
if (PyUnicode_Check(x))
return PyLong_FromUnicodeObject(x, (int)base);
Run Code Online (Sandbox Code Playgroud)
所以当没有设置基数时,我们需要查看PyNumber_Long()
,执行:
if (PyLong_CheckExact(o)) {
// 1. it's an integer already
// ...
}
m = o->ob_type->tp_as_number;
if (m && m->nb_int) { /* This should include subclasses of int */
// 2. it has an __int__ method
// ...
}
trunc_func = _PyObject_LookupSpecial(o, &PyId___trunc__);
if (trunc_func) {
// 3. it has a __trunc__ method
// ...
}
if (PyUnicode_Check(o))
// 5. it's a string
return PyLong_FromUnicodeObject(o, 10);
Run Code Online (Sandbox Code Playgroud)
注意_PyObject_LookupSpecial()
调用,这是特殊的方法查找实现; 它最终使用_PyType_Lookup()
,它使用缓存; 因为没有str.__trunc__
方法在第一次MRO扫描后缓存将永远返回null.此方法也从不引发异常,它只返回请求的方法或null.
float()
处理字符串的方式在Python 2和3之间保持不变,因此您只需要查看Python 2 float_new()
函数,对于字符串来说非常简单:
// test for subclass and retrieve the single x argument
/* If it's a string, but not a string subclass, use
PyFloat_FromString. */
if (PyString_CheckExact(x))
return PyFloat_FromString(x, NULL);
return PyNumber_Float(x);
Run Code Online (Sandbox Code Playgroud)
因此,对于字符串对象,我们直接跳转到解析,否则用于PyNumber_Float()
查找实际float
对象,或使用__float__
方法或字符串子类.
这确实揭示了一种可能的优化:如果int()
要PyString_CheckExact()
在所有其他类型测试之前进行首次测试,那么它将float()
与字符串一样快.PyString_CheckExact()
排除了一个带有__int__
或者__trunc__
方法的字符串子类,所以这是一个很好的第一次测试.
为了解决其他的答案的基础上分析这指责(所以找了0b
,0o
,0
或0x
前缀,情况不区分大小写),默认int()
有一个字符串参数调用并寻找一个基地,基地被硬编码到10.这是传递一个错误在这种情况下带有前缀的字符串中:
>>> int('0x1')
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
ValueError: invalid literal for int() with base 10: '0x1'
Run Code Online (Sandbox Code Playgroud)
仅当您将第二个参数显式设置为时,才会执行基本前缀解析0
:
>>> int('0x1', 0)
1
Run Code Online (Sandbox Code Playgroud)
因为没有__trunc__
对base=0
前缀进行测试解析案例与base
明确设置为任何其他受支持的值一样快:
$ python2.7 -m timeit "int('1')"
1000000 loops, best of 3: 0.472 usec per loop
$ python2.7 -m timeit "int('1', 10)"
1000000 loops, best of 3: 0.268 usec per loop
$ python2.7 bin/python -m timeit "int('1', 0)"
1000000 loops, best of 3: 0.271 usec per loop
$ python2.7 bin/python -m timeit "int('0x1', 0)"
1000000 loops, best of 3: 0.261 usec per loop
Run Code Online (Sandbox Code Playgroud)