Jupyter(Python)中無法使用Cache原理分析

分類:IT技術 時間:2017-09-25

前言

最近需要在Jupyter中寫一個類庫,其中有一個文件實現從數據庫中讀取空間數據並加載為Feature對象,Feature對象是cartopy封裝的geometry列表,能夠方便的用於作圖等。因為有很多數據是經常用到的,所以就寫了很多常量將數據事先讀好供用戶直接調用,這樣造成的一個問題是每次加載該頁面的時候很慢,於是我就考慮可以寫個Cache來緩存這些數據,這在其他情況下是再正常不過的需求,然而我卻在這裏折騰半天,踏了坑,坑裏還有水,再也沒有出來。。。在這裏我簡單分析一下失敗的原因,如果有人有能解決的方案或者我有什麽說的不對的地方,歡迎批評指導!

折騰過程

首先我考慮這個應該是寫個Cache類,其中加入一個字典,於是找到了這樣一塊代碼,初步看了一下代碼沒有問題,於是Copy上:

#coding=utf-8
from time import time
class Cache:
    '''簡單的緩存系統'''
    def __init__(self):
        '''初始化'''
        self.mem = {}
        self.time = {}

    def set(self, key, data, age=-1):
        '''保存鍵為key的值,時間位age'''
        self.mem[key] = data
        if age == -1:
            self.time[key] = -1
        else:
            self.time[key] = time() + age
        return True

    def get(self,key):
        '''獲取鍵key對應的值'''
        if key in self.mem.keys():
            if self.time[key] == -1 or self.time[key] > time():
                return self.mem[key]
            else:
                self.delete(key)
                return None
        else:
            return None

    def delete(self,key):
        '''刪除鍵為key的條目'''
        del self.mem[key]
        del self.time[key]
        return True

    def clear(self):
        '''清空所有緩存'''
        self.mem.clear()
        self.time.clear()

很清晰的一段代碼,並且加入了緩存時間,應當能滿足我的要求的,在此頁面定義了一個變量,創建一個FEATURE_CACHE對象如下:

FEATURE_CACHE = Cache()

這樣我在需要緩存的頁面只要先判斷是否在緩存內,是則直接讀取,否則使用原來的邏輯讀取數據庫並存入緩存即可,改造如下:

if FEATURE_CACHE.get(ds_id) != None:
    return FEATURE_CACHE.get(ds_id)
else:
    ...
    geo_feature = ...
    FEATURE_CACHE.set(ds_id, geo_feature)
    return geo_feature

邏輯上清晰易懂,然後嘗試調用。新建一個jupyter頁面,多次調用,很好,只有第一次比較慢,再次調用就非常快,本以為這就解決了問題,我也是靈光一閃,既然我是全局緩存那就再開一個頁面試試吧,於是又新建了一個jupyter頁面,大跌眼鏡的事情出現了,居然也是第一次調用非常慢,這是什麽邏輯,為什麽這裏面沒有緩存。然後經歷了無數次加輸出信息調試、重啟kernel調試、staticmethod方法、單例等均達不到效果,單例的代碼如下:

class Cache:
    __instance = None  
    
    __lock = threading.Lock()   # used to synchronize code  
    
    mem = {}
    time = {}
 
    def __init__(self):  
        "disable the __init__ method"  
        
    '''簡單的緩存系統'''

    def set(self, key, data, age=-1):
        '''保存鍵為key的值,時間位age'''
        self.mem[key] = data
        if age == -1:
            self.time[key] = -1
        else:
            self.time[key] = time() + age
        return True

    def get(self,key):
        '''獲取鍵key對應的值'''
        if key in self.mem.keys():
            if self.time[key] == -1 or self.time[key] > time():
                return self.mem[key]
            else:
                self.delete(key)
                return None
        else:
            return None

    def delete(self,key):
        '''刪除鍵為key的條目'''
        del self.mem[key]
        del self.time[key]
        return True

    def clear(self):
        '''清空所有緩存'''
        self.mem.clear()
        self.time.clear()
        
    @staticmethod  
    def getInstance():  
        if not Cache.__instance:  
            Cache.__lock.acquire()  
            if not Cache.__instance:  
                Cache.__instance = object.__new__(Cache)  
                object.__init__(Cache.__instance)  
            Cache.__lock.release()  
        return Cache.__instance

這樣就是不再創建Cache的實例,而是直接調用Cache.getInstance()。可想而知這樣也是不行的。於是折騰一番後我得出這麽一個結果。

結果與原理

當我們在一個jupyter頁面中調用某個python庫的時候,只要在這個jupyter頁面中不重新啟動內核,則已經加載過的模塊會自動緩存(是python的緩存,並非我寫的緩存),重啟內核相當於打開一個新的jupyter頁面,並且在重新打開一個jupyter頁面時,即使其他jupyter頁面已經加載過了相應的調用,也不會緩存,會再次去執行程序,這樣我寫的Cache類就沒有用了。所以結論就是在jupyter中我的Cahce緩存類加不加效果是一樣的。那麽原理是什麽呢?

其實很簡單,只是我剛開始對python的運行機理和生命周期等不太熟悉,才走了這個彎路,折騰一番大概明白了。首先普通的python程序使用python xx.py啟動的時候這樣寫Cahce肯定是可行的,能夠實現全局緩存,因為這是在一個application內部,加載過的python文件會編譯成pyc,再次加載的時候會直接調用此pyc而不會重新執行,並且整體是共享內存的。而在jupyter中每一個jupyter頁面都相當於啟動了一個application,所以他們相互之間是隔離的,即無法共享pyc文件,也無法共享內存,於是重新打開一個jupyter頁面就是一個新的Cache,這樣寫不寫Cache得到的結果是一致的。

總結

當然可以考慮采用文件緩存的方式,即首次讀取的時候將數據庫內容加載到本地文件,再次調用的時候讀取文件,然而並沒有嘗試這樣會快多少,並且本身訪問量就不大,數據庫是完全能抗住的,於是不知道這樣的緩存有多少意義。當然也可以使用redis、memcache等緩存件,但是這樣就整大發了,沒必要使用jupyter了吧。以上是我對此問題的個人見解,歡迎大家提出寶貴意見,不甚感激!


Tags: amp 39 self time 數據 一個

文章來源:


ads
ads

相關文章
ads

相關文章

ad