如何避免Python模块全局初始化的延迟?

时间:2012-02-21 16:48:54

标签: google-app-engine optimization python lazy-initialization

我正在尝试优化用python编写的Web应用程序的一般加载时间。我的应用程序使用了很多模块,其中一些模块实际上可能需要或者可能不需要。

由于页面加载时间是最终用户感知网站质量的重要因素,我试图减少加载可能不必要的模块的影响 - 特别是,尝试减少初始化所需的时间(和内存)可能根本不需要的全局变量。

简单地说,我的目标是:

  1. 尽可能减少模块初始化时间(不是CPU使用率)。
  2. 减少不需要的全局变量占用的内存
  3. 为了说明,这是一个简单的模块示例:

    COMMON = set(('alice', 'has', 'cat', 'with', 'blue', 'eyes'))
    

    COMMON构建集合需要时间 - 如果不使用COMMON,则会浪费加载时间和内存。
    显然,对于单个模块/全局,成本可以忽略不计,但是如果你有<100个模块包含100个变量会怎么样?

    使这种方法更快的一种方法是延迟初始化,如下所示:

    __cache_common = None
    def getCommon():
        global __cache_common
        # not use before
        if __cache_common is None:
            __cache_common = set(('alice', 'has', 'cat', 'with', 'blue', 'eyes'))
        # get cached value
        return __cache_common
    

    它节省了加载时间和内存,牺牲了一些CPU。

    我尝试了一些其他技术(见下文),其中两个比上面的简单缓存快一点。

    我可以使用另一种技术来进一步减少可能不会在给定请求上使用的模块和全局变量的加载时间吗?


    到目前为止我尝试过的方法,需要Python 2.6 +:

    from timeit import Timer
    
    __repeat = 1000000
    __cache = None
    
    def getCache():
        return __cache
    
    def getCacheTest():
        for i in range(__repeat):
            getCache()
    
    def getLocal():
        return set(('alice', 'has', 'cat', 'with', 'blue', 'eyes'))
    
    def getLocalTest():
        for i in range(__repeat):
            getLocal()
    
    def getLazyIf():
        global __cache
        if __cache is None:
            __cache = getLocal()
        return __cache
    
    def getLazyIfTest():
        for i in range(__repeat):
            getLazyIf()
    
    def __realLazy():
        return __cache
    
    def getLazyDynamic():
        global __cache, getLazyDynamic
        __cache = getLocal()
        getLazyDynamic = __realLazy
        return __cache
    
    def getLazyDynamicTest():
        for i in range(__repeat):
            getLazyDynamic()
    
    def getLazyDynamic2():
        global __cache, getLazyDynamic2
        __cache = getLocal()
        def __realLazy2():
            return __cache
        getLazyDynamic2 = __realLazy2
        return __cache
    
    def getLazyDynamic2Test():
        for i in range(__repeat):
            getLazyDynamic2()
    
    print sum(Timer(getCacheTest).repeat(3, 1)), getCacheTest, 'raw access'
    print sum(Timer(getLocalTest).repeat(3, 1)), getLocalTest, 'repeat'
    print sum(Timer(getLazyIfTest).repeat(3, 1)), getLazyIfTest, 'conditional'
    print sum(Timer(getLazyDynamicTest).repeat(3, 1)), getLazyDynamicTest, 'hook'
    print sum(Timer(getLazyDynamic2Test).repeat(3, 1)), getLazyDynamic2Test, 'scope hook'
    

    使用Python 2.7,我得到了这些时间(最好是没有范围的钩子):

    1.01902420559 <function getCacheTest at 0x012AE170> raw access
    5.40701374057 <function getLocalTest at 0x012AE1F0> repeat
    1.39493902158 <function getLazyIfTest at 0x012AE270> conditional
    1.06692051643 <function getLazyDynamicTest at 0x012AE330> hook
    1.15909591862 <function getLazyDynamic2Test at 0x012AE3B0> scope hook
    

1 个答案:

答案 0 :(得分:1)

import语句执行模块,所以你不应该改变它的语义。

你如何将import语句隐藏在需要它们的函数或方法中?这样他们只会在需要时才会发生,而不是在应用程序启动时发生。

同样的全局变量 - 将它们变成类静态或其他东西。无论如何,拥有大量的全局变量都是糟糕的风格。

但为什么这个问题呢?你是否真的包含了这么多模块,只是简单地发现它们会减慢速度,或者是一些包含在内的包进行了大量昂贵的初始化(例如,打开连接)?我的钱在第二个。如果您编写了负责减速的模块,请查看将初始化包装到适当的构造函数中。