mcmdのメソッド比較

nysol.mcmdが提供する70以上の処理メソッドの中から、主な処理メソッドについての処理速度の実験を行う。

準備

本節で扱っているPythonスクリプトの全ては github よりダウンロードできる ( リスト 16 )。 ダウンロードしたディレクトリ下 bench/methods に 以下の2つのスクリプトがある。

  • mkdata.py : 集計キーや数値項目を備えたランダムなデータを生成する

  • bench.py : ベンチマーク用スクリプト( リスト 21 )

リスト 16 ベンチマークスクリプトのダウンロード
1$ git clone https://github.com/nysol/bench.git
2$ cd bench/methods

利用データ

利用したデータは、「 mcmdベンチマーク用乱数データセット 」節で解説されている人工的に生成したデータであり、 上述のgitからダウンロードしたスクリプト mkdata.py を実行すれば ./DATA ディレクトリが作成され、 そこに 表 10 に示される異なる3つのサイズのデータが生成される。 処理には10分ほどかかる。 データの詳細については、「 mcmdベンチマーク用乱数データセット 」節を参照されたい。

表 10 ベンチマークに用いたデータ一覧

名称

ファイル名

行数(ヘッダ除く)

サイズ

small

10000.csv

10000

665 KB

middle

1000000.csv

1000000

68.5 MB

large

100000000.csv

100000000

7.05 GB

検証内容

mcmdの処理メソッドの速度を評価するために、最も処理速度が速いであろう mcut 処理メソッドをベンチマークに、 他のメソッドの相対的実行時間を計測した。 個々の処理メソッドの関数を bench.py から抜粋したものを リスト 17 に示している。 それぞれの関数では、入力ファイル名 iFile と 実行回数 loop をパラメータとしてとる。 入力ファイルは、 表 10 に示される3種のCSVデータで、 実行回数は5回固定である。 時間の計測は、実行に要した実時間を計測し、その平均と標準偏差を計算している。 なお、出力は基本的にカレントディレクトリにファイルとして書き出している。

msum_key3 関数は、key3項目を集計キーにして全ての数値項目を合計する処理である。 このように関数の名前にキー項目など主だった特徴となるキーワードを用いている。 msum_key3_presort 関数では、キー項目を事前に並べ替えた後で msum を実行している。 このことによって、裏で自動的に実行される並べ替えを省いて純粋なmsumの実行時間を計測できることになる。

リスト 17 ベンチマークスクリプトのダウンロード
 1def mcut(iFile,loop):
 2  sec=[]
 3  for i in range(loop):
 4    st=time.time()
 5    nm.mcut(f="id,key1,key2,key3,int1,int2,float1,float2,date,time",i=iFile,o="oFile").run()
 6    sec.append(time.time()-st)
 7  return sec
 8
 9def msum_key3(iFile,loop):
10  sec=[]
11  for i in range(loop):
12    st=time.time()
13    nm.msum(k="key3",f="int1,int2,float1,float2",i=iFile,o="oFile").run()
14    sec.append(time.time()-st)
15  return sec
16
17def msum_key3_presort(iFile,loop):
18  nm.msortf(f="key3",i=iFile,o="sorted").run()
19  sec=[]
20  for i in range(loop):
21    st=time.time()
22    nm.msum(k="key3",f="int1,int2,float1,float2",i="sorted",o="oFile").run()
23    sec.append(time.time()-st)
24  return sec

実験結果

表 11 に、mcut処理メソッドをベンチマークにした相対時間を示す。 msum_key3 のlargeで6.2となっているが、mcutのlargeを処理した時間に比べて6.2倍の時間を要したことを意味している。 個々の処理内容の詳細については bench.py を直接参照されたい。 また、表 12 に実時間の平均と標準偏差を示す。

表 11 実験結果(対mcutの実行時間比)

method

small(1万行:665KB)

middle(100万行:68.5MB)

large(1億行:7.05GB)

