怎样让多维数组的访问更快?应该怎么做啊

vincen_cn 2007-11-21 01:48:57
我在初始化的时候创建了一个多维数组,在程序中要频繁的访问它,
初始化:
m_convertTable = new unsigned char **[256];
for(int i = 0; i < 256; i++)
{
m_convertTable[i] = new unsigned char *[256];
for(int j = 0; j < 256; j++)
{
m_convertTable[i][j] = new unsigned char[256];
for(int k = 0; k < 256; k++)
{
m_convertTable[i][j][k] = 0.3 * i+0.6 * j+0.1* k;
}
}
}
程序中使用的部分:
const int d = 1024*768;
for(int i = 0; i < d; i++)
{
unsigned char r = *pPixel;
unsigned char g = *(pPixel++);
unsigned char b = *(pPixel++);
GrayGraphic[i] = m_convertTable[r][g][b];
}
在这里面因为数组非常大 256 *256 *256 ,
所以数组的访问效率很低,不知道要怎样才能提高数组的访问效率,要怎样改进呢?
...全文
346 29 打赏 收藏 转发到动态 举报
写回复
用AI写文章
29 条回复
切换为时间正序
请发表友善的回复…
发表回复
langya54 2007-12-13
  • 打赏
  • 举报
回复
mark
suyouxin 2007-12-12
  • 打赏
  • 举报
回复
如果用高级语言写就是用r*256*256+g*256+b先把偏移算出来然后直接访问m_convertTable1的偏移数值,其它帮助都不大,然后把编译器优化开到最大。

如果还不满意就直接优化成汇编算了,每个循环只5次访存
计算尽量不用乘操作,然后查查手册,调一调指令顺序,让流水线跑起来
zju3020912063 2007-12-02
  • 打赏
  • 举报
回复
楼主,你的数组非常大,所以涉及到2级缓存的调入调出问题,楼上有人建议你用RGB是对的,表面上没有减少访问次数,实际上由于整个数组减小到1/3,二级缓存重新装载分页的次数会下降到1/3.缓存分页大小我记得是128字节,不同硬件可能会有些差别
知道SSE指令集有一个功能就是命令一个内存分页调入二级缓存,这样在播放前一帧的时候就可以开始调入下一帧,缓存调入的缓慢可见一般.
loops 2007-11-22
  • 打赏
  • 举报
回复
1. 我刚才又测试了一下,加入了pixel数组,就是r=pixel[i];g=pixel[i];b=pixel[i];
发现如果pixel里面内容一样的话,test1和test2时间就产不多,如果pixel里面内容不一样的话,那么用MAKEDWORD的方法才占优。
可能是数据缓存的作用。由于数据缓存的存在给加速了。

2. r,g,b的三次访问内存的操作消耗比较大。


lz可以再测试下面的程序:新加入test3,是对r,g,b进行优化的。pixel值是随机生成的。m_convertTable3是全局静态变量。

#include <string>
#include <iostream>
#include <stdexcept>
#include <windows.h>
#include <time.h>
using namespace std;
#define MAKEDWORD(r,g,b) ((( ((unsigned char)(r)<<8) +(unsigned char)(g))<<8)+(unsigned char)(b) )

char GrayGraphic[1024*768*256];
char ***m_convertTable1 = (char***)new unsigned char **[256];
char *m_convertTable2 = (char*)new unsigned char [256*256*256]; //就是2^24
char m_convertTable3[256*256*256];

unsigned char pixel[1024*768*3];
unsigned int rgb[ 1024*768 ];

void InitTest1()
{
for(int i = 0; i < 256; i++)
{
m_convertTable1[i] = (char**)new unsigned char *[256];
for(int j = 0; j < 256; j++)
{
m_convertTable1[i][j] = (char*)new unsigned char[256];
}
}
}



void test1()
{
int m;
for(int i = 0; i < 1024*768*256; i++)
{
//k=i/256;

m=i%256;
unsigned char r = pixel[m];
unsigned char g = pixel[m+1];
unsigned char b = pixel[m+2];
GrayGraphic[i] = m_convertTable1[r][g][b];
}
}

void test2()
{
int m;
for(int i = 0; i < 1024*768*256; i++ )
{
m=i%256;
unsigned char r = pixel[m];
unsigned char g = pixel[m+1];
unsigned char b = pixel[m+2];
GrayGraphic[i] = m_convertTable2[MAKEDWORD(r,g,b)];
}
}

