我意识到这是一个愚蠢的问题,因为缺乏一个更好的术语。我只是在寻找任何有关提高此代码效率的外部想法,因为它会严重阻碍系统(它必须执行此功能)并且我的想法很少。
它正在做什么加载两个图像容器(imgRGB用于全彩色img和imgBW用于b& w图像)逐个像素的图像存储在“unsigned char * pImage”中。
imgRGB和imgBW都是根据需要访问单个像素的容器。
// input is in the form of an unsigned char
// unsigned char *pImage
for (int y=0; y < 640; y++) {
for (int x=0; x < 480; x++) {
imgRGB[y][x].blue = *pImage;
pImage++;
imgRGB[y][x].green = *pImage;
imgBW[y][x] = *pImage;
pImage++;
imgRGB[y][x].red = *pImage;
pImage++;
}
}
就像我说的那样,我只是在寻找有关更好的内存管理和/或复制的新输入和想法。有时候我会看到自己的代码,以至于我得到隧道视觉......有点心理障碍。如果有人想要/需要更多信息,请务必告诉我。
答案 0 :(得分:7)
答案 1 :(得分:4)
我认为数组访问(它们是真正的数组访问还是operator []?)会杀了你。每一个代表一个乘法。
基本上,你想要这样的东西:
for (int y=0; y < height; y++) {
unsigned char *destBgr = imgRgb.GetScanline(y); // inline methods are better
unsigned char *destBW = imgBW.GetScanline(y);
for (int x=0; x < width; x++) {
*destBgr++ = *pImage++;
*destBW++ = *destBgr++ = *pImage++; // do this in one shot - don't double deref
*destBgr++ = *pImage++;
}
}
这将为每条扫描线进行两次乘法运算。您的代码每个PIXEL执行4次乘法。
答案 2 :(得分:4)
在这种情况下我喜欢做的是进入调试器并逐步完成反汇编以查看它实际上在做什么(或让编译器生成汇编列表)。这可以为您提供很多关于效率低下的线索。他们往往不在你想的地方!
通过实施上述Assaf和David Lee建议的更改,您可以获得前后指令计数。这真的有助于我优化紧密的内环。
答案 3 :(得分:3)
你可以使用下标运算符[] []优化掉你正在做的一些指针运算,而是使用迭代器(即推进指针)。
答案 4 :(得分:3)
内存带宽是你的瓶颈。将所有数据传输到系统存储器和从系统存储器传输所需的理论最小时间。我写了一个小测试来比较OP的版本和一些简单的汇编程序,看看编译器有多好。我正在使用具有默认释放模式设置的VS2005。这是代码:
#include <windows.h>
#include <iostream>
using namespace std;
const int
c_width = 640,
c_height = 480;
typedef struct _RGBData
{
unsigned char
r,
g,
b;
// I'm assuming there's no padding byte here
} RGBData;
// similar to the code given
void SimpleTest
(
unsigned char *src,
RGBData *rgb,
unsigned char *bw
)
{
for (int y = 0 ; y < c_height ; ++y)
{
for (int x = 0 ; x < c_width ; ++x)
{
rgb [x + y * c_width].b = *src;
src++;
rgb [x + y * c_width].g = *src;
bw [x + y * c_width] = *src;
src++;
rgb [x + y * c_width].r = *src;
src++;
}
}
}
// the assembler version
void ASM
(
unsigned char *src,
RGBData *rgb,
unsigned char *bw
)
{
const int
count = 3 * c_width * c_height / 12;
_asm
{
push ebp
mov esi,src
mov edi,bw
mov ecx,count
mov ebp,rgb
l1:
mov eax,[esi]
mov ebx,[esi+4]
mov edx,[esi+8]
mov [ebp],eax
shl eax,16
mov [ebp+4],ebx
rol ebx,16
mov [ebp+8],edx
shr edx,24
and eax,0xff000000
and ebx,0x00ffff00
and edx,0x000000ff
or eax,ebx
or eax,edx
add esi,12
bswap eax
add ebp,12
stosd
loop l1
pop ebp
}
}
// timing framework
LONGLONG TimeFunction
(
void (*function) (unsigned char *src, RGBData *rgb, unsigned char *bw),
char *description,
unsigned char *src,
RGBData *rgb,
unsigned char *bw
)
{
LARGE_INTEGER
start,
end;
cout << "Testing '" << description << "'...";
memset (rgb, 0, sizeof *rgb * c_width * c_height);
memset (bw, 0, c_width * c_height);
QueryPerformanceCounter (&start);
function (src, rgb, bw);
QueryPerformanceCounter (&end);
bool
ok = true;
unsigned char
*bw_check = bw,
i = 0;
RGBData
*rgb_check = rgb;
for (int count = 0 ; count < c_width * c_height ; ++count)
{
if (bw_check [count] != i || rgb_check [count].r != i || rgb_check [count].g != i || rgb_check [count].b != i)
{
ok = false;
break;
}
++i;
}
cout << (end.QuadPart - start.QuadPart) << (ok ? " OK" : " Failed") << endl;
return end.QuadPart - start.QuadPart;
}
int main
(
int argc,
char *argv []
)
{
unsigned char
*source_data = new unsigned char [c_width * c_height * 3];
RGBData
*rgb = new RGBData [c_width * c_height];
unsigned char
*bw = new unsigned char [c_width * c_height];
int
v = 0;
for (unsigned char *dest = source_data ; dest < &source_data [c_width * c_height * 3] ; ++dest)
{
*dest = v++ / 3;
}
LONGLONG
totals [2] = {0, 0};
for (int i = 0 ; i < 10 ; ++i)
{
cout << "Iteration: " << i << endl;
totals [0] += TimeFunction (SimpleTest, "Initial Copy", source_data, rgb, bw);
totals [1] += TimeFunction ( ASM, " ASM Copy", source_data, rgb, bw);
}
LARGE_INTEGER
freq;
QueryPerformanceFrequency (&freq);
freq.QuadPart /= 100000;
cout << totals [0] / freq.QuadPart << "ns" << endl;
cout << totals [1] / freq.QuadPart << "ns" << endl;
delete [] bw;
delete [] rgb;
delete [] source_data;
return 0;
}
C和汇编程序之间的比例约为2.5:1,即C是汇编程序版本的2.5倍。
我刚刚注意到原始数据是以BGR顺序排列的。如果副本交换了B和R组件,那么它确实使汇编代码更复杂一些。但它也会使C代码更复杂。
理想情况下,您需要确定理论上的最短时间,并将其与您实际获得的时间进行比较。为此,您需要知道内存频率和内存类型以及CPU MMU的工作情况。
答案 5 :(得分:2)
您可以尝试使用简单的强制转换来获取RGB数据,然后重新计算灰度数据:
#pragma pack(1)
typedef unsigned char bw_t;
typedef struct {
unsigned char blue;
unsigned char green;
unsigned char red;
} rgb_t;
#pragma pack(pop)
rgb_t *imageRGB = (rgb_t*)pImage;
bw_t *imageBW = (bw_t*)calloc(640*480, sizeof(bw_t));
// RGB(X,Y) = imageRGB[Y*480 + X]
// BW(X,Y) = imageBW[Y*480 + X]
for (int y = 0; y < 640; ++y)
{
// try and pull some larger number of bytes from pImage (24 is arbitrary)
// 24 / sizeof(rgb_t) = 8
for (int x = 0; x < 480; x += 24)
{
imageBW[y*480 + x ] = GRAYSCALE(imageRGB[y*480 + x ]);
imageBW[y*480 + x + 1] = GRAYSCALE(imageRGB[y*480 + x + 1]);
imageBW[y*480 + x + 2] = GRAYSCALE(imageRGB[y*480 + x + 2]);
imageBW[y*480 + x + 3] = GRAYSCALE(imageRGB[y*480 + x + 3]);
imageBW[y*480 + x + 4] = GRAYSCALE(imageRGB[y*480 + x + 4]);
imageBW[y*480 + x + 5] = GRAYSCALE(imageRGB[y*480 + x + 5]);
imageBW[y*480 + x + 6] = GRAYSCALE(imageRGB[y*480 + x + 6]);
imageBW[y*480 + x + 7] = GRAYSCALE(imageRGB[y*480 + x + 7]);
}
}
答案 6 :(得分:2)
您可以采取几个步骤。结果在这个答案结束时。
首先,使用指针。
const unsigned char *pImage;
RGB *rgbOut = imgRGB;
unsigned char *bwOut = imgBW;
for (int y=0; y < 640; ++y) {
for (int x=0; x < 480; ++x) {
rgbOut->blue = *pImage;
++pImage;
unsigned char tmp = *pImage; // Save to reduce amount of reads.
rgbOut->green = tmp;
*bwOut = tmp;
++pImage;
rgbOut->red = *pImage;
++pImage;
++rgbOut;
++bwOut;
}
}
如果imgRGB
和imgBW
被声明为:
unsigned char imgBW[480][640];
RGB imgRGB[480][640];
您可以合并两个循环:
const unsigned char *pImage;
RGB *rgbOut = imgRGB;
unsigned char *bwOut = imgBW;
for (int i=0; i < 640 * 480; ++i) {
rgbOut->blue = *pImage;
++pImage;
unsigned char tmp = *pImage; // Save to reduce amount of reads.
rgbOut->green = tmp;
*bwOut = tmp;
++pImage;
rgbOut->red = *pImage;
++pImage;
++rgbOut;
++bwOut;
}
您可以利用单词读取比四个char读取更快的事实。我们将为此使用辅助宏。请注意,此示例假定使用little-endian目标系统。
const unsigned char *pImage;
RGB *rgbOut = imgRGB;
unsigned char *bwOut = imgBW;
const uint32_t *curPixelGroup = pImage;
for (int i=0; i < 640 * 480; ++i) {
uint64_t pixels = 0;
#define WRITE_PIXEL \
rgbOut->blue = pixels; \
pixels >>= 8; \
\
rgbOut->green = pixels; \
*bwOut = pixels; \
pixels >>= 8; \
\
rgbOut->red = pixels; \
pixels >>= 8; \
\
++rgbOut; \
++bwOut;
#define READ_PIXEL(shift) \
pixels |= (*curPixelGroup++) << (shift * 8);
READ_PIXEL(0); WRITE_PIXEL;
READ_PIXEL(1); WRITE_PIXEL;
READ_PIXEL(2); WRITE_PIXEL;
READ_PIXEL(3); WRITE_PIXEL;
/* Remaining */ WRITE_PIXEL;
#undef COPY_PIXELS
}
(您的编译器可能会优化第一个or
中的冗余READ_PIXEL
操作。它还会优化移位,删除多余的<< 0
。)
如果RGB
的结构如此:
struct RGB {
unsigned char blue, green, red;
};
您可以进一步优化,直接复制到结构,而不是通过其成员(red
,green
,blue
)。这可以使用匿名结构(或转换,但这使得代码更麻烦,可能更容易出错)来完成。 (同样,这取决于小端系统等等):
union RGB {
struct {
unsigned char blue, green, red;
};
uint32_t rgb:24; // Make sure it's a bitfield, otherwise the union will strech and ruin the ++ operator.
};
const unsigned char *pImage;
RGB *rgbOut = imgRGB;
unsigned char *bwOut = imgBW;
const uint32_t *curPixelGroup = pImage;
for (int i=0; i < 640 * 480; ++i) {
uint64_t pixels = 0;
#define WRITE_PIXEL \
rgbOut->rgb = pixels; \
pixels >>= 8; \
\
*bwOut = pixels; \
pixels >>= 16; \
\
++rgbOut; \
++bwOut;
#define READ_PIXEL(shift) \
pixels |= (*curPixelGroup++) << (shift * 8);
READ_PIXEL(0); WRITE_PIXEL;
READ_PIXEL(1); WRITE_PIXEL;
READ_PIXEL(2); WRITE_PIXEL;
READ_PIXEL(3); WRITE_PIXEL;
/* Remaining */ WRITE_PIXEL;
#undef COPY_PIXELS
}
您可以像阅读时一样优化写入像素(以单词写入而不是24位)。事实上,这是一个非常好的主意,并将成为优化的下一步。但是,编码太累了。 =]
当然,你可以用汇编语言编写例程。然而,这使它不像现在那样便携。
答案 7 :(得分:1)
我现在假设以下情况,所以如果我的假设是错误的,请告诉我:
a)imgRGB是
类型的结构
struct ImgRGB
{
unsigned char blue;
unsigned char green;
unsigned char red;
};
或至少类似的东西。
b)imgBW看起来像这样:
struct ImgBW
{
unsigned char BW;
};
c)代码是单线程的
假设上述情况,我发现您的代码有几个问题:
for (int y=0; y blue = *pImage;
...
}
}
总而言之,您只需要使您的循环适合您的底层硬件,因此对于特定的优化技术,您可能需要了解您的硬件运行良好以及它的功能是什么。
答案 8 :(得分:1)
确保将pImage,imgRGB和imgBW标记为__restrict。 使用SSE并一次执行16个字节。
实际上你从那里做的事情看起来你可以使用一个简单的memcpy()将pImage复制到imgRGB(因为imgRGB是行主格式,显然与pImage的顺序相同)。您可以通过使用一系列SSE swizzle和store ops填写imgBW来打包绿色值,但这可能很麻烦,因为您需要一次处理(3 * 16 =)48个字节。
启动时,您确定pImage和输出数组都处于dcache状态吗?尝试使用预取提示提前获取128个字节并进行测量以查看是否可以改善效果。
编辑如果您不在x86上,请将“SSE”替换为适合您硬件的SIMD指令集。 (那是VMX,Altivec,SPU,VLIW,HLSL等)。
答案 9 :(得分:0)
如果可能的话,将其修改为更高级别,然后将位或指令修复!
您可以将B&amp; W图像类专门用于引用彩色图像类的绿色通道(从而为每个像素保存一份副本)。如果你总是成对创建它们,你可能根本不需要天真的imgBW
类。
通过关注如何在imgRGB
中存储数据,您可以从输入数据中一次复制三元组。更好的是,您可以复制整个内容,甚至只是存储引用(这也使之前的建议变得容易)。
如果你不控制这里所有的实现,你可能会被卡住,然后:
答案 10 :(得分:0)
您似乎将每个像素定义为某种结构或对象。使用基本类型(比方说,int)可能会更快。正如其他人所提到的,编译器很可能使用指针增量来优化数组访问。如果编译不能为您执行此操作,则可以在使用array [] []时自行执行此操作以避免乘法运算。
由于每个像素只需要3个字节,因此可以将一个像素打包成一个int。通过这样做,您可以一次复制3个字节而不是逐个字节。唯一棘手的事情是当你想要读取像素的各个颜色分量时,你需要一些掩码和移位。这可能会比使用int节省的开销更多。
或者您可以分别为3个颜色组件使用3个int数组。但是,你需要更多的存储空间。
答案 11 :(得分:0)
这是一个非常小的,非常简单的优化:
您反复提到imageRGB [y] [x],并且可能需要在每一步重新计算。
相反,计算一次,看看是否有所改善:
Pixel* apixel;
for (int y=0; y < 640; y++) {
for (int x=0; x < 480; x++) {
apixel = &imgRGB[y][x];
apixel->blue = *pImage;
pImage++;
apixel->green = *pImage;
imgBW[y][x] = *pImage;
pImage++;
apixel->red = *pImage;
pImage++;
}
}
答案 12 :(得分:0)
如果pImage已完全在内存中,为什么需要按摩数据?我的意思是,如果它已经是伪RGB格式,为什么你不能只编写一些内联例程/宏,可以按需吐出值而不是复制它?
如果重新排列像素数据对以后的操作很重要,请考虑块操作和/或缓存线优化。