2014-06-12 28 views
1

我有一個awk腳本,我通常使用外部變量$ a並行運行。在一個範圍內使用索引鍵的awk

awk -v a=$a '$4>a-5 && $4<a+5 {print $10,$4}' INFILE 

當然,這將運行得更快使用數組所以我想這樣的事情來得到它做同樣的事情($ 2 listfile中是爲$ 4搜索值INFILE

awk 'FNR==NR{a[$2]=($2-5);next}$4 in a{if ($4>a[$4] && $4<a[$4]+10 {print} LISTFILE INFILE 

這當然不起作用,因爲awk掃描直到到達key,然後開始測試if語句,因此只發現了下游範圍。不幸的是,這不是一個連續的列表,因此經常沒有$ 2-5的值,否則我會用它作爲陣列的鑰匙。

顯然我知道如何使用awk和bash的組合來做到這一點,但我想知道是否有這種awk唯一的解決方案。

回答

1

我的第一個答案解決問的實際問題,並修復了awk腳本。但也許我錯過了這一點。如果你想要速度,並且不介意更多地使用你的多核處理器,你可以使用GNU parallel。下面是一個將同時啓動4個就業機會的實現:

awk_cmd='$4 > var - 5 && $4 < var + 5 { print $10, $4 }' 

parallel -j 4 "awk -v var={} '$awk_cmd' INFILE" :::: LISTFILE 

正如你所看到的,這將讀取INFILE最多同時四倍。這個答案在調整作業數量之後,應該提供與您在shell中描述的並行實現非常相似的性能。因此,您可能希望將您的LISTFILE分成更小的塊,並將awk_cmd設置爲我在先前答案中發佈的命令。可能有一個最佳的方式來處理您的輸入,但這主要取決於INFILE的大小和LISTFILE中的元素數量。 HTH。



測試:

創建LISTFILE

paste - - < <(seq 16) > LISTFILE 

創建INFILE

awk 'BEGIN { for (i=1; i<=9999999; i++) { print i, i, i, int(i * rand()), i, i, i, i, i, i } }' > INFILE 

結果:

測試1:

time awk 'FNR==NR { a[$2]; next } { for (i in a) { if ($4 > i - 5 && $4 < i + 5) { print $10, $4 } } }' LISTFILE INFILE >/dev/null 

real 0m45.198s 
user 0m45.090s 
sys  0m0.160s 

測試2:

time for i in $(seq 1 2 16); do awk -v var="$i" '$4 > var - 5 && $4 < var + 5 { print $10, $4 }' INFILE; done >/dev/null 

real 0m55.335s 
user 0m54.433s 
sys  0m0.953s 

TEST3:

awk_cmd='$4 > var - 5 && $4 < var + 5 { print $10, $4 }' 

time parallel --colsep "\t" -j 4 "awk -v var={2} '$awk_cmd' INFILE" :::: LISTFILE >/dev/null 

real 0m28.190s 
user 1m42.750s 
sys  0m1.757s 


我對THIS答案回覆:

1:

The awk1 script does not run much faster than the awk script.

有15%的時間節約在我看來是非常顯著。

I suspect because it scans the LISTFILE for every line in the INFILE.

是的,本質上。 awk1腳本只通過INFILE循環一次。

So number of lines scanned using the array with for (i in a) = NR(INFILE)*NR(LISTFILE).

關閉。但不要忘記,通過使用數組,我們實際上刪除了LISTFILE中的任何重複值。因此

This is the same number of lines you would scan by going through the INFILE repeatedly with the bash script.

這種說法是唯一真正當LISTFILE不包含重複。即使LISTFILE從不包含任何嘟嘟聲,最好避免不得不多次讀取單個文件。

2:

Running awk and awk2 in a different folder produced different results (where my 4 min result came from versus the ~2 min result here, not sure what the difference is because they are next door in the parent directory.

哪四個分鐘出結果?在對這類事情進行基準測試時,應該停止將輸出寫入磁盤。如果您的機器在運行測試時有一些後臺進程正在進行,那麼您最終只會以磁盤的寫入速度對結果進行偏置。改爲使用/dev/null

3:

Awk and Awk2 are essentially the same. Any idea why awk2 runs faster?

如果刪除管sortuniq你會得到的時間差就是一個更好的主意。你會發現做$4 > i - 5 && $4 < i + 5與做$4 < i + 5 && $4 > i - 5完全不同。如果awkout.txtawkout.txt相同,則需要花費時間處理重複項。

4:

你張貼在這裏的第二個命令可以避免這個測試:$4 > i - 5 && $4 < i + 5。我不認爲僅憑這一點就能保證運行時間提高90%。有什麼東西聞到錯誤。您是否願意將您的測試重新寫入/dev/null併發布LISTFILEINFILE的內容?如果這兩個文件是保密的,您能否提供一些內容數量與原件相同的示例文件?

其他的想法:

對我來說,它看起來像東西沿着這些路線也將工作:

awk 'FNR==NR { for (i=$2-4;i<$2+5;i++) a[i]; next } $4 in a { b[$10,$4] } END { print length b }' LISTFILE INFILE 
+0

我已經發布了我的結果並在下面回覆了答案。 – jeffpkamp

+0

@jeffpkamp:我已在上述問題中添加了回覆。 HTH。 – Steve

+0

啊,那最後的功能就是我從一開始就在尋找的東西!我調整了它的結尾「{b [$ 10,$ 4] ++} END {for(i in b)print i,b [i]}」This subs for my sort | uniq -c功能步驟仍然只需要12秒鐘。我認爲運行時間增加90%是因爲只讀取一次文件(讀取8 + 2000萬行),而不是循環讀取每行文件(讀取8 * 2000萬行),因此運行速度提高了8倍。謝謝您的幫助。 – jeffpkamp

1

看起來您只需要將LISTFILE的按鍵添加到數組中,然後,在處理INFILE(逐行)時,使用'if'語句測試數組中的每個鍵。您可以使用下面的結構或類似的做到這一點:

for (i in a) { print i, a[i] } 

下面是一些未經測試的代碼,可以幫助你開始。請注意,我怎麼還沒有分配任何值,以我的鑰匙:

awk 'FNR==NR { a[$2]; next } { for (i in a) { if ($4 > i - 5 && $4 < i + 5) { print $10, $4 } } }' LISTFILE INFILE 
+0

這似乎工作,雖然它顯着減慢了awk腳本從22s到7m 30s。我猜想和bash腳本並行的做法可能是這樣做的,除非你可以想辦法加快這個腳本的速度。 – jeffpkamp

+0

我不相信那段時間的比較是公平的。它看起來像你正在比較你的第一個腳本,它搜索一個單一的變量,一個腳本,採取MULTIPLE變量。更好的比較將涉及對'LISTFILE'中的每個變量反覆運行第一個腳本,然後對牆壁時間求和。我可以向你保證,這個答案中發佈的代碼會更快,因爲你只需要閱讀一次'INFILE'。如果你想要速度,並行處理'INFILE'。我在下面添加了另一個答案。 – Steve

+0

奇怪的是,對於直接比較來說,它仍然比較慢,對於8個變量,當awk腳本花了4:23時,bash循環花了2:10。很顯然,awk腳本必須通過INFILE循環多次。如果我將LISTFILE中的$ 2擴展列表包含在-5到+5之間,那隻需要12秒,這正是我所希望的。我想我可以製作一個awk腳本來擴展LISTFILE,然後將它作爲數組運行,以獲得更快的結果。 – jeffpkamp

0

史蒂夫回答上面是正確答案的問題。以下是處理問題的數組和非數組方式的比較。

我創建了一個測試程序來查看兩個不同的場景以及每個場景的結果。該測試程序代碼是在這裏:

echo time for bash 

time for line in `awk '{print $2}' $1` ; do awk -v a=$line '$4>a-5&&$4<a+5{print $4,$10}' $2 ; done | sort | uniq -c > bashout.txt 

echo time for awk 
time awk 'FNR==NR{a[$2]; next}{for (i in a) {if ($4>i-5&&$4<i+5) print $10,$4}}' $1 $2 |sort | uniq -c > awkout.txt 

echo time for awk2 

time awk 'FNR==NR{a[$2]; next}{for (i in a) {if ($4<i+5&&$4>i-5) print $10,$4}}' $1 $2 |sort | uniq -c > awk2out.txt 

echo time for awk3 
time awk '{a=$2;b=$1;for (i=a-4;i<a+5;i++) print b,i}' $1 > LIST2;time awk 'FNR==NR{a[$2];next}$4 in a{print $10,$4}' LIST2 $2 | sort | uniq -c > awk3out.txt 

這裏是輸出:

time for bash 
real 2m22.394s 
user 2m15.938s 
sys  0m6.409s 

time for awk 
real 2m1.719s 
user 2m0.919s 
sys  0m0.782s 

time for awk2 
real 1m49.146s 
user 1m47.607s 
sys  0m1.524s 

time for awk3 
real 0m0.006s 
user 0m0.000s 
sys  0m0.001s 

real 0m12.788s 
user 0m12.096s 
sys  0m0.695s 

4的意見/問題

  1. 的awk1腳本不跑的比awk腳本快得多。我懷疑是因爲它會掃描INFILE中每一行的LISTFILE。因此,使用數組掃描行數爲(i,a)= NR(INFILE)* NR(LISTFILE)。這與通過使用bash腳本重複執行INFILE來掃描的行數相同。

  2. 在不同的文件夾中運行AWK和awk2產生不同的結果(在我的4分鐘結果從兌〜2分鐘,結果來到了這裏,不知道有什麼區別,因爲他們是在父目錄隔壁。

  3. awk和awk2在本質上是一樣的。任何想法,爲什麼awk2跑得快?

  4. 從ListFile中製作的擴展列表2,並將它作爲數組使得程序運行顯著更快,增加了存儲成本考慮到我看到的列表(只有200-300多長)似乎是要走的路,即使這樣做我平行。