void test3()
{
int m;
for(int i = 0; i < 1024*768*256; i++ )
{
m=i%256;
GrayGraphic[i] = m_convertTable3[ rgb[m] ];
}
}

int main()
{
int i;
srand((unsigned)time(NULL));
for( i = 0; i < 1024*768*3; i++ )
{
pixel[i]=rand()%256;
}

for( i=0; i<1024*768; i+=3 )
{
rgb[i] = MAKEDWORD( pixel[i],pixel[i+1],pixel[i+2] );
}

DWORD t;
InitTest1();
t = ::GetTickCount();
test2();
cout << "test2 "<< ::GetTickCount()-t<<endl;

t = ::GetTickCount();
test1();
cout << "test1 "<< ::GetTickCount()-t<<endl;

t = ::GetTickCount();
test3();
cout << "test3 "<< ::GetTickCount()-t<<endl;
return 0;
}



xugang_2001 2007-11-22
  • 打赏
  • 举报
回复
没有让你做成全局的,你看看23楼的3个程序就知道了。上面做成全局是因为方便测试用
vincen_cn 2007-11-22
  • 打赏
  • 举报
回复
我在我的机器上的情况是这样的,因为我用到的是一个256*256*256的内存的访问
图像的大小是1024*768。机器配置AMD2500+ 512内存。xp sp2 vc6 sp6
出来的结果是loop的效果跟我以前的效果差不多都是12+ ms
把convertable做成全局变量后效率反而下降了。
xugang_2001 2007-11-21
  • 打赏
  • 举报
回复
我重新梳理了一下,并且发现我的方法和loops差别不大,loops兄可以拷贝然后在Release下执行看看
我猜测编译器可能自动作出优化。但是一维比多维数组效率要高1倍左右,这是肯定的。


#include <string>
#include <iostream>
#include <stdexcept>
#include <windows.h>
using namespace std;
#define MAKEDWORD(r,g,b) ( (( (r<<8) +g)<<8)+b )

char GrayGraphic[1024*768*256];


void InitTest1()
{
int m;
char ***m_convertTable1 = (char***)new unsigned char **[256];
for(int i = 0; i < 256; i++)
{
m_convertTable1[i] = (char**)new unsigned char *[256];
for(int j = 0; j < 256; j++)
{
m_convertTable1[i][j] = (char*)new unsigned char[256];
}
}

for(int i = 0; i < 1024*768*256; i++)
{
m=i%256;
unsigned char r = m;
unsigned char g = m;
unsigned char b = m;
GrayGraphic[i] = m_convertTable1[r][g][b];
}
}



void test1()
{
int m;
char *m_convertTable1 = (char*)new unsigned char [256*256*256]; //就是2^24
for(int i = 0; i < 1024*768*256; i++)
{
m = i % 256;
unsigned char r = m;
unsigned char g = m;
unsigned char b = m;
GrayGraphic[i] = m_convertTable1[r*256*256+g*256+b];
}
}

void test2()
{
int m;
char *m_convertTable2 = (char*)new unsigned char [256*256*256]; //就是2^24
for(int i = 0; i < 1024*768*256; i++ )
{
m=i%256;
unsigned char r = m;
unsigned char g = m;
unsigned char b = m;
GrayGraphic[i] = m_convertTable2[MAKEDWORD(r,g,b)];
}
}

int main()
{
DWORD t;
t = ::GetTickCount();
InitTest1();//Release =~ 5100
cout << ::GetTickCount()-t<<endl;
t = ::GetTickCount();
test1();//Release =~ 3100
cout << ::GetTickCount()-t<<endl;

t = ::GetTickCount();
test2();//Release =~ 3100
cout << ::GetTickCount()-t<<endl;
system("pause");
}
loops 2007-11-21
  • 打赏
  • 举报
回复
r*256*256+g*256+b编译器聪明一点的话就优化成(r<<16)+(g<<8)+b了,其实竹子写的最后那个程序基本上就是我所想的那个了。
256^3==(2^8)*(2^8)*(2^8)=2^(8+8+8)=2^24次方。

