您的位置:首页 > 其它

第一部分 GeoGeo脚本基础 第8章 内存/数据管理

2014-09-25 06:10 204 查看

第8章内存/数据管理

GeoGeo是面向数据集操作的,这些数据集有时会非常大,因此对内存和数据的管理显得尤为重要。

8.1内存的申请和释放

从可见性看,GeoGeo的变量有两类,一类为局地变量,另一类为全局变量。无论局地变量还是全局变量都在内存堆上申请,变量的大小和类型也没有区别。

8.2.1变量声明

变量的申请如前所述(第1章),直接使用变量声明即可。如:

float f[2][3][4];

STRING str;

一个合法的数据类型均可以申请数组,数组的维数不超过16维,每维大小任意。变量所占用的堆内存是一块连续的存储空间。如上述3维数组,在内存中的排列次序为:

f[0][0][0]  f[0][0][1]  f[0][0][2]  f[0][0][3]

f[0][1][0]  f[0][1][1]  f[0][1][2]  f[0][1][3]

f[0][2][0]  f[0][2][1]  f[0][2][2]  f[0][2][3]

f[1][0][0]  f[1][0][1]  f[1][0][2]  f[1][0][3]

f[1][1][0]  f[1][1][1]  f[1][1][2]  f[1][1][3]

f[1][2][0]  f[1][2][1]  f[1][2][2]  f[1][2][3]

数据次序横向一行行逐个排列。下标2、3、4表示数据分两组,每组3行,每行4列。

内存变量除了上述所讲的数据外,还有一部分附加的控制信息与这些数据同时存在,用于描述数据状态和其它数据管理。这部分控制信息用户暂时无法访问。

STRING类型的数据也可以申请数组,如:

STRING str[8];

不同的是STRING类型在声明变量时不分配数据空间,除非用字符串在申请时初始化。如:

STRING str = “这是一个字符串”;

字符串在后续的赋值等操作中动态分配堆空间,使用时注意。

在函数内部定义的变量为局部变量,这些变量在函数结束时自动清除。在函数外定义的变量为全局变量,全局变量在GeoGeo脚本进程结束时不销毁,在GeoGeo解释程序结束时销毁。但用户可以根据需要显式销毁全局变量。

在前述讲述过程线程时,一个过程可以作为一个线程被另一个过程启动。在这两个或者多个过程的任何一个中申请的全局变量在进程中全部有效。这样就遇到2个问题,一是两个过程都申请了同样的全局变量,或者循环启动一个申请了全局变量的过程,有重复申请全局变量的问题。GeoGeo的原则是第1次申请有效,同样的全局变量重复申请被忽略。第2个问题是在一个过程中使用其它过程中申请的全局变量,建议的方法是在需要使用的过程中使用extern语句导入该全局变量名。事实上,即使不使用extern,任何进程都能成功的从全局变量检索表中找到需要使用的变量。使用extern可以提高程序的可读性。

8.2.2释放和声明全局变量

全局变量在不需要时可以使用Free函数释放:

int Free(T var);

函数的参数是任意数据类型的待释放的变量。释放成功时返回1,否则返回0。

注意:锁定的数据对象不能释放,Free函数遇到释放锁定数据对象时直接返回0,不等待解锁或者获取钥匙。可使用IsLocked函数查询数据对象是否被锁定。

可以使用Alloc函数在GeoGeo任何可以执行到的位置声明全局变量:

int Alloc(声明语句);

函数的参数是一个声明变量的完整的声明语句。成功时函数返回1,否则返回0。

应用Alloc函数声明的全局变量与在函数之外直接声明的全局变量一样,可以使用Free函数释放,也可以在GeoGeo解释程序结束时才销毁。

下面是一个声明和销毁全局变量的例子:

