awk中的关联数组具有挑战性的内存限制

Mur*_*gie 5 memory awk gawk

这与我最近在关联数组的awk代码中的帖子有关 - 数组似乎没有填充,但没有错误,也没有优化循环,从外部文件传递参数,在awk中命名数组参数

我在这里的基本问题是简单地从古代的详细档案金融市场数据,#transactions的每日汇总,#shares,价值,按日期来计算,企业-ID,交换等学会了awk来用关联数组实现这一点,是很高兴能够在11分钟的时钟时间内处理9,900多万行.直到我喝完咖啡之前.

变得更加雄心勃勃,从2个阵列下标转移到4个,现在我无法一次处理超过6500行.

获取表单的错误消息:

K:\用户文件夹\ KRISHNANM\PAPERS\FII_Transaction_Data> zcat RAW_DATA\2003_1.zip | gawk -f CODE\FII_daily_aggregates_v2.awk> OUTPUT\2003_1.txt&

gawk:CODE\FII_daily_aggregates_v2.awk:33:(FILENAME = - FNR = 49300)致命:more_no des:nextfree:无法分配内存(空间不足)

在一些运行中,机器告诉我它缺少52 KB的内存.我对Win-7和8MB RAM的std配置有所了解.

(经济学家通过培训,而不是计算机科学家.)我意识到,从2到4阵列使计算机上的问题在计算上变得更加复杂,但是有一些人可以做些什么来改善内存管理至少一点点.我试过关闭我正在做的其他事情.该错误始终只与内存有关,从不与磁盘空间或其他任何内容有关.

示例INPUT:

49290,C198962542782200306,6/30/2003,433581,F5811773991200306,S5405611832200306,B5086397478200306,NESTLE INDIA LTD.,INE239A01016,6/27/2003,1,E9035083824200306,REG_DL_STLD_02,591.13,5655,3342840.15,REG_DL_INSTR_EQ,REG_DL_DLAY_P,DL_RPT_TYPE_N,DL_AMDMNT_DEL_00
49291,C198962542782200306,6/30/2003,433563,F6292896459200306,S6344227311200306,B6110521493200306,GRASIM INDUSTRIES LTD.,INE047A01013,6/27/2003,1,E9035083824200306,REG_DL_STLD_02,495.33,3700,1832721,REG_DL_INSTR_EQ,REG_DL_DLAY_P,DL_RPT_TYPE_N,DL_AMDMNT_DEL_00
49292,C198962542782200306,6/30/2003,433681,F6513202607200306,S1724027402200306,B6372023178200306,HDFC BANK LTD,INE040A01018,6/26/2003,1,E745964372424200306,REG_DL_STLD_02,242,2600,629200,REG_DL_INSTR_EQ,REG_DL_DLAY_D,DL_RPT_TYPE_N,DL_AMDMNT_DEL_00
49293,C7885768925200306,6/30/2003,48128,F4406661052200306,S7376401565200306,B4576522576200306,Maruti Udyog Limited,INE585B01010,6/28/2003,3,E912851176274200306,REG_DL_STLD_04,125,44600,5575000,REG_DL_INSTR_EQ,REG_DL_DLAY_P,DL_RPT_TYPE_N,DL_AMDMNT_DEL_00
49294,C7885768925200306,6/30/2003,48129,F4500260787200306,S1312094035200306,B4576522576200306,Maruti Udyog Limited,INE585B01010,6/28/2003,4,E912851176274200306,REG_DL_STLD_04,125,445600,55700000,REG_DL_INSTR_EQ,REG_DL_DLAY_P,DL_RPT_TYPE_N,DL_AMDMNT_DEL_00
49295,C7885768925200306,6/30/2003,48130,F6425024637200306,S2872499118200306,B4576522576200306,Maruti Udyog Limited,INE585B01010,6/28/2003,3,E912851176274200306,REG_DL_STLD_04,125,48000,6000000,REG_DL_INSTR_EU,REG_DL_DLAY_P,DL_RPT_TYPE_N,DL_AMDMNT_DEL_00
Run Code Online (Sandbox Code Playgroud)

BEGIN { FS = "," } 
# For each array subscript variable -- DATE ($10), firm_ISIN ($9), EXCHANGE ($12), and FII_ID ($5), after checking for type = EQ, set up counts for each value, and number of unique values.
         ( $17~/_EQ\>/ )    {    if (date[$10]++ == 0) date_list[d++] = $10;
                                 if (isin[$9]++ == 0) isin_list[i++] = $9;
                                 if (exch[$12]++ == 0) exch_list[e++] = $12;
                                 if (fii[$5]++ == 0) fii_list[f++] = $5;
                                 }
# For cash-in, buy (B), or cash-out, sell (S) count NR = no of records, SH = no of shares, RV = rupee-value.
         (( $17~/_EQ\>/ ) && ( $11~/1|2|3|5|9|1[24]/ )) {{ ++BNR[$10,$9,$12,$5]} {BSH[$10,$9,$12,$5] += $15} {BRV[$10,$9,$12,$5] += $16} }
         (( $17~/_EQ\>/ ) && ( $11~/4|1[13]/ ))         {{ ++SNR[$10,$9,$12,$5]} {SSH[$10,$9,$12,$5] += $15} {SRV[$10,$9,$12,$5] += $16} }
 END  { 
          { print NR, "records processed."}
          { print "  " }
          { printf("%-11s\t%-13s\t%-20s\t%-19s\t%-7s\t%-7s\t%-14s\t%-14s\t%-18s\t%-18s\n",    \
           "DATE", "ISIN", "EXCH", "FII", "BNR", "SNR", "BSH", "SSH", "BRV", "SRV")  }
       { for (u = 0; u < d; u++)  
             {
              for (v = 0; v < i; v++)
                  {
                   for (w = 0; w < e; w++)
                       {
                        for (x = 0; x < f; x++)
                        #check first below for records with zeroes, don't print them
                            {    if  (BNR[date_list[u],isin_list[v],exch_list[w],fii_list[x]] + SNR[date_list[u],isin_list[v],exch_list[w],fii_list[x]] > 0)
                                      { BR = BNR[date_list[u],isin_list[v],exch_list[w],fii_list[x]]
                                        SR = SNR[date_list[u],isin_list[v],exch_list[w],fii_list[x]]
                                        BS = BSH[date_list[u],isin_list[v],exch_list[w],fii_list[x]]
                                        BV = BRV[date_list[u],isin_list[v],exch_list[w],fii_list[x]]
                                        SS = SSH[date_list[u],isin_list[v],exch_list[w],fii_list[x]]
                                        SV = SRV[date_list[u],isin_list[v],exch_list[w],fii_list[x]]
                                       { printf("%-11s\t%13s\t%20s\t%19s\t%7d\t%7d\t%14d\t%14d\t%18.2f\t%18.2f\n",    \
                                              date_list[u], isin_list[v], exch_list[w], fii_list[x], BR, SR, BS, SS, BV, SV) } }
                            }
                        }
                   }
               }
         }
  }
Run Code Online (Sandbox Code Playgroud)

预期产出

 6 records processed.

DATE        ISIN            EXCH                    FII                 BNR     SNR     BSH             SSH             BRV                 SRV               
6/27/2003    INE239A01016      E9035083824200306      F5811773991200306       1       0           5655               0          3342840.15                0.00
6/27/2003    INE047A01013      E9035083824200306      F6292896459200306       1       0           3700               0          1832721.00                0.00
6/26/2003    INE040A01018    E745964372424200306      F6513202607200306       1       0           2600               0           629200.00                0.00
6/28/2003    INE585B01010    E912851176274200306      F4406661052200306       1       0          44600               0          5575000.00                0.00
6/28/2003    INE585B01010    E912851176274200306      F4500260787200306       0       1              0          445600                0.00         55700000.00
Run Code Online (Sandbox Code Playgroud)

在这种情况下,当输入记录的数量超过6500时,我最终会遇到内存问题.总共有大约700万条记录.

对于2数组下标问题,尽管在不同的数据集上,在同一台机器上使用相同的GNU-AWK在11分钟的时钟时间内处理了12亿多行,请参阅优化循环,从外部文件传递参数,命名数组awk中的参数

问题:awk在内存管理方面是不是很聪明,但是其他一些更现代的工具(比如说SQL)会用相同的内存资源来完成这项任务吗?或者这只是关联数组的一个特征,我发现它让我能够避免对数据,许多循环和SORT程序进行多次传递,但是它可能很好地工作到2个数组下标,然后面对指数内存资源成本那?

后记:超级详细的几乎防止白痴的教程以及Ed Morton在下面的评论中提供的代码产生了巨大的差异,尤其是他的GAWK脚本tst.awk.他告诉我(a)智能地使用SUBSEP(b)处理不必要的循环,这在这个问题中至关重要,这个问题往往具有非常稀疏的数组,具有各种AWK结构.与我的旧代码的性能相比(在一台机器上只接受了多达6500行输入,另一台甚至无法达到这一点),Ed Morton的tst.awk的性能可以从下表中看出:

**filename  start      end       min        in ln   out lines
2008_1  12:08:40 AM 12:27:18 AM 0:18        391438  301160
2008_2  12:27:18 AM 12:52:04 AM 0:24        402016  314177
2009_1  12:52:05 AM 1:05:15 AM  0:13        302081  238204
2009_2  1:05:15 AM  1:22:15 AM  0:17        360072  276768
2010_1  "slept"                         507496  397533
2010_2  3:10:26 AM  3:10:50 AM  0:00         76200   58228
2010_3  3:10:50 AM  3:11:18 AM  0:00         80988   61725
2010_4  3:11:18 AM  3:11:47 AM  0:00         86923   65885
2010_5  3:11:47 AM  3:12:15 AM  0:00         80670   63059**
Run Code Online (Sandbox Code Playgroud)

简单地通过在执行tst.awk之前和之后的行上使用%time%来获得时间,所有这些都放在一个简单的批处理脚本中,"min"是所花费的时钟时间(默认情况下EXCEL执行的任何舍入),"in ln" "out line"分别是输入和输出的行.从处理,我们有完整的数据,从2003年1至1月2014年,我们发现的输出记录的理论最大数量=#日期*#ISINs*#交流*#境外机构投资者= 2992*2955*567*82268,而实际数量总输出线数仅为5261,942,仅为理论最大值的1.275*10 ^( - 8) - 非常稀疏.虽然存在稀疏性,但我们之前做过猜测,但是数组可能非常稀疏 - 这对于内存管理来说非常重要 - 我们无法通知实际数据集.所花费的时间似乎在输入大小中呈指数级增长,但在不会造成实际困难的限度内.谢谢你,艾德.

Ed *_*ton 11

通常,关联数组没有问题.在awk中(除了用于真正2D数组的gawk),具有4个下标的关联数组与具有2个下标的关联数组相同,因为实际上它只有一个下标,其是由SUBSEP分隔的每个伪下标的串联.

鉴于你说I am unable to process more than 6500 lines at a time.这个问题远比你编写代码的方式更可能比任何基本的awk问题更多,所以如果你想要更多的帮助,请发布一个带有示例输入和预期输出的小脚本来演示你的问题和尝试的解决方案看看我们是否有改进内存使用方法的建议.

鉴于您发布的脚本,我预计问题出在您的END部分中的嵌套循环当您执行以下操作时:

