
哪個 shell 命令是解析數百萬行文字的最快方法。目前我在腳本中使用 GREP,但需要幾個小時才能完成。
輸入範例:
May 1 2014 00:00:00 Allow
May 1 2014 00:00:00 Allow
May 1 2014 01:00:00 Deny
May 1 2014 01:00:00 Deny
May 1 2014 02:00:00 Allow
May 1 2014 02:00:00 Deny
範例輸出:
(其中第一行中的“2”是“grep -c“允許””,“0”是“grep -c“拒絕””)
May 1 2014 00:00:00,2,0
May 1 2014 01:00:00,0,2
May 1 2014 02:00:00,1,1
答案1
遠離正規表示式。它們很慢(在每種語言中),並且遠遠超出了我們在這裡所需的簡單子字串比較的範圍。
- 取0:20的子字串作為第一個key
- 將 21:22(單一字元)的子字串作為第二個鍵的布林結果
- 這個組合的值應該是一個整數,每次看到它時只需遞增該整數。
我在 Python 中實現了這個想法:
data = {}
with open("file", "r") as f:
for line in f:
key = line[0:20]
allowed = int(line[21:22] != "A")
if not key in data:
data[key] = [0,0]
data[key][allowed] += 1
for key, val in data.items():
print('%s,%d,%d' % (key, val[0], val[1]))
不知道它的表現如何,但嘗試一下。如果速度較慢,請將其轉換為 C++(有點像 PITA,這就是我使用 Python 的原因!),這應該會破壞您的資料。這並不難編程,但卻是最佳速度所需要的。
一點重構,很難移植,除非你相當於defaultdict
:
from collections import defaultdict
data = defaultdict(lambda: [0,0])
with open("file", "r") as f:
for line in f:
key = line[0:20]
allowed = int(line[21:22] != "A")
data[key][allowed] += 1
for key, val in data.items():
print('%s,%d,%d' % (key, val[0], val[1]))
以及 Steeldriver 和我的想法的混合的 Python 實作。這可能是您將獲得的最高效的記憶體效率,並且它使用子字串比較而不是正則表達式提取,因此應該很簡單。但它需要排序輸入。
last = ""
score = [0,0]
with open("file", "r") as f:
for line in f:
key = line[0:20]
allowed = int(line[21:22] != "A")
if key and key != last:
print '%s,%d,%d' % (last, score[0], score[1])
score = [0,0]
last = key
score[allowed] += 1
print '%s,%d,%d' % (last, score[0], score[1])
標竿管理
為了對其中的一些內容進行測試(出於我自己的好奇心,也出於其他原因),我決定對涵蓋 2400 個單獨日期的 2,400,000 條記錄文件進行一些基準測試。
我使用以下 Python 腳本產生一個具有隨機允許/拒絕結尾的大檔案:
import itertools, datetime, random
CHOICES = ['Allow', 'Deny']
with open("file", "w") as f:
for _ in itertools.repeat(None, 2400):
epoch = random.randint(1, 1404226041)
d = datetime.datetime.fromtimestamp(epoch)
print d
dstr = d.strftime('%b %d %Y %X')
for _ in itertools.repeat(None, 1000):
f.write('%s %s\n' % (dstr, CHOICES[random.randint(0,1)]))
這比 Bash 等價物快了大約一千倍(請參閱修訂日誌),並為我們提供了多種日誌檔案可供使用。它是未排序的,因此需要整理輸入的兩個基準(下面的 3 和 4)正在使用單獨的排序版本(sort file > file-sorted
需要 0m4.253s 才能完成)。
- 我的第一個:0m1.544s
- 我的重構
defaultdict
:0m1.413s - Steeldriver 的
awk
:0m5.168s + 0m4.253s 排序 - 我的 Python 重新實作#3:0m1.489s + 0m4.253s 排序
我用 240 萬個不同的日期重複了這一生成(應該將我的前兩個日期推向極限)。這種排序花費了 0m6.999s。我還添加了pypy
Python 版本的計時。
- 0m11.589s(0m7.080s 中
pypy
) - 0m11.307s(0m7.087s 中
pypy
) - 0米8.652秒+0米6.999秒
- 0m6.586s + 0m6.999s(0m1.566s 中
pypy
)
分析與結果
- 在小鍵組上,1 和 2 都表現最佳。
pypy
有助於更大的鍵集。 - 4 比 3 更快
awk
主要是因為我們沒有進行正規化 - 4 速度最快且佔用空間最小,但前提是資料已預先排序
- 如果我們有混亂的數據,2 是最快的
- 外部排序是真的很慢。
答案2
很難猜測它是否會更有效,因為您還沒有發布有關您現在正在做的事情的足夠詳細信息,但是如果資料按時間戳順序排序我會建議一個類似的演算法
- 累積
Allow
和Deny
計數,直到時間戳發生變化(或到達輸入末端) - 列印結果並(如果我們還沒有到達輸入末尾)重置計數
在 awk 中,你可以這樣做
awk '
FNR==1 {t = $1FS$2FS$3FS$4}
$1FS$2FS$3FS$4 == t {
a += $5=="Allow" ? 1 : 0
d += $5=="Deny" ? 1 : 0
}
$1FS$2FS$3FS$4 != t {
printf "%s,%d,%d\n",t,a,d
a = $5=="Allow" ? 1 : 0
d = $5=="Deny" ? 1 : 0
t = $1FS$2FS$3FS$4
}
END {printf "%s,%d,%d\n",t,a,d}
' input