簡體   English   中英

如何優化代碼以更快地處理?

[英]How can I optimize my code to process faster?

我編寫的代碼存在一些性能問題。 該代碼的目的是比較2個csv文件(一個中有900k行,另一個中有50k〜80k行)。

目的是比較csv1和csv2,並將匹配的數據寫入第三個csv。

我的數據如下所示:

CSV1:

address,name,order_no
add1,John,d009
add2,Smith,d019
add3,Mary,d890
.....(900k more rows)

CSV2:

address,hub_id
add3,x345
add4,x310
add1,a109
....(50k ~ 80k more rows)

預期輸出:

CSV3:

order_no,hub_id
d890,x345
d009,a109
.....(etc)

我現在正在處理的代碼(盡管很簡單)實際上有效。 但是,比較和編寫的整個過程需要很長時間才能完成。

任何指針將不勝感激。 自從我剛開始學習以來,我可能已經忽略了一些在比較大型數據時可以使用的python函數。

import csv
import time
start_time = time.time()

with open('csv1.csv', newline='', encoding='Latin-1') as masterfile:
    reader = csv.DictReader(masterfile)
    for row in reader:
        with open('csv2.csv', newline='', encoding='Latin-1') as list1:
            reader2 = csv.DictReader(list1)
            for row2 in reader2:
                if row2['address'] == row['address']:
                     with open('csv3.csv', 'a') as corder:
                     print(row2['wip'] + ', ' + row['lat'] + ', ' + row['long'], file=corder)

print("--- %s seconds ---" % (time.time() - start_time))

您的算法當前正在做什么:

  1. 加載一行大文件。
  2. 打開較小的文件。
  3. 從磁盤對小文件進行線性搜索
  4. 打開輸出文件並將其寫入。
  5. 沖洗並重復。

所有這些步驟完成了900k次以上。

步驟#2(打開較小的文件)應該只執行一次。 打開文件並從磁盤加載它是一項昂貴的操作。 僅僅從一開始就加載一次並在內存中執行線性搜索(第3步),您就會看到很大的改進。

步驟#4同樣:打開輸出文件只能執行一次。 每次關閉文件時,系統都會將文件刷新到磁盤。 這是非常浪費的步驟。 如果使文件保持打開狀態,則將數據緩沖輸出,直到有足夠的空間將完整的塊寫入磁盤為止,這是完成此操作的更快方法。

通過使用正確的數據結構,可以對步驟3進行很多優化。 哈希表是日常生活中最常見的概率用途之一。 它們無處不在,因為它們使查找成為恆定時間的操作(與線性搜索不同,線性搜索隨輸入大小而線性縮放)。 哈希表在Python的dict類中實現。 通過創建一個以address為鍵的dict ,可以將處理時間減少到900k + 80k的倍數,而不是900k * 80k的倍數。 查找算法復雜度以了解更多信息。 我特別推薦Steve Skiena的“算法設計手冊”。

最后一步是在每個文件中找到地址的交集。 有一些可用的選項。 您可以將兩個文件都轉換為dict並執行一set類似鍵的交集,也可以將一個文件加載到dict ,然后逐行對其進行測試。 我強烈建議您使用后者,將較小的文件作為您加載到dict 從算法角度來看,元素減少10倍意味着您可以降低哈希沖突的可能性。 這也是最便宜的方法,因為它會在較大文件的無關行上快速失敗,而不會對其進行記錄。 從實際的角度來看,如果我懷疑它有多個具有相同地址的行,您甚至可能沒有選擇將較大的文件直接轉換為字典。

這是我一直在談論的實現:

with open('csv2.csv', newline='', encoding='Latin-1') as lookupfile:
    lookup = dict(csv.reader(lookupfile))

with open('csv1.csv', newline='', encoding='Latin-1') as masterfile, open('csv3.csv', 'w') as corder:
    reader = csv.reader(masterfile)
    corder.write('order_no,hub_id\n')
    for address, name, order_no in reader:
        hub_id = lookup.get(address)
        if hub_id is not None:
            corder.write(f'{order_no},{hub_id}\n')

如果任何行的長度不完全是兩個元素,則表達式dict(csv.reader(lookupfile))將失敗。 例如,空白行將使其崩潰。 這是因為dict的構造函數期望兩個元素的序列可迭代來初始化鍵值映射。

作為次要的優化,我沒有使用csv.DictReader ,因為這需要對每一行進行額外的處理。 此外,我已經從輸出中完全刪除了csv模塊,因為您可以更快地完成工作,而無需添加包裝器。 如果你的文件一樣整潔格式化為你展示,你可以從身邊分裂他們得到一個小的性能提升,你自己,而不是使用csv

很長一段時間是因為:

  • 復雜度為O(n**2) 永遠不會對像這樣的大數據執行線性搜索
  • 常量文件讀/寫會增加費用

通過創建2個字典, 地址作為鍵 ,整行作為值,可以做得更好。

然后執行鍵的交點,並寫入結果,並根據需要在每個字典中選擇數據。

以下代碼已針對您的示例數據進行了測試

import csv

with open('csv1.csv', newline='', encoding='Latin-1') as f:
    reader = csv.DictReader(f)
    master_dict = {row["address"]:row for row in reader}
with open('csv2.csv', newline='', encoding='Latin-1') as f:
    reader = csv.DictReader(f)
    secondary_dict = {row["address"]:row for row in reader}

# key intersection

common_keys = set(master_dict) & set(secondary_dict)

with open("result.csv", "w", newline='', encoding='Latin-1') as f:
    writer = csv.writer(f)
    writer.writerow(['order_no',"hub_id"])
    writer.writerows([master_dict[x]['order_no'],secondary_dict[x]["hub_id"]] for x in common_keys)

結果是:

order_no,hub_id
d009,a109
d890,x345

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM