如何为 python 中的数字 class 加速 __init__?
How to speed up __init__ for a Number class in python?
作为(现已完成)挑战的一部分,我最终创建了一个分数 class 供使用。在对我的整体代码进行概要分析后,我在 __init__
上花费了不到一半的时间 class 而不是做一些有用的事情。
这里是 __init__
:
class fraction(Number):
__slots__ = ("n", "d")
def __init__(self, n, d, returnint = False) -> None:
if type(n) == int and type(d) == int:
pass
elif type(n) not in (fraction, int) or type(d) not in (fraction, int):
raise TypeError("Can't create fraction ({a}, {b}) numerator and denominator must be int or fraction.".format(a = n, b = d))
else:
if type(n) == fraction:
d = d * n.d
n = n.n
if type(d) == fraction:
n = n * d.d
d = d.n
if d == 0:
raise ValueError("Denominator cannot be zero")
a = n
b = d
while not b == 0:
r = a%b
a = b
b = r
gcd = int(a)
self.n = int(n/gcd)
self.d = int(d/gcd)
下面是 cprofile 对整体代码的示例输出:
1971273 function calls (1935347 primitive calls) in 3.232 seconds
Ordered by: standard name
ncalls tottime percall cumtime percall filename:lineno(function)
...
272503/250039 1.516 0.000 1.541 0.000 doomsday.py:46(__init__)
...
是的,我正在创建它们的 lot 对于我的解决方案的某些变体,我可能会调整算法以避免分数但是......它真的很麻烦,并且在其他情况下,如果没有很多分数,我就无法逃脱。
我还尝试拆分简化步骤(从 a=n
到 gcd = int(a)
)并重新分析 - 这表明 50-60% 的时间都在进行此操作(但增加了 10额外函数调用的 % 开销)。我的 运行 时间还有不到 25% 用于简单地启动对象而不进行实际计算。
我怎样才能加快速度?
感谢评论者指点
tldr;使用 class 会有开销。重构主要代码以使用整数数学更快。
使用对象/class 有明显的开销。我通过两种方式改进了原始代码的性能:
- 使用
isinstance()
代替 type() ==
- 删除简化代码中对
int()
的调用
进行这些更改后,在 __init__
中花费的时间大约有一半是开销(包括 if 语句)。另一半是运行的简化。
基于此,我还重构了我的主要代码,通过实施 Bareiss 算法(请参阅此处的(我的)答案:https://scicomp.stackexchange.com/questions/34512/bareiss-algorithm-vs-lu-decomposition/41262#41262)来使用整数数学来计算矩阵行列式,并避免使用分数可能。
最终代码如下所示:
class fraction(Number):
__slots__ = ("n", "d")
def __init__(self, n, d, returnint = False) -> None:
if isinstance(n, int) and isinstance(d, int):
pass
elif not isinstance(n, (fraction, int)) or not isinstance (d, (fraction, int)):
raise TypeError("Can't create fraction ({a}, {b}) numerator and denominator must be int or fraction.".format(a = n, b = d))
else:
if isinstance(n, fraction):
d = d * n.d
n = n.n
if isinstance(d, fraction):
n = n * d.d
d = d.n
if d == 0:
raise ValueError("Denominator cannot be zero")
a = n
b = d
while not b == 0:
r = a%b
a = b
b = r
gcd = a
self.n = n//gcd
self.d = d//gcd
为了完整起见,这里有一个关于设置对象开销的视图:
from timeit import timeit
class dummy(object):
__slots__ = "i"
def __init__(self, i = 0):
self.i = i
def do_int(x):
a = x
def do_dummy(x):
a = dummy(x)
print("Int")
print(timeit(lambda: do_int(6)))
print("Class")
print(timeit(lambda: do_dummy(6)))
给出:
Int
0.261943900026381
Class
0.7345267999917269
作为(现已完成)挑战的一部分,我最终创建了一个分数 class 供使用。在对我的整体代码进行概要分析后,我在 __init__
上花费了不到一半的时间 class 而不是做一些有用的事情。
这里是 __init__
:
class fraction(Number):
__slots__ = ("n", "d")
def __init__(self, n, d, returnint = False) -> None:
if type(n) == int and type(d) == int:
pass
elif type(n) not in (fraction, int) or type(d) not in (fraction, int):
raise TypeError("Can't create fraction ({a}, {b}) numerator and denominator must be int or fraction.".format(a = n, b = d))
else:
if type(n) == fraction:
d = d * n.d
n = n.n
if type(d) == fraction:
n = n * d.d
d = d.n
if d == 0:
raise ValueError("Denominator cannot be zero")
a = n
b = d
while not b == 0:
r = a%b
a = b
b = r
gcd = int(a)
self.n = int(n/gcd)
self.d = int(d/gcd)
下面是 cprofile 对整体代码的示例输出:
1971273 function calls (1935347 primitive calls) in 3.232 seconds
Ordered by: standard name
ncalls tottime percall cumtime percall filename:lineno(function)
...
272503/250039 1.516 0.000 1.541 0.000 doomsday.py:46(__init__)
...
是的,我正在创建它们的 lot 对于我的解决方案的某些变体,我可能会调整算法以避免分数但是......它真的很麻烦,并且在其他情况下,如果没有很多分数,我就无法逃脱。
我还尝试拆分简化步骤(从 a=n
到 gcd = int(a)
)并重新分析 - 这表明 50-60% 的时间都在进行此操作(但增加了 10额外函数调用的 % 开销)。我的 运行 时间还有不到 25% 用于简单地启动对象而不进行实际计算。
我怎样才能加快速度?
感谢评论者指点
tldr;使用 class 会有开销。重构主要代码以使用整数数学更快。
使用对象/class 有明显的开销。我通过两种方式改进了原始代码的性能:
- 使用
isinstance()
代替type() ==
- 删除简化代码中对
int()
的调用
进行这些更改后,在 __init__
中花费的时间大约有一半是开销(包括 if 语句)。另一半是运行的简化。
基于此,我还重构了我的主要代码,通过实施 Bareiss 算法(请参阅此处的(我的)答案:https://scicomp.stackexchange.com/questions/34512/bareiss-algorithm-vs-lu-decomposition/41262#41262)来使用整数数学来计算矩阵行列式,并避免使用分数可能。
最终代码如下所示:
class fraction(Number):
__slots__ = ("n", "d")
def __init__(self, n, d, returnint = False) -> None:
if isinstance(n, int) and isinstance(d, int):
pass
elif not isinstance(n, (fraction, int)) or not isinstance (d, (fraction, int)):
raise TypeError("Can't create fraction ({a}, {b}) numerator and denominator must be int or fraction.".format(a = n, b = d))
else:
if isinstance(n, fraction):
d = d * n.d
n = n.n
if isinstance(d, fraction):
n = n * d.d
d = d.n
if d == 0:
raise ValueError("Denominator cannot be zero")
a = n
b = d
while not b == 0:
r = a%b
a = b
b = r
gcd = a
self.n = n//gcd
self.d = d//gcd
为了完整起见,这里有一个关于设置对象开销的视图:
from timeit import timeit
class dummy(object):
__slots__ = "i"
def __init__(self, i = 0):
self.i = i
def do_int(x):
a = x
def do_dummy(x):
a = dummy(x)
print("Int")
print(timeit(lambda: do_int(6)))
print("Class")
print(timeit(lambda: do_dummy(6)))
给出:
Int
0.261943900026381
Class
0.7345267999917269