为什么 try ... except 比 if 快?
Why `try ... except` is faster than `if`?
在我的代码中,我有一个列表 l
,我正在从中创建一个列表字典。 (我正在对具有相同 key
的对象进行分组)。通过 try
语句和 if
条件实现它,我在 line_profiler 中注意到前者似乎更有效率:
Line # Hits Time Per Hit % Time Line Contents
==============================================================
293 44378450 59805020 1.3 16.9 for element in l:
# stuff that compute 'key' from 'element'
302 2234869 2235518 1.0 0.6 try:
303 2234869 82486133 36.9 23.3 d[key].append(element)
304 57358 72499 1.3 0.0 except KeyError:
305 57358 1758248 30.7 0.5 d[key] = [element]
对比:
Line # Hits Time Per Hit % Time Line Contents
==============================================================
293 44378450 60185880 1.4 14.0 for element in l:
# stuff that compute 'key' from 'element'
307 2234869 81683512 36.5 19.1 if key in d.keys():
308 2177511 76393595 35.1 17.8 d.get(key).append(element)
309 else:
310 57358 1717679 29.9 0.4 d[key] = [element]
我知道使用 try
时,您只会在引发异常时进入 except
(因此除了少数例外,总的来说它比每次测试条件的成本都低是有道理的),但在这里,即使 Time per hit
对于异常 (1.3+30.7 µs) 也比测试条件 (36.5 µs) 慢。我认为引发异常比检查字典中是否有键(in
只是测试散列键,不?这不是行搜索)的成本更高。那是为什么?
额外的运行时间来自 .keys()
调用。如果你想阻止额外的调用并仍然使用 if
和 else
,请尝试这样的事情:
obj = d.get(key)
if obj:
obj.append(element)
else:
d[key] = [element]
或者您可以使用 defaultdict
在后台执行此操作。示例:
from collections import defaultdict
d = defaultdict(list)
d['123'].append('abc')
您应该认为在每次迭代中您都会浪费一些时间来测试是否有条件检查。如果您不引发异常,您的代码将使用 try except 更快,否则处理异常需要更多时间。
换句话说,如果你确定你的异常是异常的(它只发生在特殊情况下),那么使用 try-except 会更便宜。否则,如果您在约 50% 的情况下排除异常,则最好使用 if.
("it's easier to ask for forgiveness than permission")
try...except
仅当实际引发的异常数量与循环执行次数相当时才会变慢。在您的情况下,异常仅引发 2.5% 的循环迭代。
下面分析四种情况-
def func1():
l = [1,2,3,4]
d = {}
for e in l:
k = e - 1
try:
d[k].append(e)
except KeyError:
d[k] = [e]
return d
def func2():
l = [1,2,3,4]
d = {}
for e in l:
k = e - 1
if k in d.keys():
d.get(k).append(e)
else:
d[k] = [e]
return d
def func3():
l = [1,2,3,4]
d = {}
for e in l:
k = 1
try:
d[k].append(e)
except KeyError:
d[k] = [e]
return d
def func4():
l = [1,2,3,4]
d = {}
for e in l:
k = 1
if k in d.keys():
d.get(k).append(e)
else:
d[k] = [e]
return d
这个的计时结果 -
In [7]: %timeit func1()
The slowest run took 4.17 times longer than the fastest. This could mean that an intermediate result is being cached
100000 loops, best of 3: 2.55 µs per loop
In [8]: %timeit func2()
1000000 loops, best of 3: 1.77 µs per loop
In [10]: %timeit func3()
The slowest run took 4.34 times longer than the fastest. This could mean that an intermediate result is being cached
1000000 loops, best of 3: 2.01 µs per loop
In [11]: %timeit func4()
The slowest run took 6.83 times longer than the fastest. This could mean that an intermediate result is being cached
100000 loops, best of 3: 2.4 µs per loop
在 func1()
和 func2()
的情况下,每个元素都进入一个单独的列表,因此对于每个键, try..except
块引发并捕获一个例外。在那种情况下,func2()
更快。
在 func3()
和 func4()
的情况下,异常只会抛出一次,因此异常的开销只会发生一次,而仍然会为每个键检查条件(即使它存在),这就是为什么在这种情况下,try..except
更快。
我猜你的情况可能会发生类似的情况,同一个密钥被多次计算,因此 try..except
块的速度更快。您可以查看列表中有多少个实际元素以及字典中有多少个键,看看是否是这个原因。
假设 hits
列是特定行被执行的次数,您可以看到,行 -
d[key].append(element)
执行了 2234869 次,而异常仅引发 - 57358 次,仅占元素总数的 2.56%。
我在我的一个 classes 中了解到,处理器试图重新预测指令并将其加载到内存中。
如果你放一个 if 语句,处理器不知道加载哪个代码并且会浪费时间 loaing/unloading 指令等
可能,在发生异常的情况下,处理器假设异常很少见,并在不考虑异常的情况下加载主代码...
我不知道这里是不是这样,但这是我们在代码优化中学到的东西 class。
在我的代码中,我有一个列表 l
,我正在从中创建一个列表字典。 (我正在对具有相同 key
的对象进行分组)。通过 try
语句和 if
条件实现它,我在 line_profiler 中注意到前者似乎更有效率:
Line # Hits Time Per Hit % Time Line Contents
==============================================================
293 44378450 59805020 1.3 16.9 for element in l:
# stuff that compute 'key' from 'element'
302 2234869 2235518 1.0 0.6 try:
303 2234869 82486133 36.9 23.3 d[key].append(element)
304 57358 72499 1.3 0.0 except KeyError:
305 57358 1758248 30.7 0.5 d[key] = [element]
对比:
Line # Hits Time Per Hit % Time Line Contents
==============================================================
293 44378450 60185880 1.4 14.0 for element in l:
# stuff that compute 'key' from 'element'
307 2234869 81683512 36.5 19.1 if key in d.keys():
308 2177511 76393595 35.1 17.8 d.get(key).append(element)
309 else:
310 57358 1717679 29.9 0.4 d[key] = [element]
我知道使用 try
时,您只会在引发异常时进入 except
(因此除了少数例外,总的来说它比每次测试条件的成本都低是有道理的),但在这里,即使 Time per hit
对于异常 (1.3+30.7 µs) 也比测试条件 (36.5 µs) 慢。我认为引发异常比检查字典中是否有键(in
只是测试散列键,不?这不是行搜索)的成本更高。那是为什么?
额外的运行时间来自 .keys()
调用。如果你想阻止额外的调用并仍然使用 if
和 else
,请尝试这样的事情:
obj = d.get(key)
if obj:
obj.append(element)
else:
d[key] = [element]
或者您可以使用 defaultdict
在后台执行此操作。示例:
from collections import defaultdict
d = defaultdict(list)
d['123'].append('abc')
您应该认为在每次迭代中您都会浪费一些时间来测试是否有条件检查。如果您不引发异常,您的代码将使用 try except 更快,否则处理异常需要更多时间。
换句话说,如果你确定你的异常是异常的(它只发生在特殊情况下),那么使用 try-except 会更便宜。否则,如果您在约 50% 的情况下排除异常,则最好使用 if.
("it's easier to ask for forgiveness than permission")
try...except
仅当实际引发的异常数量与循环执行次数相当时才会变慢。在您的情况下,异常仅引发 2.5% 的循环迭代。
下面分析四种情况-
def func1():
l = [1,2,3,4]
d = {}
for e in l:
k = e - 1
try:
d[k].append(e)
except KeyError:
d[k] = [e]
return d
def func2():
l = [1,2,3,4]
d = {}
for e in l:
k = e - 1
if k in d.keys():
d.get(k).append(e)
else:
d[k] = [e]
return d
def func3():
l = [1,2,3,4]
d = {}
for e in l:
k = 1
try:
d[k].append(e)
except KeyError:
d[k] = [e]
return d
def func4():
l = [1,2,3,4]
d = {}
for e in l:
k = 1
if k in d.keys():
d.get(k).append(e)
else:
d[k] = [e]
return d
这个的计时结果 -
In [7]: %timeit func1()
The slowest run took 4.17 times longer than the fastest. This could mean that an intermediate result is being cached
100000 loops, best of 3: 2.55 µs per loop
In [8]: %timeit func2()
1000000 loops, best of 3: 1.77 µs per loop
In [10]: %timeit func3()
The slowest run took 4.34 times longer than the fastest. This could mean that an intermediate result is being cached
1000000 loops, best of 3: 2.01 µs per loop
In [11]: %timeit func4()
The slowest run took 6.83 times longer than the fastest. This could mean that an intermediate result is being cached
100000 loops, best of 3: 2.4 µs per loop
在
func1()
和func2()
的情况下,每个元素都进入一个单独的列表,因此对于每个键,try..except
块引发并捕获一个例外。在那种情况下,func2()
更快。在
func3()
和func4()
的情况下,异常只会抛出一次,因此异常的开销只会发生一次,而仍然会为每个键检查条件(即使它存在),这就是为什么在这种情况下,try..except
更快。
我猜你的情况可能会发生类似的情况,同一个密钥被多次计算,因此 try..except
块的速度更快。您可以查看列表中有多少个实际元素以及字典中有多少个键,看看是否是这个原因。
假设 hits
列是特定行被执行的次数,您可以看到,行 -
d[key].append(element)
执行了 2234869 次,而异常仅引发 - 57358 次,仅占元素总数的 2.56%。
我在我的一个 classes 中了解到,处理器试图重新预测指令并将其加载到内存中。 如果你放一个 if 语句,处理器不知道加载哪个代码并且会浪费时间 loaing/unloading 指令等
可能,在发生异常的情况下,处理器假设异常很少见,并在不考虑异常的情况下加载主代码...
我不知道这里是不是这样,但这是我们在代码优化中学到的东西 class。