mcut

1

1

1

msum_key3

21.9

4.9

6.2

msum_key3_presort

3.7

1.5

1.1

mhashsum_key3

7.4

2.6

1.8

msortf_key3

4.8

2.2

4.6

msortf_float2

20.8

6.1

12.9

表 12 実験結果(実時間秒数:mean(sd))

method

small(1万行:665KB)

middle(100万行:68.5MB)

large(1億行:7.05GB)

mcut

0.004002(0.000703)

0.307001(0.001781)

30.737572(0.125166)

msum_key3

0.087729(0.113214)

1.494672(0.012172)

190.174376(1.948978)

msum_key3_presort

0.014930(0.000098)

0.463388(0.001697)

35.182463(0.048672)

mhashsum_key3

0.029656(0.002965)

0.805394(0.003971)

56.813676(0.107048)

msortf_key3

0.019332(0.004538)

0.666170(0.004806)

140.808167(0.528175)

msortf_float2

0.083201(0.131085)

1.887404(0.024765)

397.061643(0.161616)

ベンチマークテストを実施した計算環境は以下の通りである。

  • PC: MacPro(2013)

  • CPU: 2.7GHz 12-Core Intel Xeon E5

  • memory: 64GB

  • hdd: USB3 HDD

参考: ベンチマークスクリプト

参考までにベンチマークテストに利用したスクリプトを、 リスト 18 に示す( 2021年08月18日 時点)。

リスト 18 ベンチマークスクリプト
  1#!/usr/bin/env python
  2# -*- coding: utf-8 -*-
  3
  4import os
  5import time
  6import datetime
  7import nysol.mcmd as nm
  8
  9def mcut(iFile,loop):
 10  sec=[]
 11  for i in range(loop):
 12    st=time.time()
 13    nm.mcut(f="id,key1,key2,key3,int1,int2,float1,float2,date,time",i=iFile,o="oFile").run()
 14    sec.append(time.time()-st)
 15  return sec
 16
 17def msortf_key1(iFile,loop):
 18  sec=[]
 19  for i in range(loop):
 20    st=time.time()
 21    nm.msortf(f="key1",i=iFile,o="oFile").run()
 22    sec.append(time.time()-st)
 23  return sec
 24
 25def msortf_key2(iFile,loop):
 26  sec=[]
 27  for i in range(loop):
 28    st=time.time()
 29    nm.msortf(f="key1",i=iFile,o="oFile").run()
 30    sec.append(time.time()-st)
 31  return sec
 32
 33def msortf_key3(iFile,loop):
 34  sec=[]
 35  for i in range(loop):
 36    st=time.time()
 37    nm.msortf(f="key1",i=iFile,o="oFile").run()
 38    sec.append(time.time()-st)
 39  return sec
 40
 41def msortf_float2(iFile,loop):
 42  sec=[]
 43  for i in range(loop):
 44    st=time.time()
 45    nm.msortf(f="float2%n",i=iFile,o="oFile").run()
 46    sec.append(time.time()-st)
 47  return sec
 48
 49def msum_key3(iFile,loop):
 50  sec=[]
 51  for i in range(loop):
 52    st=time.time()
 53    nm.msum(k="key3",f="int1,int2,float1,float2",i=iFile,o="oFile").run()
 54    sec.append(time.time()-st)
 55  return sec
 56
 57def msum_key3_presort(iFile,loop):
 58  nm.msortf(f="key3",i=iFile,o="sorted").run()
 59  sec=[]
 60  for i in range(loop):
 61    st=time.time()
 62    nm.msum(k="key3",f="int1,int2,float1,float2",i="sorted",o="oFile").run()
 63    sec.append(time.time()-st)
 64  return sec
 65
 66
 67def mhashsum_key3(iFile,loop):
 68  sec=[]
 69  for i in range(loop):
 70    st=time.time()
 71    nm.mhashsum(k="key3",f="int1,int2,float1,float2",i=iFile,o="oFile").run()
 72    sec.append(time.time()-st)
 73  return sec
 74
 75
 76##########################################################################
 77# functions for benchmark test
 78##########################################################################
 79# calculate actual execution time for each method
 80# iFile
 81# method,dataSize,mean,sd
 82# mcut,10000,0.004002,0.000703
 83# mcut,1000000,0.307001,0.001781
 84# oFile
 85# method,small,middle,large
 86# mcut,0.004002(0.000703),0.307001(0.001781),30.737572(0.125166)
 87# msum_key3,0.087729(0.113214),1.494672(0.012172),190.174376(1.948978)
 88# msum_key3_presort,0.014930(0.000098),0.463388(0.001697),35.182463(0.048672)
 89def calTime(iFile,oFile):
 90  f=None
 91  f <<= nm.mnumber(q=True, a="id", i=iFile)
 92  f <<= nm.mcal(c='$s{mean}+"("+$s{sd}+")"', a="time")
 93  f <<= nm.m2cross(k="method",s="dataSize",f="time")
 94  f <<= nm.msortf(f="id%n")
 95  f <<= nm.mcut(f="method,10000:small,1000000:middle,100000000:large")
 96  f <<= nm.mfldname(q=True,o=oFile)
 97  f.run()
 98
 99# calculate relative execution time to "mcut" method for each method
