Python 3.5:slice vs islice vs alternatives?效率比较
Python 3.5: slice vs islice vs alternatives? Efficiency comparison
上下文
这是关于效率的一般性问题。
我有一个列表,我需要列表中的连续 运行 / 子列表。通常,这是通过切片完成的:
my_list[start:end]
但是,slice 会生成原始列表的副本(至少是原始列表的引用)。因此,此操作可能比不执行此操作慢。
islice
是一种替代方法,它代替了迭代器。由于我只关心将所有值集中在一起,而不是对它们进行迭代,因此我将不得不键入 cast:
list(islice(my_list, start, end))
背景工作
为了做一些比较,我在列表中随机 sliced/isliced 10 次,列表的大小从 1 增加到 10,000:
is_vals = []
s_vals = []
for l in range(1, 10000):
my_list = [random.random() for k in range(l)]
for p in range(10):
i = random.randint(0, l)
j = random.randint(0, l)
if i < j:
start_time = time.clock()
list(islice(my_list, i, j))
is_vals.append(time.clock() - start_time)
start_time = time.clock()
my_list[i:j]
s_vals.append(time.clock() - start_time)
else:
start_time = time.clock()
list(islice(my_list, j, i))
is_vals.append(time.clock() - start_time)
start_time = time.clock()
my_list[j:i]
s_vals.append(time.clock() - start_time)
print(statistics.mean(is_vals) - statistics.mean(s_vals))
我发现 slice 仍然更快,islice 和 slice 之间的差异是 2.99e-05。
我不确定,但我会继续将其归结为对迭代器对象进行类型转换。
问题
有没有比 slice 更有效的方法来获取列表中的连续 运行 / 子列表?
奖金:有没有办法或多或少地将列表/元组类型转换为切片?例如把 [i,j] 变成 i:j?
你无法在速度上击败 mylist[start:stop]
,不。如果您想要 新列表对象 包含来自输入列表连续区域的相同元素,则不是。
那是因为 list
类型实现可以直接访问列表对象的内部存储。您无法从外部更快地访问这些元素。
只有在内存效率很重要时才使用迭代器。迭代器增加了迭代速度开销,它们通常 不 更快。在这种情况下,表达式 list(islice(my_list, start, stop))
将执行以下工作:
- 为
my_list
创建一个列表迭代器对象;这将在您迭代它时从 my_list
中产生元素。
- 创建一个新的
islice()
迭代器对象;这将跳过列表迭代器中的 start
个元素,然后生成值,直到到达 stop
索引。
- 从
islice()
迭代器对象生成一个迭代器。在这种情况下,它只会重复使用同一个对象,但这仍然是一个单独的 (C) 函数调用。
- 从第 3 步中生成的迭代器对象生成的所有元素生成一个新的列表对象。
另一方面,mylist[start:stop]
调用只会执行以下操作:
- 呼叫
mylist.__getitem__(slice(start, stop))
。此方法直接生成一个新的列表对象,其中的元素从其内部数组直接复制到新的列表对象数组。
import random
import time
from itertools import islice
import statistics
l = 1000000
is_vals, s_vals = [], []
my_list = [random.random() for _ in range(l)]
for p in range(10):
i = random.randint(0, l//3)
j = random.randint(l-l//3, l)
start_time = time.clock()
sum1 = 0
for k in islice(my_list, i, j):
sum1 += k
is_vals.append(time.clock() - start_time)
start_time = time.clock()
sum2 = 0
for k in my_list[i:j]:
sum2 += k
s_vals.append(time.clock() - start_time)
assert sum1 == sum2
print(is_vals)
print(s_vals)
print(statistics.mean(is_vals)-statistics.mean(s_vals))
这表明 islice 比 slice 稍快。这是因为 Python 解释器创建了一个新列表 (my_list[i:j]),然后在行
中对其进行迭代
for k in my_list[i:j]:
而在行
for k in islice(my_list, i, j):
它不创建新列表,直接从第 i 个索引到第 j 个索引遍历 my_list。然而,当你写
list(islice(my_list, i, j))
同时创建了新列表,因此您看不出有什么比 slice 更好的地方。
上下文
这是关于效率的一般性问题。 我有一个列表,我需要列表中的连续 运行 / 子列表。通常,这是通过切片完成的:
my_list[start:end]
但是,slice 会生成原始列表的副本(至少是原始列表的引用)。因此,此操作可能比不执行此操作慢。
islice
是一种替代方法,它代替了迭代器。由于我只关心将所有值集中在一起,而不是对它们进行迭代,因此我将不得不键入 cast:
list(islice(my_list, start, end))
背景工作
为了做一些比较,我在列表中随机 sliced/isliced 10 次,列表的大小从 1 增加到 10,000:
is_vals = []
s_vals = []
for l in range(1, 10000):
my_list = [random.random() for k in range(l)]
for p in range(10):
i = random.randint(0, l)
j = random.randint(0, l)
if i < j:
start_time = time.clock()
list(islice(my_list, i, j))
is_vals.append(time.clock() - start_time)
start_time = time.clock()
my_list[i:j]
s_vals.append(time.clock() - start_time)
else:
start_time = time.clock()
list(islice(my_list, j, i))
is_vals.append(time.clock() - start_time)
start_time = time.clock()
my_list[j:i]
s_vals.append(time.clock() - start_time)
print(statistics.mean(is_vals) - statistics.mean(s_vals))
我发现 slice 仍然更快,islice 和 slice 之间的差异是 2.99e-05。
我不确定,但我会继续将其归结为对迭代器对象进行类型转换。
问题
有没有比 slice 更有效的方法来获取列表中的连续 运行 / 子列表?
奖金:有没有办法或多或少地将列表/元组类型转换为切片?例如把 [i,j] 变成 i:j?
你无法在速度上击败 mylist[start:stop]
,不。如果您想要 新列表对象 包含来自输入列表连续区域的相同元素,则不是。
那是因为 list
类型实现可以直接访问列表对象的内部存储。您无法从外部更快地访问这些元素。
只有在内存效率很重要时才使用迭代器。迭代器增加了迭代速度开销,它们通常 不 更快。在这种情况下,表达式 list(islice(my_list, start, stop))
将执行以下工作:
- 为
my_list
创建一个列表迭代器对象;这将在您迭代它时从my_list
中产生元素。 - 创建一个新的
islice()
迭代器对象;这将跳过列表迭代器中的start
个元素,然后生成值,直到到达stop
索引。 - 从
islice()
迭代器对象生成一个迭代器。在这种情况下,它只会重复使用同一个对象,但这仍然是一个单独的 (C) 函数调用。 - 从第 3 步中生成的迭代器对象生成的所有元素生成一个新的列表对象。
另一方面,mylist[start:stop]
调用只会执行以下操作:
- 呼叫
mylist.__getitem__(slice(start, stop))
。此方法直接生成一个新的列表对象,其中的元素从其内部数组直接复制到新的列表对象数组。
import random
import time
from itertools import islice
import statistics
l = 1000000
is_vals, s_vals = [], []
my_list = [random.random() for _ in range(l)]
for p in range(10):
i = random.randint(0, l//3)
j = random.randint(l-l//3, l)
start_time = time.clock()
sum1 = 0
for k in islice(my_list, i, j):
sum1 += k
is_vals.append(time.clock() - start_time)
start_time = time.clock()
sum2 = 0
for k in my_list[i:j]:
sum2 += k
s_vals.append(time.clock() - start_time)
assert sum1 == sum2
print(is_vals)
print(s_vals)
print(statistics.mean(is_vals)-statistics.mean(s_vals))
这表明 islice 比 slice 稍快。这是因为 Python 解释器创建了一个新列表 (my_list[i:j]),然后在行
中对其进行迭代for k in my_list[i:j]:
而在行
for k in islice(my_list, i, j):
它不创建新列表,直接从第 i 个索引到第 j 个索引遍历 my_list。然而,当你写
list(islice(my_list, i, j))
同时创建了新列表,因此您看不出有什么比 slice 更好的地方。