Bash 针对大型数据集优化AWK脚本

Bash 针对大型数据集优化AWK脚本,bash,awk,Bash,Awk,对于以下输入数据 Chr C rsid D A1 A2 ID1_AA ID1_AB ID1_BB ID2_AA ID2_AB ID2_BB ID3_AA ID3_AB ID3_BB ID4_AA ID4_AB ID4_BB ID5_AA ID5_AB ID5_BB 10 p rsid1 q A G 0.00 0.85 0.15 0.70 0.10 0.20 0.40 0.50 0.

对于以下输入数据

Chr C   rsid    D   A1  A2  ID1_AA  ID1_AB  ID1_BB  ID2_AA  ID2_AB  ID2_BB  ID3_AA  ID3_AB  ID3_BB  ID4_AA  ID4_AB  ID4_BB  ID5_AA  ID5_AB  ID5_BB
10  p   rsid1   q   A   G   0.00    0.85    0.15    0.70    0.10    0.20    0.40    0.50    0.10    0.30    0.30    0.40    0.10    0.20    0.80
10  p   rsid2   q   C   T   0.90    0.10    0.00    0.80    0.10    0.10    0.70    0.10    0.20    0.30    0.40    0.30    0.30    0.20    0.40
10  p   rsid3   q   A   G   0.40    0.50    0.10    0.80    0.20    0.00    0.20    0.30    0.50    0.50    0.30    0.20    0.20    0.30    0.40
我需要生成以下输出数据

rsid        ID1         ID2         ID3         ID4         ID5
rsid1      2.15        1.50        1.70        2.10        2.90
rsid2      1.10        1.30        1.50        2.00        1.90
rsid3      1.70        1.20        2.30        1.70        2.00
该表显示了3列_AA、_AB和_BB的总和,即乘以每个ID ID1、ID2、ID3等的常数因子1、2、3

我编写了以下AWK脚本来建立任务,它工作得非常好

请注意:我是AWK的初学者


后来我被告知,输入数据可以达到6000万行30万列,这意味着输出数据将达到60Mx100K。如果我没记错的话,AWK一次读取一行&因此,在一瞬间内存中将保存300K列的数据。这是个问题吗?在这种情况下,如何改进代码?

虽然这两种方法都有优点/缺点,而且它们都可以处理任意数量的行/列,因为它们一次只在内存中存储一行,我会使用这种方法,而不是因为你每行有300000列,所以他的方法要求你每行测试NR==1近100000次,而下面的方法每行只测试1次,所以它应该更有效:

awk -v OFS="\t" '
            {
              printf("%s",$3);
              for(i=7;i<=NF; i+=3)
              {
                if(FNR==1)
                {
                   sub(/_.*/,"",$i)
                   f = $i
                }else
                {
                    f = sprintf("%5.2f",$i*1 + $(i+1)*2 + $(i+2)*3)
                }
                   printf("%s%s",OFS,f)
              }
                print ""
            }
    ' file
$ cat tst.awk
BEGIN { OFS="\t" }
{
    printf "%s", $3
    if (NR==1) {
        gsub(/_[^[:space:]]+/,"")
        for (i=7; i<=NF; i+=3) {
            printf "%s%s", OFS, $i
        }
    }
    else {
        for (i=7; i<=NF; i+=3) {
            printf "%s%.2f", OFS, $i + $(i+1)*2 + $(i+2)*3
        }
    }
    print ""
}

$ awk -f tst.awk file
rsid    ID1     ID2     ID3     ID4     ID5
rsid1   2.15    1.50    1.70    2.10    2.90
rsid2   1.10    1.30    1.50    2.00    1.90
rsid3   1.70    1.20    2.30    1.70    2.00

我强烈建议您阅读Arnold Robbins的《有效的Awk编程》,第四版,了解什么是Awk以及如何使用它。

虽然这两种方法都有优点/缺点,它们都可以处理任意数量的行/列,因为它们在内存中一次只存储一行,我会使用这种方法,而不是因为你每行有300000列,所以他的方法要求你每行测试NR==1近100000次,而下面的方法每行只测试1次,所以它应该更有效:

$ cat tst.awk
BEGIN { OFS="\t" }
{
    printf "%s", $3
    if (NR==1) {
        gsub(/_[^[:space:]]+/,"")
        for (i=7; i<=NF; i+=3) {
            printf "%s%s", OFS, $i
        }
    }
    else {
        for (i=7; i<=NF; i+=3) {
            printf "%s%.2f", OFS, $i + $(i+1)*2 + $(i+2)*3
        }
    }
    print ""
}

$ awk -f tst.awk file
rsid    ID1     ID2     ID3     ID4     ID5
rsid1   2.15    1.50    1.70    2.10    2.90
rsid2   1.10    1.30    1.50    2.00    1.90
rsid3   1.70    1.20    2.30    1.70    2.00
我强烈建议您阅读Arnold Robbins的《高效Awk编程》第四版,了解什么是Awk以及如何使用它

你认为使用像C这样的低级语言吗

C++或C语言在自动速度上并不比awk快,而且代码可读性较差,更脆弱

另一个C++解决方案,比较

//p.cpp
#include <stdio.h>

//to modify this value
#define COLUMNS 5

