Try   HackMD

pandas to_datetime 的快取

tags: Tech Python

Jade Lin
May 2022

前言

要將字串轉換成 datetime,相比同功能的函式,pandas.to_datetime的效能應該是滿好的。我是參考這裡

如果查 to_datetime 效能問題,可能會得到一些回答。但是如果該做的都做了,卻比別人測出來的慢超多,或甚至就像我遇到的案例,明明什麼都沒改,偏偏就是有的處理會特別慢呢?這可能和資料特性有關…

TL;TD
Image Not Showing Possible Reasons
  • The image file may be corrupted
  • The server hosting the image is unavailable
  • The image path is incorrect
  • The image format is not supported
Learn More →

部門的專案裡,有一個小程式,每次會從資料庫,拉取資料做處理。隨著資料量越來越大,從資料庫拉取資料的時間變得太長,以致於該程式無法在目標時間內消化完資料。於是DBA提供另一個語法,讓我測試看看能提升多少拉取資料的速度。原本的語法必須經過某個排序,而新的語法不經過排序(若排序也有額外的效能問題),但最後取得的資料是一樣的,只是順序不同

於是我用新的語法,測試從資料庫拉取大量資料,觀察到時間上有大幅度的改善,就很開心的把程式正式更改成這種新語法。

BUT!人生就是有這樣的BUT!程式實際運作後,卻發現程式的總處理時間變得更長了?!我可以很確定我沒有改到其它部分,那……只好進去挖到底是哪裡變慢了……

在這個小程式處理這些資料的過程中,需要將部份欄位的值做格式轉換。其中有些是日期字串,要轉成datetime,尤其面對的資料,不同欄位也會含有不同格式的日期字串。最後選擇使用to_datetime,實際運作起來,效能的確也滿不錯。

但現在單純只是資料順序改變,在轉換日期的效能變得極差,變差時的執行時間甚至超過十倍 =.=

實在太奇怪了,於是 trace code 到 pandas 裡,才發現原來to_datetime是否快取的差異……

to_datetime 做快取與否

進入本篇的主題:pandas.to_datetime

如果仔細看官方文件,to_datetime 有一個引數藏在最後面:cache

cache: bool, default True
If True, use a cache of unique, converted dates to apply the datetime conversion. May produce significant speed-up when parsing duplicate date strings, especially ones with timezone offsets. The cache is only used when there are at least 50 values. The presence of out-of-bounds values will render the cache unusable and may slow down parsing.

這裡提到,對有重複的日期字串的資料,開啟快取能有很大幅度的加速。另外,在50筆資料以下不會啟用快取。

但這段文字只說到這,單看這個也不會覺得有什麼關係,畢竟cache預設值是True,那應該表示只要大於50筆資料,就是要有快取吧?呵呵,才不是呢 =.=

仔細看 pandas datetime source code
會看到引數cache會傳入_maybe_cache,而這裡的引數cache只是初步看要不要試著做快取。所以當cache == False就肯定不會做快取,可能用於幾乎沒有重複的資料上。
若直接用預設值cache == True呼叫,則決定是否做快取的邏輯,實際上會在should_cache函式裡。此時會看到這段文字:

By default for a sequence of less than 50 items in size, we don't do caching; for the number of elements less than 5000, we take ten percent of all elements to check for a uniqueness share; if the sequence size is more than 5000, then we check only the first 500 elements.
All constants were chosen empirically by.

個人解讀:

  • 少於50筆資料,不會做快取。這個和一開始看到的說明一致。
  • 若少於5000筆資料,會取10%的資料,檢查資料差異性有多大。差異性太大則不做快取。
  • 若大於5000筆資料,則檢查前500筆資料,檢查資料差異性有多大。差異性太大則不做快取。
  • 上面這些常數(50、500、10%等等這些數字),是憑經驗決定的。

所以資料內容是會影響是否啟用快取的。那怎麼"檢查資料差異性有多大"?
這裡我們以5000筆資料來看:

  • 取出前500筆 check_count==500
  • 取出不重覆的資料清單 unique_elements
  • 預設的比例unique_share==0.7
  • 實際公式是
    ​​​​do_caching = len(unique_elements) <= check_count * unique_share
    

也就是說,一般以預設值下去運作的話,超過5000筆資料,它會取前500筆資料,計算不重複的值的數量,若超過 500*0.7 = 350的數量,即認定資料差異性過大,它就不會做快取;反之,若是<=350 的話,表示資料差異性不大,它就會執行快取的機制,來加速日期的轉換。

至於當資料在50~5000筆之間時,也是取10%的值來判斷要不要做快取,但是畢竟數量不大,有沒有差距可能也不會太明顯。

到這裡就可以理解我遇到的案例,只是資料的順序改變了,卻造成巨大的效能差異的原因。

案例實測結果

  • Python: 3.9.x
  • pandas: 1.3.2
  • date str format: %Y-%m-%dT%H:%M:%S.%f%z

轉換單欄資料,從字串轉成datetime

data counts spent time with cache
13,995 0.051s YES
13,995 0.581s NO
848,990 3.626s YES
848,990 41.954s NO

可以看出,以這程式面對到的資料特性來說,基於同樣的機器環境、處理同樣的資料,使用快取與否,造成極大的效能差異。

Conclusion

注意資料順序可能會影響到處理的效能。根據面對到的資料特性,可以注意一下是否有遇到相似的狀況,再視情況看怎麼調整出最好的使用方式。

我遇到的案例,後來是在轉換日期前,先將資料做排序。因為即使排序多花費了幾秒,對於因此省下來的轉換時間,還是遠遠值得啊~~

還有,註解真的是很棒的東西。不然在 trace code 時,應該會花更多的時間在猜作者的想法吧!