for (i=1; i<=maxI; i++) {
    for (j=1; j<=maxJ; j++) {
        if  ( arr[i,j] != 0 ) {
            print arr[i,j]
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

你正在通过测试为循环之前不存在的i和j的每个可能组合创建arr [i,j] arr[i,j] != 0.如果您改为写道:

for (i=1; i<=maxI; i++) {
    for (j=1; j<=maxJ; j++) {
        if  ( (i,j) in arr ) {
            print arr[i,j]
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

然后循环本身不会创建新的条目arr[].

所以改变这个块:

if (BNR[date_list[u],isin_list[v],exch_list[w],fii_list[x]] + SNR[date_list[u],isin_list[v],exch_list[w],fii_list[x]] > 0)
{
    BR = BNR[date_list[u],isin_list[v],exch_list[w],fii_list[x]]
    SR = SNR[date_list[u],isin_list[v],exch_list[w],fii_list[x]]
    BS = BSH[date_list[u],isin_list[v],exch_list[w],fii_list[x]]
    BV = BRV[date_list[u],isin_list[v],exch_list[w],fii_list[x]]
    SS = SSH[date_list[u],isin_list[v],exch_list[w],fii_list[x]]
    SV = SRV[date_list[u],isin_list[v],exch_list[w],fii_list[x]]
Run Code Online (Sandbox Code Playgroud)

这可能会不必要地将BNR,SNR,BSH,BRV,SSH和SRV中的每一个变成巨大但高度稀疏的数组,如下所示:

idx = date_list[u] SUBSEP isin_list[v] SUBSEP exch_list[w] SUBSEP fii_list[x]
BR = (idx in BNR ? BNR[idx] : 0)
SR = (idx in SNR ? SNR[idx] : 0)
if ( (BR + SR) > 0 )
{
    BS = (idx in BSH ? BSH[idx] : 0)
    BV = (idx in BRV ? BRV[idx] : 0)
    SS = (idx in SSH ? SSH[idx] : 0)
    SV = (idx in SRV ? SRV[idx] : 0)
Run Code Online (Sandbox Code Playgroud)

如果有帮助,请告诉我们.还要检查代码中您可能正在执行相同操作的其他位置.

当你没有2时,你有4个下标这个问题的原因只是你在循环中有4级嵌套现在创建更大和更稀疏的数组,当你只有2.

最后 - 你的脚本中有一些奇怪的语法,其中一些@MarkSetchell在注释中指出,你的脚本效率不高,因为你没有使用else语句,所以测试多个条件可以'可能都是真的,你正在反复测试相同的条件,并且它不稳健,因为你没有锚定你的RE(例如你测试/4|1[13]/而不是/^(4|1[13])$/如此,例如你的4匹配1441等等而不仅仅是4它自己)所以将整个脚本更改为:

$ cat tst.awk
BEGIN { FS = "," }
# For each array subscript variable -- DATE ($10), firm_ISIN ($9), EXCHANGE ($12), and FII_ID ($5), after checking for type = EQ, set up counts for each value, and number of unique values.
$17 ~ /_EQ\>/ {
    if (!seenDate[$10]++) date_list[++d] = $10
    if (!seenIsin[$9]++)  isin_list[++i] = $9
    if (!seenExch[$12]++) exch_list[++e] = $12
    if (!seenFii[$5]++)   fii_list[++f]  = $5

    # For cash-in, buy (B), or cash-out, sell (S) count NR = no of records, SH = no of shares, RV = rupee-value.
    idx = $10 SUBSEP $9 SUBSEP $12 SUBSEP $5
    if ( $11 ~ /^([12359]|1[24])$/ ) {
        ++BNR[idx]; BSH[idx] += $15; BRV[idx] += $16
    }
    else if ( $11 ~ /^(4|1[13])$/ ) {
        ++SNR[idx]; SSH[idx] += $15; SRV[idx] += $16
    }
}
END {
    print NR, "records processed."
    print "  "
    printf "%-11s\t%-13s\t%-20s\t%-19s\t%-7s\t%-7s\t%-14s\t%-14s\t%-18s\t%-18s\n",
           "DATE", "ISIN", "EXCH", "FII", "BNR", "SNR", "BSH", "SSH", "BRV", "SRV"
    for (u = 1; u <= d; u++)
    {
        for (v = 1; v <= i; v++)
        {
            for (w = 1; w <= e; w++)
            {
                for (x = 1; x <= f; x++)
                {
                    #check first below for records with zeroes, don't print them
                    idx = date_list[u] SUBSEP isin_list[v] SUBSEP exch_list[w] SUBSEP fii_list[x]
                    BR = (idx in BNR ? BNR[idx] : 0)
                    SR = (idx in SNR ? SNR[idx] : 0)
                    if ( (BR + SR) > 0 )
                    {
                        BS = (idx in BSH ? BSH[idx] : 0)
                        BV = (idx in BRV ? BRV[idx] : 0)
                        SS = (idx in SSH ? SSH[idx] : 0)
                        SV = (idx in SRV ? SRV[idx] : 0)
                        printf "%-11s\t%13s\t%20s\t%19s\t%7d\t%7d\t%14d\t%14d\t%18.2f\t%18.2f\n",
                            date_list[u], isin_list[v], exch_list[w], fii_list[x], BR, SR, BS, SS, BV, SV
                    }
                }
            }
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

seen在4个数组名称前添加了,因为通过约定数组通常会命名测试预先存在的值seen.此外,当填充SNR [] etc数组时,我首先创建了一个idx变量,而不是每次重复使用字段数,以便将来更改它,并且主要是因为字符串连接在awk中相对较慢而且当你使用时会发生什么数组中的多个索引最好只显式地执行字符串连接.并且我将date_list [] etc数组更改为从1开始而不是零,因为所有awk生成的数组,字符串和字段编号从1开始.您可以手动创建一个从0或-357开始的数组或者您想要的任何数字但是如果你总是在1点开始,那么它有一天会让你自己在脚下射击.

我希望通过将嵌套循环限制为可能存在于封闭循环索引组合的值(例如,并非u + v + w的每个值都可能存在,因此有时候你不应该打扰它)可以使它更有效率.在x)上循环.例如:

$ cat tst.awk
BEGIN { FS = "," }
# For each array subscript variable -- DATE ($10), firm_ISIN ($9), EXCHANGE ($12), and FII_ID ($5), after checking for type = EQ, set up counts for each value, and number of unique values.
$17 ~ /_EQ\>/ {
    if (!seenDate[$10]++) date_list[++d] = $10
    if (!seenIsin[$9]++)  isin_list[++i] = $9
    if (!seenExch[$12]++) exch_list[++e] = $12
    if (!seenFii[$5]++)   fii_list[++f]  = $5

    # For cash-in, buy (B), or cash-out, sell (S) count NR = no of records, SH = no of shares, RV = rupee-value.
    idx = $10 SUBSEP $9 SUBSEP $12 SUBSEP $5
    if ( $11 ~ /^([12359]|1[24])$/ ) {
        seen[$10,$9]
        seen[$10,$9,$12]
        ++BNR[idx]; BSH[idx] += $15; BRV[idx] += $16
    }
    else if ( $11 ~ /^(4|1[13])$/ ) {
        seen[$10,$9]
        seen[$10,$9,$12]
        ++SNR[idx]; SSH[idx] += $15; SRV[idx] += $16
    }
}
END {
    printf "d = %d\n", d | "cat>&2"
    printf "i = %d\n", i | "cat>&2"
    printf "e = %d\n", e | "cat>&2"
    printf "f = %d\n", f | "cat>&2"

    print NR, "records processed."
    print "  "
    printf "%-11s\t%-13s\t%-20s\t%-19s\t%-7s\t%-7s\t%-14s\t%-14s\t%-18s\t%-18s\n",
           "DATE", "ISIN", "EXCH", "FII", "BNR", "SNR", "BSH", "SSH", "BRV", "SRV"
    for (u = 1; u <= d; u++)
    {
        date = date_list[u]
        for (v = 1; v <= i; v++)
        {
            isin = isin_list[v]
            if ( (date,isin) in seen )
            {
                for (w = 1; w <= e; w++)
                {
                    exch = exch_list[w]
                    if ( (date,isin,exch) in seen )
                    {
                        for (x = 1; x <= f; x++)
                        {
                            fii = fii_list[x]
                            #check first below for records with zeroes, don't print them
                            idx = date SUBSEP isin SUBSEP exch SUBSEP fii
                            if ( (idx in BNR) || (idx in SNR) )
                            {
                                if (idx in BNR)
                                {
                                    bnr = BNR[idx]
                                    bsh = BSH[idx]
                                    brv = BRV[idx]
                                }
                                else
                                {
                                    bnr = bsh = brv = 0
                                }

                                if (idx in SNR)
                                {
                                    snr = SNR[idx]
                                    ssh = SSH[idx]
                                    srv = SRV[idx]
                                }
                                else
                                {
                                    snr = ssh = srv = 0
                                }

                                printf "%-11s\t%13s\t%20s\t%19s\t%7d\t%7d\t%14d\t%14d\t%18.2f\t%18.2f\n",
                                    date, isin, exch, fii, bnr, snr, bsh, ssh, brv, srv
                            }
                        }
                    }
                }
            }
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

  • 我刚刚在最后添加了另一个示例,该示例演示了如何通过仅在输入中存在父循环的值时执行子循环来提高效率.我不知道有关添加BR和SR以及与零`if((BR + SR)> 0)的比较`对你的输入是否有意义,或者你应该使用像`if((在BNR中的idx)这样的测试)||(SNR中的idx))`.试想一下吧.我会对结果非常感兴趣,看看是否有性能改进,以及是否由于先前未实现的RE匹配而消除了一些不正确的结果. (2认同)
  • 一点也不危险,他们只是使用三元表达式来决定是否在数组'BNR`中填充索引`idx`,如果是这样,则将'BR`设置为它的值,否则将`BR`设置为零.这是常见的日常用品 - 谷歌"三元运营商". (2认同)
  • 就像我说的那样,它只是一个普通的园林三元运营商.`BR =(BNR中的idx?BNR [idx]:0)`相当于`if(BNR中的idx)BR = BNR [idx]; 否则BR = 0` (2认同)
  • 哇,艾德!这是我见过的最全面,最合理和最好解释的答案之一!我希望每个人都读它,消化它,赞成它并接受它! (2认同)