程序清单 8.1 8-1-全局变量声明和销毁.c
double Mat1[10000][8000];//声明全局变量 Mat1
main()
{
Print("Mat1已定义,占
%d字节。",SizeOf(Mat1));
Sleep(5000);
Alloc(double Mat2[8000][6000];);//声明全局变量
Mat2
Print("Mat2已定义,占
%d字节。",SizeOf(Mat2));
Sleep(5000);
Free(Mat1);//释放全局变量 Mat1
Print("释放Mat1");
Sleep(5000);
Free(Mat2);//释放全局变量 Mat2
Print("释放Mat2");
}
运行上述代码,输出:

Mat1已定义,占640000000字节

Mat2已定义,占384000000字节

释放Mat1

释放Mat2

8.2变量/内存分块

8.2.1子变量

多维数组的一部分可以单独作为变量使用,GeoGeo将这一部分定义为子变量(见1.6节)。对于1维数组,子变量就是各个数组元素。二维及以上的数组,子变量就是其高维数组的分支。如:

int data[2][2] = {1,2,3,4};

data[0] 就是其一个子变量,其值为1,2。data[1]的值为3,4。

这样做的好处是可以比较容易地分离数据,例如有一个栅格格式的遥感数据,数据的宽度和高度的像元数为width和height,有bands个通道,如果数据以BSQ格式排列(第1通道全部数据、第2通道全部数据…,第bands通道全部数据)。变量名为A,可以表示为:

WORD A[bands][height][width];

此时A[0]
表示是全部1通道数据,A[1]表示2通道数;A[0][0]表示是1通道第1行数据。余类推。

如果数据以BIP格式排列(每个像元位置上排列全部通道数据),上述改为:

WORD A [height][width] [bands];

A[i][j] 就表示第i行第j列上所有通道的数据。

下面是一个子变量赋值的例子:

程序清单 8.2 8-2-子变量.c
main(){
double data[2][2]; //声明一个数组
Rand(0.0,1.0,data); //生成随机数填充该数组
double a[2],b[2];
a = data[0];
b = data[1];
Print("%f %f",a[0],a[1]);
Print("%f %f",b[0],b[1]);
}
运行上述代码,输出如下:

0.239967 0.693045

0.754265 0.320475

8.2.2变量分块拷贝粘贴

对于较大的数据有时需要将局部数据传入或者传出,GeoGeo提供了分块拷贝粘贴函数。Stick函数将一部分数据粘贴到另一个数组的指定位置。Slab函数从一个数组中指定的位置复制出一部分数据。

int Stick( T varD, T varS, int pos[], int size[]);

函数参数 T varD是目标数组,数据希望传输到该数组中,数据类型是GeoGeo合法数据类型;T varS是来源数组,为待传输的数据,数据类型应该和T
varD一致;第三个参数是目标数组的起始位置,本身是一个1维数组,大小应与T varD维数一致,数据类型为int型;最后一个参数与第3个参数对应,指定传输数据每维的大小,例如:

double data[100][100];

double a[2][2] = {1,2,3,4};

int pos[2] = {10,20};

int size[2] = {2,2};

Stick (data,a,pos,size);

第1个参数data是接受数据的变量;第2个参数a是存放待传输数据的变量;第3个参数pos表示传输的数据存放在data中第10行20列的开始位置;最后一个参数size表示传输2行2列的数据。传输成功时函数返回1,否则返回0。

注意:传输数据的某一维开始位置和对应传输数据个数之和不应该超过目标标量对应维的大小。如上述从第10行开始传2行,和为12,不能超过data的行数(100)。

另一个函数Slab从一个来源变量拷贝部分数据:

T varD = Slab(T varS, int pos[], int size[]);

这个函数的第一个参数是来源数据,第2和第3个参数是来源数据的开始位置和各维的大小。函数的返回值是从来源数据拷贝到的数据结果。可以用SizeOf函数检查数据是否传输成功。如果SizeOf函数返回值为0表示传输失败。

下面的示例代码将生成的随机数先粘贴到一个数组中,然后再取来输出:

程序清单 8.3 8-3-内存块拷贝粘贴.c
main(){
double data[200][100][10];//声明一个双精度浮点型数组data
data = 0.0;
//用0初始化该数组

double rnd[2][2][2];//声明一个较小的数组
Rand(0.0,1.0,rnd); //生成随机数填充该数组

int pos[3] ={100,50,5};
int size[3] ={2,2,2};
Stick(data,rnd,pos,size);//将随机数粘贴到data中100,50,5位置

double slb[2][2][4];
pos[2] = 4;
//第3维放数据的前一个位置
size[2] = 4;
//第3维取4个数据
slb =
Slab(data,pos,size);
int n =SizeOf(slb);
Print("拷贝数据 %d字节",n);
if( n != 0){
int i,j;
for(i=0;i<2; i=i+1){
for(j=0; j<2;j=j+1){
Print("%f %f %f %f",slb[i][j][0],slb[i][j][1],slb[i][j][2],slb[i][j][3]);
}
}
}
}
运行上述代码,输出结果如下:

拷贝数据 128字节

0.000000 0.022492 0.596881 0.000000

0.000000 0.788354 0.481429 0.000000

0.000000 0.066103 0.421094 0.000000

0.000000 0.225898 0.112064 0.000000

8.3文件映射

文件映射是将一个磁盘文件的内容的部分或者全部映射到计算机的内存中,这样可以使操作更快捷。如果一个文件足够小,一次读入内存即可,但是如果有一个足够大的文件,无法读入内存,文件映射就显得非常必要。其它还有一些效率和共享等方面原因有理由使用文件映射。

(暂时无内容)

8.4大数据集(Tile File System,TFS)

地理空间数据处理经常遇到非常庞大的数据系统,即使理论上这些数据可以写在一个磁盘文件中,但是是当数据足够大(比方几十几百G或者几个T),由于系统处理能力和计算机系统等诸多限制,对这样数据文件的存取处理已经毫无性能可言。GeoGeo设计的大数据集TFS(Tile
File System)就是用于处理这类数据的。

TFS将非常庞大的数据集分解成诸多的瓦块文件进行处理,同时让用户感觉如同在操作1个单一的大数组。

8.4.1创建TFS

从GeoGeo编程的观点来看,TFS也是一个变量。但有下述限制:


只能是简单变量,不能使用自定义结构声明TFS。


只能是全局变量,不能是局地变量。


使用AllocTile函数创建,不能直接声明。

当然还有一些必要条件,比方缓存磁盘必须要有足够的磁盘空间和一些文件存取权限等。

使用AllocTile函数创建TFS

int AllocTile(声明语句);

函数的参数是一个声明变量的完整的声明语句。成功时函数返回1,否则返回0。这与Alloc函数是一样的。

销毁TFS使用FreeTile函数,同Free函数一样。

下面是一个创建TFS的例子。假设需要创建一个水平480000像元,垂直360000像元,共1700亿左右像元的6个通道的遥感数据的数据集:

程序清单 8.4 8-4-TFS创建和销毁.c
1 main(){
2 DWORD tc = GetTickCount();
3 AllocTile(BYTE data[360000][480000][6];);
4 tc = GetTickCount() - tc;
5 int n = SizeOf(data);
6 Print("创建TFS延续时间:%d 毫秒,数据集字节数%d",tc,n);
7 }
运行上述代码(注意D盘有一个D:\RsdTmp的文件夹,并且有大于1T的磁盘空间)。输出如下:
创建TFS延续时间:47830毫秒,数据集字节数1036800000000
上述代码第3行创建一个1T左右的TFS,从编程的角度看,它就是一个360000×480000×6的3维数组,数组名为data,数组的数据类型为单字节整型。如果不考虑访问效率等因素,这里可以直接使用数组下标存取数组数据。例如我们可以写如下语句:

data[i][j][k] = 128;

BYTE n = data[i][j][k];

i、j、k分别为个下标索引范围内的整型数。

这样多达1T规模的数组当然不是建立在计算机内存上的,通常使用GeoGeo(或者RSD(Remote Sensing Desktop)平台)临时文件目录,如
D:\RsdTmp,创建上述名为data的TFS后,在该目录可以找到一个名为data的子目录,在这个目录中保存了所有的TFS文件。

如果这个临时文件目录需要经常清理,或许用户需要将TFS保存到其它路径,可以使用SetTfsPath函数设置TFS路径名。

查看TFS临时文件目录(如D:\RsdTmp)里面会有一个与变量名相同的目录(如data)。打开这个目录里面会有一系列的文件,如:

data000000000000.tlf



注意:无论使用缺省的TFS路径还是指定路径,一定要确保该路径所在磁盘有足够的存储空间。

8.4.2关闭和销毁TFS

通常的内存数据对象在程序关闭时或者人为指定将其销毁,内存数据对象被销毁时就已经不复存在,占用的内存也被释放。如果希望将这些内存对象保留后续使用,可行的办法之一是将其保存为磁盘文件,以备下次使用。TFS对象与此略有不同,非常辛苦建立的TFS对象有时不是一次应用后就丢弃,可能希望后续的处理中要频繁使用。转储为磁盘文件虽然是一种可行的办法,但是庞大的数据转储为磁盘文件需要大量的磁盘空间和很长的时间,还有一些情况是由于数据太庞大无法用简单的单个磁盘文件存储。

GeoGeo为此设计了两种处理方法,一种是彻底删除TFS对象,不考虑后续应用的情况,这时可以使用FreeTile函数来彻底销毁一个TFS:

int FreeTile(T var);

函数的参数为一合法数据类型的TFS对象,删除成功时函数返回1,否则返回0。

注意:FreeTile函数遇TFS对象被锁定时,是自旋(类似自旋锁)等待的。

程序清单 8.5 8-5-TFS删除.c
1 main(){
2 AllocTile(BYTE data[360000][480000][6];);
3 Sleep(10000);
4 FreeTile(data);
5 }
运行上述代码之前,如果存在data目录,先将其删除。

运行上述代码,创建TFS对象后休眠10秒,然后删除该TFS对象。查看临时文件目录,data子目录已经被清除。

第二种处理方法是使用CloseTile函数关闭TFS对象:

int CloseTile(T var);

这个函数的参数和返回值的意义与FreeTile函数相同,不同的是CloseTile函数在进程中删除了以TFS对象名为标识的内存对象,但是磁盘映像部分被保留下来。可以在下一个应用中使用OpenTile函数再次打开。

将上述程序清单8.5的

FreeTile(data);

一行换成

CloseTile(data);

运行代码会发现临时目录中的data目录不被删除。实际上不使用FreeTle函数和CloseTile函数的任何一个,进程在结束时不会删除TFS,使用CloseTile函数除了使全局变量data立即失效外没有其它更多的作用。

8.4.3打开TFS

未删除的TFS可以重新打开。使用OpenTile函数:

int OpenTile(STRING tfspath,STRING varname);

函数的第一个参数是TFS路径名,第2个参数是打开时的变量名。函数的第二个参数可以省略,省略时使用路径名的最后一个目录名作为变量名。TFS打开成功时返回1,否则返回0。

有时一个打开的TFS有关数据类型和维数等并非预先知道,可以使用下述参数查询:

①使用TypeOf函数查询变量的数据类型:

int TypeOf(T var);

函数的参数为任意类型的数据对象,返回类型名的ID值,返回值的数字所代表的的类型请参阅变量与数组一节的内容。

②使用IsTile函数查询一个变量是不是TFS:

int IsTile(T var);

函数的参数为任意类型的数据对象,如果该数据对象是一个TFS,返回值为1,返回其它值时说明此数据对象不是TFS。

③使用RankOf函数查询数据对象的维数:

int RankOf(T var);

函数的参数为任意类型的数据对象,成功时函数返回数据对象的维数。0表示仅为标量数据(单个数据)。1表示1维数组,余类推。GEOGEO支持数据的最高维数是16维。

注意:查询失败或者出错时返回-1。

④使用DimsOf函数查询各维的大小:

int[] DimsOf(T var);

函数的参数为任意类型的数据对象,返回值为整型1维数组,数组的元素数与RankOf函数返回的数组维数一致。每个元素指明每维数据的大小。查询失败时各维大小均为0。

下面的例子演示了如何打开一个TFS并查询其相关信息。程序清单8.5中将FreeTile函数替换为CloseTile,运行后在D:\RsdTmp产生一个data子目录存放生成的TFS,在这里打开这个TFS。代码如下:

程序清单 8.6 8-6-TFSOpen.c
1 main(){
2 int n = OpenTile("d:\\RsdTmp\\data","data2");//第2个参数应该是合法变量名,可省略
3 longlong sz = SizeOf(data2); //OpenTile的第2个参数可直接作为变量使用
4 int tp = TypeOf(data2);
5 STRING str = "不是TFS";
6 if( 1==IsTile(data2)){
7 str = "是TFS";
8 }
9 int rk = RankOf(data2);
10 longlong dm[rk];
11 dm = DimsOf(data2);
12 Print("打开大数据集,%d字节,数据类型%d,%s,数据维数%d",sz,tp, str,rk);
13 int i;
14 for(i=0; i<rk; i=i+1){
15 Print("第%d维大小:%d",i+1,dm[i]);
16 }
17 CloseTile(data2);
18 }
第2行使用OpenTile函数打开D:\RsdTmp中的data TFS,并将其赋予变量名data2;第3行使用SizeOf函数查询该数据集的总字节数;第4行使用TypeOf函数查询数据类型;第6行使用IsTile函数查询其是否为TFS;第9行使用RankOf函数查询该数据集的维数;第11行使用DimsOf函数查询各维大小。

运行上述代码,输出结果如下:

打开大数据,1036800000000字节,数据类型1,是TFS,数据维数
3

第1
维大小:360000

第2
维大小:480000

第3
维大小:6

8.4.4 TFS的分块拷贝粘贴

TFS也可以像普通数据集一样进行粘贴和拷贝,不过需要使用单独的StickTile和SlabTile函数。

①
用StickTile函数粘贴TFS

int StickTile( T varD, T varS, LONGLONG pos[],LONGLONG size[]);

StickTile函数与Stick函数类似,函数参数 T varD是目标数组;T varS是来源数组,为待传输的数据,数据类型也应该和T
varD一致;第三个参数是目标数组的起始位置,本身是一个1维数组,大小应与T varD维数一致,数据类型为LONGLONG型;最后一个参数与第3个参数对应,指定传输数据每维的大小,也是LONGLONG类型。

TFS除了一系列的Tile文件外,还有一个或多个内存映像文件,一个内存映像文件覆盖若干Tile文件。对TFS访问时,通常是通过数据集的下标索引,定位到映射内存,再从映射文件定位到Tile文件。在这个定位过程中,内存定位文件在TFS中是不断移动的。这样的定位方式在单线程中是没有问题的。但多线程时,不同线程要求内存映射在TFS不同部位时就回引起冲突。因此,StickTile函数绕过内存映射,直接对Tile文件定位,以适应多线程需求。这样做的一个问题是Tile文件内容改变后,内存映射并不知道相应变化,因此需要强制同步。这里使用RefreshTile函数强制同步。

int RefreshTile(T var);

参数是需要强制刷新的大数据集对象。成功时返回1,否则返回0。

多线程使用StickTile函数的伪码如下:

for(i=0; i<n;i=i+1)

{

启动包含有StickTile函数的子线程;

}

等待所有子线程结束;

调用RefreshTile函数;

注意:StickTile函数含有大量的文件操作,当包含StickTile函数的线程中文件操作为主要性能瓶颈而不是主要耗费CPU时间为主的操作时,增加线程并不能提高CPU利用率并提高程序运行效率。

②
用SlabTile函数拷贝TFS

T varD = SlabTile(T varS, LONGLONG pos[], LONGLONG size[]);

同Slab函数一样,函数的第一个参数是来源数据,第2和第3个参数是来源数据的开始位置和各维的大小,但数据类型为64位整型,从而有更宽的寻址范围。函数的返回值是从来源数据拷贝到的数据结果。可以用SizeOf函数检查数据是否传输成功。如果SizeOf函数返回值为0表示传输失败。

SlabTile无需使用强制内存映像刷新,多线程应用同StickTile函数一样。

下面是使用StickTile函数和SlabTile函数的例子:

程序清单 8.7 8-7-TFS拷贝粘贴1.c
1 longlong height = 20000;
2 longlong width = 30000;
3 int bands = 10;
4 int thrds = 20;
5 longlong A[thrds][height/thrds][2][10];
6 longlong B[100][100][10];
7 thread AssignA(longlong start){
8 longlong i,j,k;
9 for(i=0;i<height/thrds;i=i+1){
10 for( j=0; j<2; j=j+1){
11 for( k=0; k<10; k=k+1){
12 //为A数组赋值,A[0][0][0]=100010001,A[100][1][2]=10100020003...
13 A[start][i][j][k] = (start*height/thrds+i+1)*100000000ll+(j+1)*10000ll +k+1;
14 }
15 }
16 }
17 }
18 thread AssignData(longlong p){
19 longlong pos[3] ={0,0,0};
20 longlong size[3] ={1000,2,10};
21 pos[0] = p*1000;
22 StickTile(data,A[p],pos,size);
23 }
24 main(){
25 longlong i,j;
26 int nThread[thrds];
27 DWORD tc = GetTickCount();
28 for( i=0; i<thrds; i=i+1){
29 nThread[i] = AssignA(i);
30 }
31 int sts = 0;
32 while(sts != -1){
33 Sleep(10);
34 for( i=0;i<thrds;i=i+1 ){
35 sts = ThreadStatus(nThread[i]);
36 if(sts != -1){
37 break;
38 }
39 }
40 }
41 tc = GetTickCount() - tc;
42 Print("对A数组赋值,延续时间:%d 毫秒",tc);
43 tc = GetTickCount();
44 AllocTile(longlong data[height][width][bands];); //创建一个大数据集
45 tc = GetTickCount() - tc;
46 Print("创建一个%d字节大数据集延续时间:%d 毫秒",SizeOf(data),tc);
47 tc = GetTickCount();
48 int nThread2[thrds];
49 for(i=0; i<thrds; i=i+1){
50 nThread2[i] = AssignData(i);
51 }
52 sts = 0;
53 while(sts != -1){
54 Sleep(10);
55 for( i=0;i<thrds;i=i+1 ){
56 sts = ThreadStatus(nThread2[i]);
57 if(sts != -1){
58 break;
59 }
60 }
61 }
62 RefreshTile(data);
63 tc = GetTickCount() - tc;
64 Print("数组A传入大数据集data 延续时间:%d 毫秒",tc);
65 longlong pos[3] ={1000,0,0};
66 longlong size[3] ={100,100,10};
67 B = SlabTile(data,pos,size);
68 for( i=0; i<10; i=i+1){
69 for( j=0; j<3; j=j+1){
70 Print("B[%d][%d][0]=%d B[%d][%d][1]=%d B[%d][%d][2]=%d ",pos[0]+i,pos[1]+j, B[i][j][0], pos[0]+i,pos[1] +j,B[i][j][1],pos[0] +i,pos[1]+j,B[i][j][2]);
71 }
72 }
73 }
注:不要在应用中引用这段代码,这段代码浪费大量磁盘存取时间
这段代码在第44行创建一个名为data的20000×30000×10的8字节整型大数据集,数据集占48G字节,无法直接建立内存对象,所以使用了TFS。在第5行建立一个内存数据集A,用于设定初值后逐次向data粘贴数据。A的大小本应该为20000×2×10,这样每次粘贴2列数据到data,15000次完成对data的完全覆盖。这里将A的第1维分解为20×1000,这样A为20×1000×2×10的4维数据。这样每个A[i]代表一个1000×2×10的数据集,共分成20个子数据集以便于多线程处理。

第7~17行是一个对A设置初始值的多线程函数AssignA,在第29行将其循环执行20次启动20个子线程,每次启动时向线程函数传递一个循环顺序的参数,这个参数决定对A的哪个子变量A[i]来设置初始值。设置初始值使用

A[start][i][j][k] = (start*height/thrds+i+1)*100000000ll+(j+1)*10000ll;

设置初始子的目标是将来在data中任意位置 [i][j][k]数据以i*108+j*104+k的形式出现,以验证粘贴和拷贝了正确的数据。

第31~40行循环等待所有20个AssignA函数的子线程全部结束,做法是根据启动线程函数时返回的线程号循环检查线程状态,返回值为-1时表明线程已经死亡。待全部20个线程全部返回-1时,结束循环继续向下执行。

第18~23行是对data粘贴数据的多线程函数AssignData,在第50行将其循环执行20次启动20个子线程,同样每次启动时向线程函数传递一个循环顺序的参数,这个参数决定使用哪个A的子变量A[i]来对data的哪部分粘贴数据。

52~61行同第31~40行一样,循环等待线程函数AssignData的所有实例结束。结束后继续向下执行程序代码。

代码第6行声明了一个100×100×10的内存变量B,用于从data中拷贝数据出来已验证粘贴数据的正确性。这个功能在第67行实现,且没有使用多线程存取。

注意:使用SlabTile和StickTile函数进行TFS操作时,除要求来源数据与目标数据的数据类型一致外,还要求除第1、2维以外的其它维数一致,或者其它维所有大小乘积一致。如上述data、A和B三个数据及第3维均为10。

运行上述代码,输出如下:

对 A数组赋值,延续时间:28751毫秒

创建一个 48000000000字节大数据集延续时间:1763毫秒

数组 A传入大数据集 data延续时间:524459毫秒

B[1000][0][0]=100100010001 B[1000][0][1]=100100010002 B[1000][0][2]=100100010003

B[1000][1][0]=100100020001 B[1000][1][1]=100100020002 B[1000][1][2]=100100020003

B[1000][2][0]=0 B[1000][2][1]=0 B[1000][2][2]=0

B[1001][0][0]=100200010001 B[1001][0][1]=100200010002 B[1001][0][2]=100200010003

B[1001][1][0]=100200020001 B[1001][1][1]=100200020002 B[1001][1][2]=100200020003

B[1001][2][0]=0 B[1001][2][1]=0 B[1001][2][2]=0



下面看一下单线程运行情况。将第18行thread AssignData(longlong p)中thread改为int,去掉52~61行等待线程结束的线程同步代码。重新执行代码,运行结果:

数组 A传入大数据集 data延续时间:198173毫秒

可见向大数据集赋值的时间反而有了显著提高。为什么使用多线程代码反而变慢呢,前面已经提到线程函数使用的SlickTile函数包含有大量的磁盘访问,此时磁盘存取是速度瓶颈而非CPU计算能力,使用多线程反而会因为线程同步开销、磁盘访问线程调度等原因使速度反而变慢。

因此当线程函数包含大量的磁盘访问时可以考虑不用多线程,当然对于分布式存储多机磁盘访问的情况还是多线程有更高效率。

另外从程序清单8.7向data传输数组A所占用的时间来看,这段耗时传送的数据仅占data数据集的1/15000,如果将data传输全部数据,时间开销是不能容忍的。实际上这是一个极端情况,一方面是由于GeoGeo目前还没有优化,优化后速度会有极大提升空间,另一方面该数组A是以列为单元向data传送数据的。以列为单元传递会造成TFS的反复寻址和定位,而每次写入仅是少量数据。

设计应用中应该尽量以行为单位传递,这样会大大提高传输效率,请看下述更改后代码:

程序清单 8.8 8-8-TFS拷贝粘贴2.c
1 longlong height = 30000;
2 longlong width = 20000;
3 int bands = 10;
4 int thrds = 20;
5 longlong A[2][20000][10];
6 longlong B[100][100][10];

7 thread AssignA(longlong start){
8 longlong i,j,k;
9 for( i=0; i<2; i=i+1){
10 for(j=0;j<width/thrds;j=j+1){
11 for( k=0; k<10; k=k+1){
12 A[i][start*width/thrds + j][k] = (i+1)*100000000ll+(start*width/thrds+j+1) *10000ll+k+1;
13 }
14 }
15 }
16 }
17 main(){
18 longlong i,j;
19 int nThread[thrds];
20 DWORD tc = GetTickCount();
21 for( i=0; i<thrds; i=i+1){
22 nThread[i] = AssignA(i);
23 }
24 int sts = 0;
25 while(sts != -1){
26 Sleep(10);
27 for( i=0;i<thrds;i=i+1 ){
28 sts = ThreadStatus(nThread[i]);
29 if(sts != -1){
30 break;
31 }
32 }
33 }
34 tc = GetTickCount() - tc;
35 Print("对A数组赋值,延续时间:%d 毫秒",tc);
36 tc = GetTickCount();
37 AllocTile(longlong data[height][width][bands];); //创建一个大数据集
38 tc = GetTickCount() - tc;
39 Print("创建一个%d字节大数据集延续时间:%d 毫秒",SizeOf(data),tc);
41 longlong pos[3] ={0,0,0};
42 longlong size[3] ={2,20000,10};
43 tc = GetTickCount();
44 StickTile(data,A,pos,size);
45 tc = GetTickCount() - tc;
46 Print("数组A传入大数据集data 延续时间:%d 毫秒",tc);
47 RefreshTile(data);
48 longlong pos2[3] ={0,1000,0};
49 longlong size2[3] ={100,100,10};
50 B = SlabTile(data,pos2,size2);
51 for( i=0; i<2; i=i+1){
52 for( j=0; j<10; j=j+1){
53 Print("B[%d][%d][0]=%d B[%d][%d][1]=%d B[%d][%d][2]=%d ",pos2[0]+i,pos2[1]+j, B[i][j][0],pos2[0]+i,pos2[1]+j, B[i][j][1],pos2[0]+i,pos2[1]+j,B[i][j][2]);
54 }
55 }
56 }
这段代码将程序清单8.7的行列进行了互换,以行为单元向data传输数据(程序清单8.8第44行)。不启用并发多线程,仅耗时几十毫秒,与程序清单8.7的10万毫秒级形成鲜明对比。

程序运行输出如下:

对 A数组赋值,延续时间:101946毫秒

创建一个 48000000000字节大数据集延续时间:4072毫秒

数组 A传入大数据集 data延续时间:47毫秒



8.5本章小结

1. 全局变量除在函数外声明以外,可以在函数内部使用Alloc函数声明。全局变量在解释程序结束时自动销毁也可以使用Free函数在需要时销毁。

2. 使用AllocTile函数可以创建非常大的“数组”,称之为GeoGeo大数据集。GeoGeo大数据集的大小仅取决于TFS临时目录所在磁盘空间的大小。这些大数据集使用起来与普通变量一样,可以直接访问数组元素和子变量。可以使用FreeTile释放这些大数据集。

3. 无论普通变量还是大数据集都可以进行分块存取处理。

下载地址:http://download.csdn.net/detail/gordon3000/7922555
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: