为什么 %s 在 python 中的整数替换比 %d 快?
Why is %s faster than %d for integer substitution in python?
我正在查看提到的示例 here, and am looking at this example。我在 ipython 上运行了下面的示例,结果是一致的,即 "%d"
比 "%s"
:
慢
In [1]: def m1():
...: return "%d" % (2*3/5)
In [2]: def m2():
...: return "%s" % (2*3/5)
In [4]: %timeit m1()
1000000 loops, best of 3: 529 ns per loop
In [5]: %timeit m2()
1000000 loops, best of 3: 192 ns per loop
In [6]: from dis import dis
In [7]: dis(m1)
2 0 LOAD_CONST 1 ('%d')
3 LOAD_CONST 5 (6)
6 LOAD_CONST 4 (5)
9 BINARY_DIVIDE
10 BINARY_MODULO
11 RETURN_VALUE
In [9]: dis(m2)
2 0 LOAD_CONST 1 ('%s')
3 LOAD_CONST 5 (6)
6 LOAD_CONST 4 (5)
9 BINARY_DIVIDE
10 BINARY_MODULO
11 RETURN_VALUE
两个代码块很相似,甚至反汇编器的输出也一样,为什么"%s"
比"%d"
快?
这在 hacker news 中讨论过,我只是为 SO 格式化 @nikital 答案:
Objects/stringobject.c
中的函数 PyString_Format
对 %
运算符进行格式化。对于 %s
,它调用 _PyObject_Str
,后者又调用对象上的 str()
。对于 %d
,它调用 formatint
(位于同一文件中)。
整数的 str()
实现在 (Objects/intobject.c
) 中的 int_to_decimal_string
中,它非常简单:
do {
*--p = '0' + (char)(absn % 10);
absn /= 10;
} while (absn);
formatint
的代码要复杂得多,它包含对本机 snprintf 的两次调用:
PyOS_snprintf(fmt, sizeof(fmt), "%s%%%s.%dl%c",
sign, (flags&F_ALT) ? "#" : "",
prec, type);
// ...
PyOS_snprintf(buf, buflen, fmt, -x);
原生 snprintf
更重,因为它处理精度、零填充和类似的东西。
我相信这就是 %d
变慢的原因。 %s
是一个直接的 "divide-by-10-and-subtract" 循环,而 %d
是对完整 sprintf
的两个库调用。但是我实际上并没有分析代码,因为我没有调试版本,所以我可能完全错了。
我正在查看提到的示例 here, and am looking at this example。我在 ipython 上运行了下面的示例,结果是一致的,即 "%d"
比 "%s"
:
In [1]: def m1():
...: return "%d" % (2*3/5)
In [2]: def m2():
...: return "%s" % (2*3/5)
In [4]: %timeit m1()
1000000 loops, best of 3: 529 ns per loop
In [5]: %timeit m2()
1000000 loops, best of 3: 192 ns per loop
In [6]: from dis import dis
In [7]: dis(m1)
2 0 LOAD_CONST 1 ('%d')
3 LOAD_CONST 5 (6)
6 LOAD_CONST 4 (5)
9 BINARY_DIVIDE
10 BINARY_MODULO
11 RETURN_VALUE
In [9]: dis(m2)
2 0 LOAD_CONST 1 ('%s')
3 LOAD_CONST 5 (6)
6 LOAD_CONST 4 (5)
9 BINARY_DIVIDE
10 BINARY_MODULO
11 RETURN_VALUE
两个代码块很相似,甚至反汇编器的输出也一样,为什么"%s"
比"%d"
快?
这在 hacker news 中讨论过,我只是为 SO 格式化 @nikital 答案:
Objects/stringobject.c
中的函数 PyString_Format
对 %
运算符进行格式化。对于 %s
,它调用 _PyObject_Str
,后者又调用对象上的 str()
。对于 %d
,它调用 formatint
(位于同一文件中)。
整数的 str()
实现在 (Objects/intobject.c
) 中的 int_to_decimal_string
中,它非常简单:
do {
*--p = '0' + (char)(absn % 10);
absn /= 10;
} while (absn);
formatint
的代码要复杂得多,它包含对本机 snprintf 的两次调用:
PyOS_snprintf(fmt, sizeof(fmt), "%s%%%s.%dl%c",
sign, (flags&F_ALT) ? "#" : "",
prec, type);
// ...
PyOS_snprintf(buf, buflen, fmt, -x);
原生 snprintf
更重,因为它处理精度、零填充和类似的东西。
我相信这就是 %d
变慢的原因。 %s
是一个直接的 "divide-by-10-and-subtract" 循环,而 %d
是对完整 sprintf
的两个库调用。但是我实际上并没有分析代码,因为我没有调试版本,所以我可能完全错了。