对于这种问题,瓶颈就是在于访问内存的开销上。
最快应该是定义一个全局数组,因为数组的地址在编译的时候便可确定,比指针少一次访存时间,频繁累加起来也是很可观的开销。不过因为编译器优化的原因,在lz的例子里面,数组地址多半和指针一样都被放在寄存器里了,已经体现不出优势了。
xugang_2001 2007-11-21
  • 打赏
  • 举报
回复
致电loops和散人:(^_^)

看完了上面的,弄明白了,lz分配的空间是256^3==2^32
现在看来,其实大家的思路是一致的,都是化成一维,但是用位操作计算下标显然比r*256*256+g*256+b速度快很多了(一倍以上)。
所以唯一的区别就在计算下标上,所以loops的算法基本上算是成型的了。赞一下!

不知道还有更好的没有,呵呵~~~

vincen_cn 2007-11-21
  • 打赏
  • 举报
回复
这个东西是在图像处理中经常用到的彩色转灰度的一个算法,
前面的这个表可以在初始化的时候做,时间多少没有关系,
主要的就是要后面的那个循环访问的时候要快速,最好只是在1-2毫秒内完成。

TO:ouyh12345
这个数组不是很大,还没到百万像素,只是一个很普通的图像了,跟我们的桌面一样大。

谢谢各位,我很受启发,继续努力,看看谁有更好的方法。
wh_peng 2007-11-21
  • 打赏
  • 举报
回复
很好很强大
loops 2007-11-21
  • 打赏
  • 举报
回复
改不改只是总体性能的问题,不影响我做比较,因为我用lz程序也是这么设定的。
~~~~~~~~~~
release优化成:GrayGraphic[i] = 一个常数(就是m_convertTable[7][7][7]的值),这样后面那个 for(int i = 0; i < 1024*768; i++ )的循环就不具备可比性了,本来是要比较m_convertTable[r][g][b]和convertTable[MAKEDWORD(r,g,b)]的访存速度的。

要论总体性能的话,lz的256*256+1次动态分配内存已经远远落后于一个new char[2^24]了。


你这样可能有问题,我原来也是这样考虑的,但是要知道MAKEWORD的结果是一个32位的数,m_converTable不可能有那么大的数组下表
~~~~~~~~~~~
你的r*256*256+g*256+b 实质上跟我的宏一样,就是(r<<16)+(g<<8)+b,跟我的宏的运算一样。之所以要MakeDWORD,是因为我没看到过有三字节的数据类型,实际那个4字节的DWORD肯定是小于2^24的,肯定不会越界的。
ouyh12345 2007-11-21
  • 打赏
  • 举报
回复
定义那么大的数组干嘛?
DWORD GrayGraphic[实际的数组大小]
loops 2007-11-21
  • 打赏
  • 举报
回复
我刨除了其他干扰因素,单纯就测试GrayGraphic[k] = m_convertTable1[r][g][b]; 和 GrayGraphic[k] = m_convertTable2[MAKEDWORD(r,g,b)];
谁快谁慢的问题。
循环里面唯一的损耗就是i%256的时间了,这儿特地用了256,此时生成的汇编代码里面一个and 指令就可以搞定,比访问内存要很快。
release下面,Test2(360)比Test1(4375)要快10倍。
此外,因为lz的256*256+1次的动态分配内存占用时间太大,所以内存分配都放到外面去了,不做比较。

#include <string>
#include <iostream>
#include <stdexcept>
#include <windows.h>
using namespace std;
#define MAKEDWORD(r,g,b) ( (( (r<<8) +g)<<8)+b )

char GrayGraphic[1024*768*256];
char ***m_convertTable1 = (char***)new unsigned char **[256];
char *m_convertTable2 = (char*)new unsigned char [256*256*256]; //就是2^24


void InitTest1()
{
for(int i = 0; i < 256; i++)
{
m_convertTable1[i] = (char**)new unsigned char *[256];
for(int j = 0; j < 256; j++)
{
m_convertTable1[i][j] = (char*)new unsigned char[256];
}
}
}



void test1()
{
int m;
for(int i = 0; i < 1024*768*256; i++)
{
//k=i/256;

m=i%256;
unsigned char r = m;
unsigned char g = m;
unsigned char b = m;
GrayGraphic[i] = m_convertTable1[r][g][b];
}
}

void test2()
{
int m;
for(int i = 0; i < 1024*768*256; i++ )
{
m=i%256;
unsigned char r = m;
unsigned char g = m;
unsigned char b = m;
GrayGraphic[i] = m_convertTable2[MAKEDWORD(r,g,b)];
}
}

int main()
{
DWORD t;
InitTest1();
t = ::GetTickCount();
test1();
cout << ::GetTickCount()-t<<endl;

t = ::GetTickCount();
test2();
cout << ::GetTickCount()-t<<endl;
return 0;
}

xugang_2001 2007-11-21
  • 打赏
  • 举报
回复
lz本来的空间耗费就是256*256*256=16M字节,何况lz还进行了256*256+1次的动态分配内存,效率很低。
竹子要测速度的话,最好把
r=rand()%256
g=rand()%256
b=rand()%256
否则,VC release 会优化掉的。
///////////////////////////////////////////////

改不改只是总体性能的问题,不影响我做比较,因为我用lz程序也是这么设定的。
xugang_2001 2007-11-21
  • 打赏
  • 举报
回复
loops: m_convertTable[MAKEDWORD(r,g,b)]
//////////////////////////////////////
你这样可能有问题,我原来也是这样考虑的,但是要知道MAKEWORD的结果是一个32位的数,m_converTable不可能有那么大的数组下表
loops 2007-11-21
  • 打赏
  • 举报
回复
lz本来的空间耗费就是256*256*256=16M字节,何况lz还进行了256*256+1次的动态分配内存,效率很低。
竹子要测速度的话,最好把
r=rand()%256
g=rand()%256
b=rand()%256
否则,VC release 会优化掉的。
我等会儿来写个测试程序。
Wolf0403 2007-11-21
  • 打赏
  • 举报
回复
struct PIXEL
{
UCHAR R;
UCHAR G;
UCHAR B;
}


// 用 #pragma align 或者 __attribute__((align)) 做一下对齐
struct PIXEL
{
UCHAR R;
UCHAR G;
UCHAR B;
UCHAR __padding;
}

xugang_2001 2007-11-21
  • 打赏
  • 举报
回复
靠,贴错了:

原始:
int   main()
{
////////////////////////////
DWORD t = ::GetTickCount();
char ***m_convertTable = (char***)new unsigned char **[256];
for( t i = 0; i < 256; i++)
{
m_convertTable[i] = (char**)new unsigned char *[256];
for(int j = 0; j < 256; j++)
{
m_convertTable[i][j] = (char*)new unsigned char[256];
for(int k = 0; k < 256; k++)
{
m_convertTable[i][j][k] = 0.3 * i+0.6 * j+0.1* k;
}
}
}

char GrayGraphic[1024*768];
const int d = 1024*768;
for(int i = 0; i < d; i++)
{
unsigned char r = 7;
unsigned char g = 7;
unsigned char b = 7;
GrayGraphic[i] = m_convertTable[r][g][b];
}

cout << ::GetTickCount()-t<<endl;
system( "pause ");
return 0;
}


改进:

int   main()
{
////////////////////////////
DWORD t = ::GetTickCount();
int i,j,k;
char *m_convertTable = (char*)new unsigned char [256*256*256];
for(int n = 0; n < 256*256*256; n++)
{
i = n/256/256;
j = n/256;
k = n%256;
m_convertTable[n] = 0.3 * i+0.6 * j+0.1* k;
}

char GrayGraphic[1024*768];
const int d = 1024*768;
for(int i = 0; i < d; i++)
{
unsigned char r = 7;
unsigned char g = 7;
unsigned char b = 7;
GrayGraphic[i] = m_convertTable[r*256*256+g*256+b];
}
cout << ::GetTickCount()-t<<endl;
system( "pause ");
return 0;
}
loops 2007-11-21
  • 打赏
  • 举报
回复
你的m_convertTable瓶颈就是访问内存上面,相比访问寄存器而言,一次访问内存的周期很长。
你原来的m_convertTable至少需要访问3次内存的时间。
如果把访问内存减到1次,那么速度就可以提高三倍。你可以试试 五岭散人和我的方案。这对于画屏幕来说是很大的改进。
加载更多回复(9)

3,882

社区成员

发帖
与我相关
我的任务
社区描述
C/C++ 其它技术问题
社区管理员
  • 其它技术问题社区
加入社区
  • 近7日
  • 近30日
  • 至今
社区公告
暂无公告

试试用AI创作助手写篇文章吧