100# iFile
101# method,dataSize,mean,sd
102# mcut,10000,0.004002,0.000703
103# mcut,1000000,0.307001,0.001781
104# oFile
105# mcut,1,1,1
106# msum_key3,21.9,4.9,6.2
107# msum_key3_presort,3.7,1.5,1.1
108# mhashsum_key3,7.4,2.6,1.8
109def calRelative(iFile,oFile):
110  mcut=None
111  mcut <<= nm.mselstr(f="method",v="mcut", i="methods.csv")
112
113  f=None
114  f <<= nm.mnumber(q=True, a="id", i=iFile)
115  f <<= nm.mjoin(k="dataSize",m=mcut,f="mean:base")
116  f <<= nm.mcal(c='round(${mean}/${base},0.1)', a="score")
117  f <<= nm.m2cross(k="method",s="dataSize",f="score")
118  f <<= nm.msortf(f="id%n")
119  f <<= nm.mcut(f="method,10000:small,1000000:middle,100000000:large")
120  f <<= nm.mfldname(q=True,o=oFile)
121  f.run()
122
123# calculate mean and SD of multiple executions
124def cal(sec):
125  mean=0
126  for s in sec:
127    mean+=s
128  mean/=len(sec)
129  sd=0
130  for s in sec:
131    sd+=(s-mean)**2
132  sd/=(len(sec)-1)
133  sd=sd**(1/2)
134  return mean,sd
135
136################
137# entry point
138
139iPath="./DATA"
140loop=5
141small =10000
142middle=1000000
143large =100000000
144funcs=[]
145funcs.append("mcut")
146funcs.append("msum_key3")
147funcs.append("msum_key3_presort")
148funcs.append("mhashsum_key3")
149funcs.append("msortf_key3")
150funcs.append("msortf_float2")
151
152with open("methods.csv","w") as fpw:
153  fpw.write("method,dataSize,mean,sd\n")
154  for func in funcs:
155    for size in [small,middle,large]:
156      iFile="%s/%d.csv"%(iPath,size)
157      name="%s_%s"%(func,size)
158      print("START:",name)
159      sec=eval(func+'("%s",%d)'%(iFile,loop))
160      print("tm",sec)
161      mean,sd=cal(sec)
162      fpw.write("%s,%s,%f,%f\n"%(func,size,mean,sd))
163
164today = datetime.date.today().strftime('%Y%m%d')
165calTime("methods.csv","time_%s.csv"%today)
166calRelative("methods.csv","score_%s.csv"%today)
167
168os.system("output files: methods.csv, time.csv, score.csv")