我正在玩弄timeit,并注意到对一个小字符串进行简单的列表理解比对小的单个字符串列表进行相同的操作花费更长的时间。有什么解释吗?这几乎是时间的 1.35 倍。
>>> from timeit import timeit
>>> timeit("[x for x in 'abc']")
2.0691067844831528
>>> timeit("[x for x in ['a', 'b', 'c']]")
1.5286479570345861
在较低级别上发生了什么导致这种情况?
为字符串创建迭代器可能会招致开销。而数组在实例化时已经包含了一个迭代器。
编辑:
>>> timeit("[x for x in ['a','b','c']]")
0.3818681240081787
>>> timeit("[x for x in 'abc']")
0.3732869625091553
这是在我的mac book pro i7上用2.7运行的。这可能是系统配置差异的结果。
当您遍历大多数容器对象(列表、元组、字典…)时,迭代器会传递容器中的对象。
但是当您迭代字符串时,必须为传递的每个字符创建一个新对象——字符串不是“容器”,就像列表是容器一样。在迭代创建这些对象之前,字符串中的各个字符并不作为不同的对象存在。
>
对于Python 2,一旦去除了大量开销,实际速度差异接近70%(或更多)。
对象创建没有错。这两个方法都没有创建新对象,因为缓存了单字符字符串。
这种差异并不明显,但很可能是由于对字符串索引进行了大量检查而产生的,涉及到类型和格式良好性。这也很有可能是因为需要检查要返回什么。
列表索引非常快。
>>> python3 -m timeit '[x for x in "abc"]'
1000000 loops, best of 3: 0.388 usec per loop
>>> python3 -m timeit '[x for x in ["a", "b", "c"]]'
1000000 loops, best of 3: 0.436 usec per loop
这和你发现的不一致…
那么,您必须使用Python 2。
>>> python2 -m timeit '[x for x in "abc"]'
1000000 loops, best of 3: 0.309 usec per loop
>>> python2 -m timeit '[x for x in ["a", "b", "c"]]'
1000000 loops, best of 3: 0.212 usec per loop
我们来解释一下版本之间的区别。我将检查编译后的代码。
对于Python 3:
import dis
def list_iterate():
[item for item in ["a", "b", "c"]]
dis.dis(list_iterate)
#>>> 4 0 LOAD_CONST 1 (<code object <listcomp> at 0x7f4d06b118a0, file "", line 4>)
#>>> 3 LOAD_CONST 2 ('list_iterate.<locals>.<listcomp>')
#>>> 6 MAKE_FUNCTION 0
#>>> 9 LOAD_CONST 3 ('a')
#>>> 12 LOAD_CONST 4 ('b')
#>>> 15 LOAD_CONST 5 ('c')
#>>> 18 BUILD_LIST 3
#>>> 21 GET_ITER
#>>> 22 CALL_FUNCTION 1 (1 positional, 0 keyword pair)
#>>> 25 POP_TOP
#>>> 26 LOAD_CONST 0 (None)
#>>> 29 RETURN_VALUE
def string_iterate():
[item for item in "abc"]
dis.dis(string_iterate)
#>>> 21 0 LOAD_CONST 1 (<code object <listcomp> at 0x7f4d06b17150, file "", line 21>)
#>>> 3 LOAD_CONST 2 ('string_iterate.<locals>.<listcomp>')
#>>> 6 MAKE_FUNCTION 0
#>>> 9 LOAD_CONST 3 ('abc')
#>>> 12 GET_ITER
#>>> 13 CALL_FUNCTION 1 (1 positional, 0 keyword pair)
#>>> 16 POP_TOP
#>>> 17 LOAD_CONST 0 (None)
#>>> 20 RETURN_VALUE
您可以在这里看到,由于每次构建列表,列表变体可能会变慢。
这就是
9 LOAD_CONST 3 ('a')
12 LOAD_CONST 4 ('b')
15 LOAD_CONST 5 ('c')
18 BUILD_LIST 3
部分。字符串变量只有
9 LOAD_CONST 3 ('abc')
你可以检查一下,这似乎确实有所不同:
def string_iterate():
[item for item in ("a", "b", "c")]
dis.dis(string_iterate)
#>>> 35 0 LOAD_CONST 1 (<code object <listcomp> at 0x7f4d068be660, file "", line 35>)
#>>> 3 LOAD_CONST 2 ('string_iterate.<locals>.<listcomp>')
#>>> 6 MAKE_FUNCTION 0
#>>> 9 LOAD_CONST 6 (('a', 'b', 'c'))
#>>> 12 GET_ITER
#>>> 13 CALL_FUNCTION 1 (1 positional, 0 keyword pair)
#>>> 16 POP_TOP
#>>> 17 LOAD_CONST 0 (None)
#>>> 20 RETURN_VALUE
这只会产生
9 LOAD_CONST 6 (('a', 'b', 'c'))
因为元组是不可变的。测试:
>>> python3 -m timeit '[x for x in ("a", "b", "c")]'
1000000 loops, best of 3: 0.369 usec per loop
很好,继续吧。
对于 Python 2:
def list_iterate():
[item for item in ["a", "b", "c"]]
dis.dis(list_iterate)
#>>> 2 0 BUILD_LIST 0
#>>> 3 LOAD_CONST 1 ('a')
#>>> 6 LOAD_CONST 2 ('b')
#>>> 9 LOAD_CONST 3 ('c')
#>>> 12 BUILD_LIST 3
#>>> 15 GET_ITER
#>>> >> 16 FOR_ITER 12 (to 31)
#>>> 19 STORE_FAST 0 (item)
#>>> 22 LOAD_FAST 0 (item)
#>>> 25 LIST_APPEND 2
#>>> 28 JUMP_ABSOLUTE 16
#>>> >> 31 POP_TOP
#>>> 32 LOAD_CONST 0 (None)
#>>> 35 RETURN_VALUE
def string_iterate():
[item for item in "abc"]
dis.dis(string_iterate)
#>>> 2 0 BUILD_LIST 0
#>>> 3 LOAD_CONST 1 ('abc')
#>>> 6 GET_ITER
#>>> >> 7 FOR_ITER 12 (to 22)
#>>> 10 STORE_FAST 0 (item)
#>>> 13 LOAD_FAST 0 (item)
#>>> 16 LIST_APPEND 2
#>>> 19 JUMP_ABSOLUTE 7
#>>> >> 22 POP_TOP
#>>> 23 LOAD_CONST 0 (None)
#>>> 26 RETURN_VALUE
奇怪的是,我们有相同的列表,但它仍然更快。Python 2的动作快得出奇。
让我们删除理解并重新计时。_=
是为了防止它被优化掉。
>>> python3 -m timeit '_ = ["a", "b", "c"]'
10000000 loops, best of 3: 0.0707 usec per loop
>>> python3 -m timeit '_ = "abc"'
100000000 loops, best of 3: 0.0171 usec per loop
我们可以看到,初始化的重要性不足以解释版本之间的差异(这些数字很小)!我们由此可以得出结论,Python 3的理解速度较慢。这是有意义的,因为Python 3改变了理解以具有更安全的范围。
好吧,现在改进基准测试(我只是删除了不是迭代的开销)。这将通过预分配可迭代对象来删除可迭代对象的构建:
>>> python3 -m timeit -s 'iterable = "abc"' '[x for x in iterable]'
1000000 loops, best of 3: 0.387 usec per loop
>>> python3 -m timeit -s 'iterable = ["a", "b", "c"]' '[x for x in iterable]'
1000000 loops, best of 3: 0.368 usec per loop
>>> python2 -m timeit -s 'iterable = "abc"' '[x for x in iterable]'
1000000 loops, best of 3: 0.309 usec per loop
>>> python2 -m timeit -s 'iterable = ["a", "b", "c"]' '[x for x in iterable]'
10000000 loops, best of 3: 0.164 usec per loop
我们可以检查调用iter
是否是开销:
>>> python3 -m timeit -s 'iterable = "abc"' 'iter(iterable)'
10000000 loops, best of 3: 0.099 usec per loop
>>> python3 -m timeit -s 'iterable = ["a", "b", "c"]' 'iter(iterable)'
10000000 loops, best of 3: 0.1 usec per loop
>>> python2 -m timeit -s 'iterable = "abc"' 'iter(iterable)'
10000000 loops, best of 3: 0.0913 usec per loop
>>> python2 -m timeit -s 'iterable = ["a", "b", "c"]' 'iter(iterable)'
10000000 loops, best of 3: 0.0854 usec per loop
不。不,不是。差异太小了,特别是对于Python 3。
因此,让我们通过使整个事情变慢来消除更多不需要的开销!目标只是进行更长的迭代,以便时间隐藏开销。
>>> python3 -m timeit -s 'import random; iterable = "".join(chr(random.randint(0, 127)) for _ in range(100000))' '[x for x in iterable]'
100 loops, best of 3: 3.12 msec per loop
>>> python3 -m timeit -s 'import random; iterable = [chr(random.randint(0, 127)) for _ in range(100000)]' '[x for x in iterable]'
100 loops, best of 3: 2.77 msec per loop
>>> python2 -m timeit -s 'import random; iterable = "".join(chr(random.randint(0, 127)) for _ in range(100000))' '[x for x in iterable]'
100 loops, best of 3: 2.32 msec per loop
>>> python2 -m timeit -s 'import random; iterable = [chr(random.randint(0, 127)) for _ in range(100000)]' '[x for x in iterable]'
100 loops, best of 3: 2.09 msec per loop
这实际上并没有太大变化,但它有所帮助。
所以去掉理解。开销不是问题的一部分:
>>> python3 -m timeit -s 'import random; iterable = "".join(chr(random.randint(0, 127)) for _ in range(100000))' 'for x in iterable: pass'
1000 loops, best of 3: 1.71 msec per loop
>>> python3 -m timeit -s 'import random; iterable = [chr(random.randint(0, 127)) for _ in range(100000)]' 'for x in iterable: pass'
1000 loops, best of 3: 1.36 msec per loop
>>> python2 -m timeit -s 'import random; iterable = "".join(chr(random.randint(0, 127)) for _ in range(100000))' 'for x in iterable: pass'
1000 loops, best of 3: 1.27 msec per loop
>>> python2 -m timeit -s 'import random; iterable = [chr(random.randint(0, 127)) for _ in range(100000)]' 'for x in iterable: pass'
1000 loops, best of 3: 935 usec per loop
这还差不多!通过使用< code > dequee 进行迭代,我们可以获得更快的速度。基本相同,但速度更快:
>>> python3 -m timeit -s 'import random; from collections import deque; iterable = "".join(chr(random.randint(0, 127)) for _ in range(100000))' 'deque(iterable, maxlen=0)'
1000 loops, best of 3: 777 usec per loop
>>> python3 -m timeit -s 'import random; from collections import deque; iterable = [chr(random.randint(0, 127)) for _ in range(100000)]' 'deque(iterable, maxlen=0)'
1000 loops, best of 3: 405 usec per loop
>>> python2 -m timeit -s 'import random; from collections import deque; iterable = "".join(chr(random.randint(0, 127)) for _ in range(100000))' 'deque(iterable, maxlen=0)'
1000 loops, best of 3: 805 usec per loop
>>> python2 -m timeit -s 'import random; from collections import deque; iterable = [chr(random.randint(0, 127)) for _ in range(100000)]' 'deque(iterable, maxlen=0)'
1000 loops, best of 3: 438 usec per loop
让我印象深刻的是Unicode和bytestrings的竞争力。我们可以通过在以下两种语言中尝试< code>bytes和< code>unicode来明确检查这一点:
>
字节
>>> python3 -m timeit -s 'import random; from collections import deque; iterable = b"".join(chr(random.randint(0, 127)).encode("ascii") for _ in range(100000))' 'deque(iterable, maxlen=0)' :(
1000 loops, best of 3: 571 usec per loop
>>> python3 -m timeit -s 'import random; from collections import deque; iterable = [chr(random.randint(0, 127)).encode("ascii") for _ in range(100000)]' 'deque(iterable, maxlen=0)'
1000 loops, best of 3: 394 usec per loop
>>> python2 -m timeit -s 'import random; from collections import deque; iterable = b"".join(chr(random.randint(0, 127)) for _ in range(100000))' 'deque(iterable, maxlen=0)'
1000 loops, best of 3: 757 usec per loop
>>> python2 -m timeit -s 'import random; from collections import deque; iterable = [chr(random.randint(0, 127)) for _ in range(100000)]' 'deque(iterable, maxlen=0)'
1000 loops, best of 3: 438 usec per loop
在这里,您可以看到Python 3实际上比Python 2快。
统一码
>>> python3 -m timeit -s 'import random; from collections import deque; iterable = u"".join( chr(random.randint(0, 127)) for _ in range(100000))' 'deque(iterable, maxlen=0)'
1000 loops, best of 3: 800 usec per loop
>>> python3 -m timeit -s 'import random; from collections import deque; iterable = [ chr(random.randint(0, 127)) for _ in range(100000)]' 'deque(iterable, maxlen=0)'
1000 loops, best of 3: 394 usec per loop
>>> python2 -m timeit -s 'import random; from collections import deque; iterable = u"".join(unichr(random.randint(0, 127)) for _ in range(100000))' 'deque(iterable, maxlen=0)'
1000 loops, best of 3: 1.07 msec per loop
>>> python2 -m timeit -s 'import random; from collections import deque; iterable = [unichr(random.randint(0, 127)) for _ in range(100000)]' 'deque(iterable, maxlen=0)'
1000 loops, best of 3: 469 usec per loop
同样,Python 3更快,尽管这是意料之中的(str
在Python3中引起了很多关注)。
事实上,这种 unicode
字节
差异非常小,这令人印象深刻。
所以让我们分析一下这个案例,因为它对我来说又快又方便:
>>> python3 -m timeit -s 'import random; from collections import deque; iterable = "".join(chr(random.randint(0, 127)) for _ in range(100000))' 'deque(iterable, maxlen=0)'
1000 loops, best of 3: 777 usec per loop
>>> python3 -m timeit -s 'import random; from collections import deque; iterable = [chr(random.randint(0, 127)) for _ in range(100000)]' 'deque(iterable, maxlen=0)'
1000 loops, best of 3: 405 usec per loop
我们实际上可以排除蒂姆·彼得 10 倍赞成的答案!
>>> foo = iterable[123]
>>> iterable[36] is foo
True
但值得一提的是:索引成本。区别可能在于索引,因此删除迭代并仅索引:
>>> python3 -m timeit -s 'import random; iterable = "".join(chr(random.randint(0, 127)) for _ in range(100000))' 'iterable[123]'
10000000 loops, best of 3: 0.0397 usec per loop
>>> python3 -m timeit -s 'import random; iterable = [chr(random.randint(0, 127)) for _ in range(100000)]' 'iterable[123]'
10000000 loops, best of 3: 0.0374 usec per loop
差异似乎很小,但至少有一半的成本是间接费用:
>>> python3 -m timeit -s 'import random; iterable = [chr(random.randint(0, 127)) for _ in range(100000)]' 'iterable; 123'
100000000 loops, best of 3: 0.0173 usec per loop
所以速度差足以决定责备它。我认为。
那么为什么索引列表要快得多呢?
好吧,我会再谈这个问题,但我的猜测是,这取决于对内部字符串(或缓存字符,如果是单独的机制)的检查。这将不如最佳速度快。但我会去查资料来源(尽管我对C…不太满意):)。
来源是这样的:
static PyObject *
unicode_getitem(PyObject *self, Py_ssize_t index)
{
void *data;
enum PyUnicode_Kind kind;
Py_UCS4 ch;
PyObject *res;
if (!PyUnicode_Check(self) || PyUnicode_READY(self) == -1) {
PyErr_BadArgument();
return NULL;
}
if (index < 0 || index >= PyUnicode_GET_LENGTH(self)) {
PyErr_SetString(PyExc_IndexError, "string index out of range");
return NULL;
}
kind = PyUnicode_KIND(self);
data = PyUnicode_DATA(self);
ch = PyUnicode_READ(kind, data, index);
if (ch < 256)
return get_latin1_char(ch);
res = PyUnicode_New(1, ch);
if (res == NULL)
return NULL;
kind = PyUnicode_KIND(res);
data = PyUnicode_DATA(res);
PyUnicode_WRITE(kind, data, 0, ch);
assert(_PyUnicode_CheckConsistency(res, 1));
return res;
}
从顶部走,我们会有一些检查。这些很无聊。然后一些赋值,应该也是无聊的。第一行有趣的是
ch = PyUnicode_READ(kind, data, index);
但我们希望这很快,因为我们通过索引从连续的 C 数组中读取它。结果 ch 将小于 256,因此我们将返回
get_latin1_char(ch)
中的缓存字符。
所以我们将运行(放弃第一次检查)
kind = PyUnicode_KIND(self);
data = PyUnicode_DATA(self);
ch = PyUnicode_READ(kind, data, index);
return get_latin1_char(ch);
在哪里
#define PyUnicode_KIND(op) \
(assert(PyUnicode_Check(op)), \
assert(PyUnicode_IS_READY(op)), \
((PyASCIIObject *)(op))->state.kind)
(这很无聊,因为在调试中会忽略断言[这样我可以检查它们是否快速]和<code>((PyASCIIObject*)(op))-
#define PyUnicode_DATA(op) \
(assert(PyUnicode_Check(op)), \
PyUnicode_IS_COMPACT(op) ? _PyUnicode_COMPACT_DATA(op) : \
_PyUnicode_NONCOMPACT_DATA(op))
(由于类似的原因,假设宏(Something_CAPITALIZED
)都很快,
#define PyUnicode_READ(kind, data, index) \
((Py_UCS4) \
((kind) == PyUnicode_1BYTE_KIND ? \
((const Py_UCS1 *)(data))[(index)] : \
((kind) == PyUnicode_2BYTE_KIND ? \
((const Py_UCS2 *)(data))[(index)] : \
((const Py_UCS4 *)(data))[(index)] \
) \
))
(这涉及到索引,但真的一点也不慢)和
static PyObject*
get_latin1_char(unsigned char ch)
{
PyObject *unicode = unicode_latin1[ch];
if (!unicode) {
unicode = PyUnicode_New(1, ch);
if (!unicode)
return NULL;
PyUnicode_1BYTE_DATA(unicode)[0] = ch;
assert(_PyUnicode_CheckConsistency(unicode, 1));
unicode_latin1[ch] = unicode;
}
Py_INCREF(unicode);
return unicode;
}
这证实了我的怀疑:
> < li>
这是缓存的:
PyObject *unicode = unicode_latin1[ch];
这应该很快。if(! Unicode)
未运行,因此在这种情况下,它实际上相当于
PyObject *unicode = unicode_latin1[ch];
Py_INCREF(unicode);
return unicode;
老实说,在测试了断言
的速度之后(通过禁用它们[我认为它适用于C级断言…]),唯一看似缓慢的部分是:
PyUnicode_IS_COMPACT(op)
_PyUnicode_COMPACT_DATA(op)
_PyUnicode_NONCOMPACT_DATA(op)
它们是:
#define PyUnicode_IS_COMPACT(op) \
(((PyASCIIObject*)(op))->state.compact)
(快,和以前一样),
#define _PyUnicode_COMPACT_DATA(op) \
(PyUnicode_IS_ASCII(op) ? \
((void*)((PyASCIIObject*)(op) + 1)) : \
((void*)((PyCompactUnicodeObject*)(op) + 1)))
(如果宏< code>IS_ASCII是快速的,则为快速),以及
#define _PyUnicode_NONCOMPACT_DATA(op) \
(assert(((PyUnicodeObject*)(op))->data.any), \
((((PyUnicodeObject *)(op))->data.any)))
(也快,因为它是断言加间接加转换)。
所以我们在(兔子洞)下面:
PyUnicode_IS_ASCII
这是
#define PyUnicode_IS_ASCII(op) \
(assert(PyUnicode_Check(op)), \
assert(PyUnicode_IS_READY(op)), \
((PyASCIIObject*)op)->state.ascii)
嗯…这看起来也很快。。。
嗯,好吧,但是让我们把它与< code>PyList_GetItem进行比较。(是的,感谢蒂姆·彼得斯给我更多的工作做:p。)
PyObject *
PyList_GetItem(PyObject *op, Py_ssize_t i)
{
if (!PyList_Check(op)) {
PyErr_BadInternalCall();
return NULL;
}
if (i < 0 || i >= Py_SIZE(op)) {
if (indexerr == NULL) {
indexerr = PyUnicode_FromString(
"list index out of range");
if (indexerr == NULL)
return NULL;
}
PyErr_SetObject(PyExc_IndexError, indexerr);
return NULL;
}
return ((PyListObject *)op) -> ob_item[i];
}
我们可以看到,在非错误情况下,这将运行:
PyList_Check(op)
Py_SIZE(op)
((PyListObject *)op) -> ob_item[i]
PyList_Check
在哪里
#define PyList_Check(op) \
PyType_FastSubclass(Py_TYPE(op), Py_TPFLAGS_LIST_SUBCLASS)
<罢工> ( 标签!标签!!!) ( 问题21587) 在5分钟内修复并合并。喜欢...是啊。该死的。他们让双向飞碟蒙羞。
#define Py_SIZE(ob) (((PyVarObject*)(ob))->ob_size)
#define PyType_FastSubclass(t,f) PyType_HasFeature(t,f)
#ifdef Py_LIMITED_API
#define PyType_HasFeature(t,f) ((PyType_GetFlags(t) & (f)) != 0)
#else
#define PyType_HasFeature(t,f) (((t)->tp_flags & (f)) != 0)
#endif
所以这通常是非常简单的(两个间接和几个布尔检查),除非Py_LIMITED_API
打开,在这种情况下…???
然后是索引和强制转换(((PyListObject *)op) -
因此,对列表的检查肯定更少,微小的速度差异肯定意味着它可能是相关的。
我认为一般来说,有更多的类型检查和间接<代码>(-
问题内容: 显然,它不是根据长度来比较它们,而是通过编码来比较它们。但是,我不知道它是如何工作的。我需要一些解释:-) 问题答案: 字符串按字典顺序进行比较。即逐个字符,直到它们不相等或没有要比较的字符为止。“11”的首字符小于“ 3”的首字符。 如果我们使用字母,则因为不小于,不小于,但是由于小于,小于。 您可以将字符串显式转换为数字:
问题内容: 我是Java的新手:) 我有2个字符串列表,我想知道比较这两者的最有效方法是什么,并得到一个包含另一个字符串的结果数组。例如,我有一个名为oldStrings的列表和一个名为Strings的列表。我已经看过Comparator函数,但是还不完全了解它的工作原理,现在我想我可以创建一个for循环,遍历每个字符串,然后保存该字符串: 此列表中最多包含200个字符串。这是解决此问题的最佳方法
我试图找到给定字符串的排列,但我想使用迭代。我在网上找到的递归解决方案,我确实理解它,但是将其转换为迭代解决方案真的行不通。下面我附上了我的代码。我真的很感激你的帮助:
问题内容: 我有一个Android应用程序,我想检查安装的应用程序名称是否与传递给包含此代码的函数的字符串匹配。代码和示例如下: 假设您打过电话,并且手机上的应用程序名称与返回的名称相同。但是,它永远不会。我记录了结果,它应该匹配,但事实并非如此。任何人都可以请问我为什么这行不通吗? 问题答案: 使用String的equals()方法代替==运算符来比较字符串: 在Java中,新手遇到的最常见错误
问题内容: 使用以下代码时,我们的代码需要10分钟才能虹吸68,000条记录: 但是,当我们执行以下操作时,仅需1秒钟: 这是代码: 我曾经用python编写过的所有代码都使用第一个选项。这只是基本的字符串操作…我们正在从文件中读取输入,对其进行处理并将其输出到新文件中。我100%确信第一种方法的运行时间比第二种方法长约600倍,但是为什么呢? 正在处理的文件是csv,但使用〜而不是逗号。我们在这
本文向大家介绍C语言字符串大小比较,包括了C语言字符串大小比较的使用技巧和注意事项,需要的朋友参考一下 C语言字符串大小比较 以上所述就是本文的全部内容了,希望大家能够喜欢。