int main() {
    char column3[256];
    bool header=true;
    while (scanf("%*s\t%*s\t%255s\t%*s\t%*s\t%*s\t", column3) == 1) {
        printf("%s", column3);
        if(header){
            header=false;
            char name[256];
            for(int i=0; i<COLUMNS; ++i){
                scanf("%[^_]_%*s\t%*s\t%*s\t", name);
                printf("\t%s", name);
            }
        }else{
            float nums[3];
            for(int i=0; i<COLUMNS; ++i){
                scanf("%f %f %f", nums, nums + 1, nums + 2);
                float sum = nums[0]+nums[1]*2+nums[2]*3;
                printf("\t%2.2f", sum);
            }
        }
        printf("\n");
    }
}
基准

输入中有一百万行和300列

埃德·莫顿溶液:2m 34s

c++:1M19S

你认为使用像C这样的低级语言吗

C++或C语言在自动速度上并不比awk快,而且代码可读性较差,更脆弱

另一个C++解决方案,比较

//p.cpp
#include <stdio.h>

//to modify this value
#define COLUMNS 5

int main() {
    char column3[256];
    bool header=true;
    while (scanf("%*s\t%*s\t%255s\t%*s\t%*s\t%*s\t", column3) == 1) {
        printf("%s", column3);
        if(header){
            header=false;
            char name[256];
            for(int i=0; i<COLUMNS; ++i){
                scanf("%[^_]_%*s\t%*s\t%*s\t", name);
                printf("\t%s", name);
            }
        }else{
            float nums[3];
            for(int i=0; i<COLUMNS; ++i){
                scanf("%f %f %f", nums, nums + 1, nums + 2);
                float sum = nums[0]+nums[1]*2+nums[2]*3;
                printf("\t%2.2f", sum);
            }
        }
        printf("\n");
    }
}
基准

输入中有一百万行和300列

埃德·莫顿溶液:2m 34s

c++:1M19S


可能会有一些小的变化,但不确定它们是否会对性能产生重大影响。其他调整,比如从每个分支分解出公共代码,使用惯用条件{action}而不是if/else,与样式的关系比其他任何东西都要大。我想如果你的脚本有效的话,这就属于了。谢谢你的评论@TomFenech,我会更新我的脚本。只是一个简短的问题,我的一位同事指示我使用callby reference在bash中重写脚本。我真的不认为那样做有什么意义。有什么建议吗?我不确定引用调用的概念如何适用于这种情况,但在bash中处理文本文件,尤其是大型文本文件,几乎肯定不是一种可行的方法。我也这么认为。几个问题1。你认为使用像C这样的低级语言并采用引用调用会解决将数据加载到内存中的问题吗?2.在您看来,如果我考虑您的条件{action},这是解决上述问题的最佳代码吗?@DhiwaTdG一次处理每行300000列数据没有问题。那没什么。用C编写代码只会使代码更脆弱、更长,甚至可能更慢。在bash中编写它是非常不合适的,只有当您有两周的假期,而您可以让它运行时,您才应该这样做。看,可能会有一些小的变化,但不确定它们是否会对性能产生重大影响。其他调整,比如从每个分支分解出公共代码,使用惯用条件{action}而不是if/else,与样式的关系比其他任何东西都要大。我想如果你的脚本有效的话,这就属于了。谢谢你的评论@TomFenech,我会更新我的脚本。只是一个简短的问题,我的一位同事指示我使用callby reference在bash中重写脚本。我真的不认为那样做有什么意义。有什么建议吗?我不确定引用调用的概念如何适用于这种情况,但在bash中处理文本文件,尤其是大型文本文件,几乎肯定不是一种可行的方法。我也这么认为。几个问题1。你认为使用像C这样的低级语言并采用引用调用会解决将数据加载到内存中的问题吗?2.你认为如果我考虑到你的条件{行动}
考虑一下,这是解决上述问题的最佳代码吗?@DhiwaTdG一次处理每行300000列数据没有问题。那没什么。用C编写代码只会使代码更脆弱、更长,甚至可能更慢。在bash中编写它是非常不合适的,只有当您有两周的假期,而您可以让它运行时,您才应该这样做。请看代码看起来非常高效。这类似于@Tom Fenech关于使用条件{action}逻辑的建议吗?不。我本来打算使用它,但后来决定不使用,这样我就可以在这两种情况下重复使用循环周围的通用代码。^1今天学到了一些新东西,很小但很有效。代码看起来非常有效。这类似于@Tom Fenech关于使用条件{action}逻辑的建议吗?不。我本来打算使用它,但后来决定不使用,这样我就可以在这两种情况下重复使用循环周围的通用代码。^1今天学到了一些新东西,很小但很有效
//p.cpp
#include <stdio.h>

//to modify this value
#define COLUMNS 5

int main() {
    char column3[256];
    bool header=true;
    while (scanf("%*s\t%*s\t%255s\t%*s\t%*s\t%*s\t", column3) == 1) {
        printf("%s", column3);
        if(header){
            header=false;
            char name[256];
            for(int i=0; i<COLUMNS; ++i){
                scanf("%[^_]_%*s\t%*s\t%*s\t", name);
                printf("\t%s", name);
            }
        }else{
            float nums[3];
            for(int i=0; i<COLUMNS; ++i){
                scanf("%f %f %f", nums, nums + 1, nums + 2);
                float sum = nums[0]+nums[1]*2+nums[2]*3;
                printf("\t%2.2f", sum);
            }
        }
        printf("\n");
    }
}
g++ p.cpp -o p
cat file | ./p