【问题标题】:Performance of accessing class variables in Python在 Python 中访问类变量的性能
【发布时间】:2012-05-22 14:00:20
【问题描述】:

我想知道在在同一个类的方法中使用以下方法访问类变量(字典)时,性能是否有任何差异:

self.class_variable_dict.add(some_key, some_value)

ClassName.class_variable_dict.add(some_key, some_value)

显然,只要没有同名的实例变量,两者都可以工作,但是有什么理由/用例我们应该更喜欢一个而不是另一个?

【问题讨论】:

    标签: python global-variables scope


    【解决方案1】:

    通过ClassName 而不是通过self 访问它会稍微快一些,因为如果你通过self 访问它,它必须首先检查实例命名空间。但我不认为差异会很显着,除非你有分析信息表明它是显着的。

    因此,我建议使用您认为作为人类更容易阅读/理解的任何一种。

    从语义上讲,只有当class_variable_dict 变量在某处被遮蔽时,它们才会有所不同——特别是,如果 (a) self 定义了一个同名变量;或 (b) selfClassName 的子类的一个实例,并且该子类(或其仍然是 ClassName 的子类的基础之一)定义了一个同名变量。如果这些都不正确,那么它们在语义上应该是相同的。

    编辑:

    delnam 有一个很好的观点:有些因素可能会加快速度。我坚持我的断言,除非它处于非常紧密的循环中,否则差异将是微不足道的。为了测试它,我创建了我能想到的最紧凑的循环,并使用timeit 对其进行计时。结果如下:

    • 通过类 var 访问: 20.226 秒
    • 通过 inst var 访问: 23.121 秒

    基于多次运行,误差线看起来大约为 1 秒 - 即,这是一个统计学上的显着差异,但可能不值得担心。这是我的测试代码:

    import timeit
    
    setup='''
    class A:
        var = {}
        def f1(self):
            x = A.var
        def f2(self):
            x = self.var
    
    a = A()
    '''
    print 'access via class var: %.3f' % timeit.timeit('a.f1()', setup=setup, number=100000000)
    print 'access via inst var: %.3f' % timeit.timeit('a.f2()', setup=setup, number=100000000)
    

    【讨论】:

    • OTOH,查找 self 会更快,因为它是本地的,因此可以从 C 数组访问,而 ClassName 是全局的,必须在字典中查找。除了分析之外,没有办法知道哪个更快!
    【解决方案2】:

    让我们看看不同的选项都有什么作用。

    In [1]: class Foo:
       ...:     bar = {}
       ...:     
    
    In [2]: import dis
    In [3]: dis.dis(lambda: Foo.bar.add(1,2))
      1           0 LOAD_GLOBAL              0 (Foo) 
                  3 LOAD_ATTR                1 (bar) 
                  6 LOAD_ATTR                2 (add) 
                  9 LOAD_CONST               1 (1) 
                 12 LOAD_CONST               2 (2) 
                 15 CALL_FUNCTION            2 
                 18 RETURN_VALUE         
    
    In [4]: dis.dis(lambda: Foo().bar.add(1,2))
      1           0 LOAD_GLOBAL              0 (Foo) 
                  3 CALL_FUNCTION            0 
                  6 LOAD_ATTR                1 (bar) 
                  9 LOAD_ATTR                2 (add) 
                 12 LOAD_CONST               1 (1) 
                 15 LOAD_CONST               2 (2) 
                 18 CALL_FUNCTION            2 
                 21 RETURN_VALUE 
    

    从中可以看出,除了在第二种情况下创建对象之外,两种样式都会生成相同的字节码。


    另一方面,这并不重要。使用以最精确的方式表达您的目标的任何内容。仅在速度很重要时才进行优化。

    我建议只使用ClassName.dict_value[key] = value

    【讨论】:

    • 如果你真的关心速度,那么显然你根本不会把它放在一个类中,而是有一个全局模块_classname_dict_value,除了有人可以访问你之外的dict_value之外没有其他好处模块范围。
    • @senderle:字节码只是在实例化对象时有所不同。
    • 好的,感谢您解决这个问题。但我仍然认为重要的是要注意相同的字节码不一定会产生相同的性能。在这种情况下,LOAD_ATTR 是一条字节码指令,但它可能会根据情况而更快或更慢——就像同一组 cpu 指令可能会更快或更慢,具体取决于是否存在缓存未命中。
    【解决方案3】:

    Instance variables vs. class variables in Python

    另请参阅此处相同的问题,其中我发布了基于@Edward Loper 测试的更广泛的测试,包括模块变量以及比类变量更快的测试。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2021-07-23
      • 2020-12-11
      • 2011-03-26
      • 1970-01-01
      • 2022-08-11
      • 2011-03-05
      • 1970-01-01
      相关资源
      最近更新 更多