1、一、 程序的功能 2 二、 程序的主要流程 3 三、 程序的主要数据结构 5 四、 程序的模块 6 4.1 词法分析模块 6 4.2 负载模块 8 4.2.1 参数介绍 10 4.2.2 重要变量 13 4.2.3 任务偏移值的产生(rankOffset) 13 4.2.5 测试文件的命名 15 4.2.6 TestIoSys函数的简要注释说明 17 4.2.7 ReduceIterResults函数的简要注释说明 21 4.2.8 SummarizeResults函数的简要注释说明 23 4.2.9 WriteOrRead函数的简要注释说明 25 4.3 底层函数
2、模块 28 4.3.1 IOR_Xfer_POSIX函数的简要注释说明 28 五、 总结 30 5.1 IOR的优点 30 5.2 IOR的缺点 30 5.3 如何修改IOR 30 六、 附录 31 6.1 每个文件的作用和信息 31 6.2重要的数据结构 32 6.3 每个c文件所包含的函数 34 6.3.1 Parse_options.c中的函数 34 6.3.2 utilities.c中的函数 36 6.3.3 aiori-POSIX.c中的函数 38 6.3.4 aiori-MPIIO.c中的函数 39 6.3.5 aiori-HDF5.c中的函数 39
3、6.3.6 aiori-NCMPI.c中的函数 39 6.3.7 IOR.c中的函数 39 一、 程序的功能 IOR是测试基准程序,它的功能是接受参数,在client上产生特定的负载,测试系统的系能,并输出测试结果。 根据IOR的功能,可以把IOR程序分成三个模块:词法分析模块、负载模块、底层函数模块。 输入的参数有三种形式: Ø 选项形式-比如 “-w –r –f –c –Z” 等 Ø 赋值形式,在-O选项后面赋值,比如“ –O api=POSIX” Ø 配置脚本形式,在配置脚本中,给参数结构体IOR_param_t成员赋值,形式如 “–f script
4、txt”使用配置脚本形式,可以设置几次测试,每次测试的参数都不一样。 IOR能模拟不同的负载,所以负载模块很复杂,其中最重要的函数TestIoSys()有400+行代码。在TestIoSys()有许多条件语句,根据参数结构体IOR_param_t里成员的值,产生不同操作。 修改IOR,减少不必要的参数,增加读写比例参数,就需要修改结构体IOR_param_t,和修改TestIoSys()函数。 二、 程序的主要流程 简略分析main函数流程,下面是main函数的不完全代码: int main(int argc, char ** argv) { IOR_queue_
5、t * tests; /*IOR_queue_t是参数结构体IOR_param_t的链表结构*/ tests = SetupTests(argc,argv); /*接受参数设置,并检查参数,然后填充参数结构体*/ While(tests != NULL) { TestIoSys(&tests->testParameters); /*根据参数结构体的成员,进行测试*/ test = test->nextTest; /*下一个测试*/ }
6、 } ParseCommandLine //词法分析命令参数,并填充参数结构体 DistributeHints //把环境变量分发到各个进程 VaildTests //检查各个参数结构体内的参数是否有效 TimeDeviation //检查每个任务之间开始时间的偏差 SeedRandGen //产生随机数 ShowTest //显示参数信息 AioriBind //装填I/O接口 ShowSetup //显示设置信息 SummarizeResults //显示测试结果 Time0 //定时器伪代码 Reduce
7、IterResults //归约操作,计算结果 三、 程序的主要数据结构 两个重要的数据结构是: aiori.h中定义的参数结构体IOR_param_t。用来填充参数。 IOR.h中定义的参数结构体队列IOR_queue_t。运行一次程序可以执行几次参数不同的测试。 typedef struct { char debug[MAX_STR]; /* debug info string */ unsigned int mode; /* file permissions */ unsigned i
8、nt openFlags; /* open flags */ int TestNum; /* test reference number */ char api[MAX_STR]; /* API for I/O */ …… int numTasks; /* number of tasks for test */ int nodes; /* number of nodes for
9、test */ int tasksPerNode; /* number of tasks per node */ int repetitions; /* number of repetitions of test */ ….. int readFile; /* read of existing file */ int writeFile; /* write of file */ …. int
10、 keepFile; /* don't delete the testfile on exit */ …. /* POSIX variables */ int singleXferAttempt; /* do not retry transfer if incomplete */ int fsYncPerWrite; /* fsync() after each write */ int fsync; /* fsync() after write */ /
11、 MPI variables */ MPI_Datatype transferType; /* datatype for transfer */ MPI_Datatype fileType; /* filetype for file view */ /* HDF5 variables */ int individualDataSets; /* datasets not shared by all procs */ int noFill; /* no fill in file creation
12、 */ IOR_offset_t setAlignment; /* alignment in bytes */ /* NCMPI variables */ int var_id; /* variable id handle for data set */ /* Lustre variables */ int lustre_stripe_count; int lustre_stripe_size; int lustre_start_ost; int lustre_ignore_locks; } IOR_param_t;
13、 typedef struct IOR_queue_t { IOR_param_t testParameters; struct IOR_queue_t * nextTest; } IOR_queue_t; 四、 程序的模块 4.1 词法分析模块 该模块包含的C文件有 Parse_options.c 该文件包括defaults.h头文件,defaults.h头文件是一个参数结构体defaultParameters的定义,该结构体包含默认的参数值。 Parse_options.c包含5个主要函数,如下表所示。 函数名 功能 ParseComm
14、andLine 对命令行参数进行词法分析 ParseLine 对一行字符串调用DecodeDirective函数进行词法分析 ReadConfigScript 读取配置脚本,分配并填充参数结构体 DecodeDirective 分析诸如“transferSize=64k”一样的偶对,并给参数结构体中的transferSize赋值。 CheckRunSettings 检查和纠正每次测试参数的参数值。 1.参数结构体中的writeFile、readFile、checkWrite、checkRead值为FALSE时,设置readFile和writeFile的值为TRUE。 2.
15、当参数结构体中的numTasks为0时,设置numTasks=MPI进程数 ParseCommandLine函数是词法分析模块的最主要的函数,它和其他函数的关系如下图 小结:参数的输入形式有三种,但对于我们编写的聚合带宽程序只需要一种输入形式,即命令行形式。所以我们只使用ParseCommandLine函数,其他函数可以删除。 4.2 负载模块 该模块包含的C文件有 IOR.C utilities.c IOR.C文件中有主函数main,SetupTests函数,TestIOSys函数,WriteOrRead函数 负载模块的函数关系图如下:
16、 4.2.1 参数介绍 编号 参数 参数作用描述 所对应的参数结构体成员 成员类型 默认值 重要程度 1 -A # 测试标志,可以在输出结果上显示,作为标记。 test reference number for easier test identification in log files TestNum 数值 -1 2 -a [POSIX|MPIIO] API接口选项 api 字符串 POSIX 重要 3 -b # 数据块大小 blockSize 数值 1048576 重要 4 -B 用于POSIX接口,使用直接I/
17、O,不使用I/O缓存。 useO_DIRECT 布尔 0 一般 5 -c 聚合(collective)I/O,MPI的文件操作为组调用 collective 布尔 0 一般 6 -C 读操作时,读取其他任务所写的文件。所有任务的任务偏移量是常量。 reorderTasks 布尔 0 一般 7 -Q # 任务偏移量,用于-C –Z选项 taskPerNodeOffset 数值 1 一般 8 -Z 所有任务的任务偏移量不是常量,是随机产生的。 reorderTasksRandom 布尔 0 一般 9 -X # 与-Z选项一起使用
18、当>0时,每次循环的随机种子都一样,当<0时,每次循环的随机种子都不一样。 reorderTasksRandomSeed 数值 0 一般 10 -d # 每次循环间的延迟时间 interTestDelay 数值 0 11 -D # 每次读写操作的限定时长。数值0表示关闭该选项。 deadlineForStonewalling 数值 0 12 -Y 每次POSIX write操作后都执行同步sync。 fsYncPerWrite 布尔 0 一般 13 -e 每次循环都执行同步sync。 fsync 布尔 0 一般 14 -
19、E 每次写访问前不删除已经存在的文件(使用已经存在的文件)。 useExistingTestFile 布尔 0 15 -f S 配置文件的名字。 16 -F 并发模式(file-per-process) filePerProc 布尔 0 重要 17 -g 所用任务同步进行打开文件操作,同步进行写操作,同步进行读操作,同步进行关闭操作。 use barriers between open, write/read, and close intraTestBarriers 布尔 0 18 -G # 设置时间戳 setTime
20、StampSignature 数值 0 19 -h 显示帮助文件 showHelp 布尔 0 需要 20 -H 显示提示。 show hints(mpi可能会根据hints进行优化操作,提高性能) showHints 布尔 21 -i # 每次测试的循环次数。 repetitions 数值 1 重要 22 -j # 显示哪个进程操作时间已经超出了平均操作时间#秒。数值0表示不使用该选项。 outlierThreshold 数值 0 23 -J # 设置HDF5数据对齐。 setAlignment 字符串 1
21、 24 -k 退出程序后,不删除测试文件。 keepFile 布尔 0 25 -K 数据检查后,保留错误的文件。(通过设置,可以在读写操作后,进行数据检查) keepFileWithError 布尔 0 26 -l 把文件偏移值写到文件中。 use file offset as stored signature storeFileOffset 布尔 0 27 -m 使用循环次序号作为测试文件名字的一部分。也就是每次循环都创建新的文件。 multiFile 布尔 0 一般 28 -n 创建HDF5文件时不填充。 noFill
22、 布尔 0 29 -N # IOR任务数,当IOR任务数大于MPI任务数时,IOR任务数被设定为MPI任务数的值。数值0表示IOR任务数等于MPI任务数。 numTasks 数值 0 重要 30 -o S 测试文件的名字。注意:在filePerProc被设置的情况下,各个任务会取用多文件名下的单文件名‘-o S1@S2@S3 ’。取用的方式是循环取用。如任务0使用的测试文件名字是‘S1’,任务1使用的测试文件名字是‘S2’,任务2使用的测试文件名诗‘S3’,任务3使用的测试文件名是‘S1’,任务4…… S1、S2、S3可以是不同文件系统下的文件名。 testF
23、ileName 字符串 testFile 重要 31 -O S IOR指令字符串。 32 -p 预先分配文件空间。(对于MPI) preallocate 布尔 0 重要 33 -q 当出现文件检查错误时,退出。 quitOnError 布尔 0 34 -r 读文件 readFile 布尔 1 重要 35 -R 读文件后,再读一次,检查是否出错。(数据检查) checkRead 布尔 0 36 -s 文件段数。 segmentCount 数值 1 重要 37 -t 传输大小 transf
24、erSize 数值 262144 重要 38 -T # 设定测试时间最大值。数值0表示关闭该选项。 maxTimeDuration 数值 0 39 -u 对于并发模式,每个任务使用一个工作目录。 uniqueDir 布尔 0 一般 40 -U 提示文件的文件名(包含很多hints,传递hints到程序中,并设置hints,hints作为许多MPI操作函数的参数,影响MPI I/O的性能) full name for hints file hintsFileName 字符串 41 -v 输出结果。 verbose 数值 0
25、 42 -V 使用MPI_File_set_view(MPI中) use MPI_File_set_view useFileView 布尔 一般 43 -w 写文件 writeFile 布尔 1 重要 44 -W 写文件后,读回文件,检查是否出错。(数据检查) checkWrite 布尔 0 45 -x 假如传输出错,不再次进行传输。 singleXferAttempt 布尔 一般 46 -z 随机偏移(randomOffset),也就是随机访问。 access is to random, not sequential, o
26、ffsets within a file 该选项与一下选项不可兼容: checkRead storeFileOffset MPIIO collective of useFileView HDF5 or NCMPI randomOffset 布尔 0 一般 4.2.2 重要变量 extern IOR_param_t defaultParameters, initialTestParams; extern int errno; /* error numb
27、er */ extern char ** environ; int totalErrorCount = 0; int numTasksWorld = 0; /*一共有多少个任务*/ int rank = 0; /*任务编号*/ int rankOffset = 0; /*任务偏移值*/ int tasksPerNode = 0; /* tasks per node */ int
28、 verbose = VERBOSE_0; /* verbose output */ double wall_clock_delta = 0; double wall_clock_deviation; MPI_Comm testComm; 4.2.3 任务偏移值的产生(rankOffset) 任务偏移值(rankOffset)的作用是,当任务读文件时,任务会读第(rank+rankOffset)%test->numTasks任务所写的文件。 以下代码是TestIoSys函数中的部分代
29、码,作用是产生任务偏移值 …… /* *读文件,并在每次I/O操作间计时。 */ if (test->readFile && (maxTimeDuration ? (GetTimeStamp() - startTime < maxTimeDuration) : 1)) { /*基于 -C,-Z,-Q,-X 选项,每个任务为读操作产生任务偏移值*/ /*当设置-C –Q选项时,产生的任务偏移值是常量*/ if (test->reorderTasks) { rankOffset = (test->taskPerNodeOffset*test->tasksPerNode) %
30、 test->numTasks; } /* 当设置-Z –Q (-X)选项时,产生的任务偏移值是随机的*/ if (test->reorderTasksRandom) { /*以下操作不会与文件偏移值(-Z选项)冲突,因为GetOffsetArrayRandom函数产生文件偏移值 */ int *rankoffs, *filecont, *filehits, ifile, jfile, nodeoffset; unsigned int iseed0; nodeoffset = test->taskPerNodeOffset; nodeoff
31、set = (nodeoffset < test->nodes) ? nodeoffset : test->nodes-1; iseed0= (test->reorderTasksRandomSeed < 0) ? (-1*test->reorderTasksRandomSeed+rep):test->reorderTasksRandomSeed; srand(rank+iseed0); rankOffset = rand() % test->numTasks; while (rankOffset < (nodeoffset*tes
32、t->tasksPerNode)) { rankOffset = rand() % test->numTasks; } …… } 4.2.5 测试文件的命名 每个任务调用GetTestFileName函数就能得到测试文件的文件名。测试文件的文件名与参数结构体变量中成员(filePerProc、uniqueDir、multiFile、repCounter)有关。 以下是GetTestFileName的源代码: void GetTestFileName(char * testFileName, IOR_param_t * test) {
33、 char ** fileNames, initialTestFileName[MAXPATHLEN], testFileNameRoot[MAX_STR], tmpString[MAX_STR]; int count; strcpy(initialTestFileName, test->testFileName); fileNames = ParseFileName(initialTestFileName, &count); /*1*/ if
34、count > 1 && test->uniqueDir == TRUE) /*2*/ ERR("cannot use multiple file names with unique directories"); if (test->filePerProc) /*3*/ { strcpy(testFileNameRoot, fileNames[((rank+rankOffset)%test->numTasks) % count]); /*4*/ } else { strcpy(testFileNa
35、meRoot, fileNames[0]); /*5*/
}
/* give unique name if using multiple files */
if (test->filePerProc) /*6*/
{
/** prepend rank subdirectory before filename, e.g., /dir/file => /dir/
36、 /*7*/ { strcpy(testFileNameRoot, PrependDir(test, testFileNameRoot)); /*8*/ } sprintf(testFileName, "%s.%08d", testFileNameRoot, (rank+rankOffset)%test->numTasks); /*9*/ } else { strcpy(testFileName, testFileName
37、Root); /*10*/ } /* add suffix for multiple files */ if (test->repCounter > -1) /*11*/ { sprintf(tmpString, ".%d", test->repCounter); strcat(testFileName, tmpString); /*12*/ } } /* GetTestFileName() */ 为了便于分析,我们设num=(rank+rankOffset
38、)%test->numTasks。
在第1行中,ParseFileName函数对参数结构体中成员testFileName进行词法分析,比如当testFileName=”/tmp/myfs@/mnt/testfs@/mnt/nfs”时,fileNames[0]=” /tmp/myfs”, fileNames[1]=” /mnt/testfs”, fileNames[2]=” /mnt/nfs”。
第8行中,PrependDir函数能把”/mnt/testfs”变成”/mnt/
39、件名加上后缀,后缀是循环序号。
当count=1时,testFileName=” /mnt/testfs”,各个任务的测试文件名如下:
count=1
uniqueDir=1
uniqueDir=0
filePerProc = 1
multiFile=1
/mnt/
40、/mnt/testfs.
41、 * fd; MPI_Group orig_group, new_group; int range[3]; IOR_offset_t dataMoved; /* for data rate calculation */ /*创建测试所需要的通信域(MPI)*/ …… /*求出每个节点的任务数*/ test->tasksPerNode = CountTasksPerNode(test->numTasks, testComm); /*初始化计时数组*/ …… /*初始化每次循环用于保存结果
42、的数组*/ …… /*bind I/O calls to specific API */ AioriBind(test->api); /*计算聚合文件的大小*/ for (rep = 0; rep < test->repetitions; rep++) { test->aggFileSizeFromCalc[rep] = test->blockSize * test->segmentCount * test->numTasks; } /*记录开始时间*/ startTime = GetT
43、imeStamp(); /*循环测试,每个循环中有四种主要操作:写文件、检查写、读文件、检查读*/ for (rep = 0; rep < test->repetitions; rep++) { /*每次循环测试由任务0记录开始时间,并向所有任务广播*/ …… /* 使用循环序号作为测试文件名的一部分,影响GetTestFileName 函数的结果*/ if (test->multiFile) test->repCounter = rep; /*写文件,并计时*/ if (test->writeFile) { GetTestFileNa
44、me(testFileName, test); DelaySecs(test->interTestDelay); if (test->useExistingTestFile == FALSE) RemoveFile(testFileName, test->filePerProc, test); MPI_CHECK(MPI_Barrier(testComm), "barrier error"); test->open = WRITE; timer[0][rep] = GetTimeStamp(); fd = IOR_
45、Create(testFileName, test); timer[1][rep] = GetTimeStamp(); if (test->intraTestBarriers) MPI_CHECK(MPI_Barrier(testComm), "barrier error"); timer[2][rep] = GetTimeStamp(); dataMoved = WriteOrRead(test, fd, WRITE); timer[3][rep] = GetTimeStamp(); if (test
46、>intraTestBarriers) MPI_CHECK(MPI_Barrier(testComm), "barrier error"); timer[4][rep] = GetTimeStamp(); IOR_Close(fd, test); timer[5][rep] = GetTimeStamp(); MPI_CHECK(MPI_Barrier(testComm), "barrier error"); /* get the size of the file just written */ t
47、est->aggFileSizeFromStat[rep] = IOR_GetFileSize(test, testComm, testFileName); /* check if stat() of file doesn't equal expected file size, use actual amount of byte moved */ CheckFileSize(test, dataMoved, rep); ReduceIterResults(test, timer, rep, WRITE);
48、 if (test->outlierThreshold) CheckForOutliers(test, timer, rep, WRITE); }/*if writeFile*/ /*检查写操作*/ if (test->checkWrite) { MPI_CHECK(MPI_Barrier(testComm), "barrier error"); if (test->corruptFile) CorruptFile(testFileName, test, rep, WRITECHECK); MPI_CHECK(MP
49、I_Barrier(testComm), "barrier error"); if (test->reorderTasks) rankOffset = (2*test->tasksPerNode) % test->numTasks; GetTestFileName(testFileName, test); test->open = WRITECHECK; fd = IOR_Open(testFileName, test); dataMoved = WriteOrRead(test, fd, WRITECHECK); IOR_Clos
50、e(fd, test); rankOffset = 0; } /*读文件,并计时*/ if (test->readFile) { /*产生任务偏移值*/ …… GetTestFileName(testFileName, test); DelaySecs(test->interTestDelay); MPI_CHECK(MPI_Barrier(testComm), "barrier error"); test->open = READ; timer[6][rep] = GetTimeStamp(); fd = IOR_Open






