寻找信息来提高代码速度

时间:2011-08-26 22:28:26

标签: c# performance optimization

我有一些代码会以720p和24fps的速度从相机中传输视频。我试图在代码中捕获这个流,并最终通过将压缩的jpegs放在mjpeg等中来创建它的视频。我遇到的问题是这个整体代码不够快,无法以每帧24 fps或.04秒的速度创建。

使用

Stopwatch();

我发现循环的内部每个循环需要.000000000022秒。

循环外部每个循环需要.0000077秒。

我发现从开始到图像保存的整个功能每次运行运行.21秒。

从内部循环计算以完成图像:

.000000000022 x 640 = .000000001408 seconds
.000000001408 x 360 = .00000050688  seconds

从外部循环计算以完成图像:

.0000077 x 360 = .002772 seconds

如果我可以创建一个与我将要设置的时间相关的图像,但运行整个代码的代码需要.21秒才能完成所有代码

temp_byte1 = main_byte1;
temp_byte2 = main_byte2;

timer1.Reset();
timer1.Start();

Bitmap mybmp = new Bitmap(1280, 720);
BitmapData BPD = mybmp.LockBits(new Rectangle(0, 0, 1280, 720), ImageLockMode.WriteOnly, mybmp.PixelFormat);
IntPtr xptr = BPD.Scan0;
IntPtr yptr = BPD.Scan0;
yptr = new IntPtr( yptr.ToInt64() + (1280 * 720 * 2));
int bytes = Math.Abs(BPD.Stride);
byte[][] rgb = new byte[720][];
int Y1, Y2, Y3, Y4, Y5, Y6, Y7, Y8;
int U1, U2, V1, V2, U3, U4, V3, V4;
for (int one = 0; one < 360; one++)
{
    timer2.Reset();
    timer2.Start();
    rgb[one] = new byte[bytes];
    rgb[360 + one] = new byte[bytes];
    for (int two = 0; two < 640; two++)
    {
        timer3.Reset();
        timer3.Start();
        U1 = temp_byte1[one * 2560 + 4 * two + 0];
        Y1 = temp_byte1[one * 2560 + 4 * two + 1];
        V1 = temp_byte1[one * 2560 + 4 * two + 2];
        Y2 = temp_byte1[one * 2560 + 4 * two + 3];

        U2 = temp_byte2[one * 2560 + 4 * two + 0];
        Y3 = temp_byte2[one * 2560 + 4 * two + 1];
        V2 = temp_byte2[one * 2560 + 4 * two + 2];
        Y4 = temp_byte2[one * 2560 + 4 * two + 3];

        RGB_Conversion(Y1, U1, V1, two * 8 + 0, rgb[one]);
        RGB_Conversion(Y2, U1, V1, two * 8 + 4, rgb[one]);

        RGB_Conversion(Y3, U2, V2, two * 8 + 0, rgb[(360 + one)]);
        RGB_Conversion(Y4, U2, V2, two * 8 + 4, rgb[(360 + one)]);

        timer3.Stop();
        timer3_[two] = timer3.Elapsed;
    }
    Marshal.Copy(rgb[one], 0, xptr, 5120);
    xptr = new IntPtr(xptr.ToInt64() + 5120);
    Marshal.Copy(rgb[(360 + one)], 0, yptr, 5120);
    yptr = new IntPtr(yptr.ToInt64() + 5120);
    timer2.Stop();
    timer2_[one] = timer2.Elapsed;
}
mybmp.UnlockBits(BPD);
mybmp.Save(GetDateTimeString("IP Pictures") + ".jpg", ImageFormat.Jpeg);

代码工作,它将yuv422传入的字节数组转换为完整大小的jpeg,但无法理解为什么for循环的速度和整个代码之间存在这样的差异

我移动了

byte[][]rgb = new byte[720];  
rgb[x] = new byte[bytes]; 

到一个在程序启动时获取init的全局而不是每个函数调用/运行没有可测量的速度增加。

更新

RGB转换:接收YUV并将其转换为RGB并将其放入保存值

的全局数组中
public void RGB_Conversion(int Y, int U, int V, int MULT, byte[] rgb)
{

    int C,D,E;
    int R,G,B;

    // create the params for rgb conversion
    C = Y - 16;
    D = U - 128;
    E = V - 128;

    //R = clamp((298 x C + 409 x E + 128)>>8)
    //G = clamp((298 x C - 100 x D - 208 x E + 128)>>8)
    //B = clamp((298 x C + 516 x D + 128)>>8)

    R = (298 * C + 409 * E + 128)/256;
    G = (298 * C - 100 * D - 208 * E + 128)/256;
    B = (298 * C + 516 * D + 128)/256;

    if (R > 255)
        R = 255;
    if (R < 0)
        R = 0;
    if (G > 255)
        G = 255;
    if (G < 0)
        G = 0;
    if (B > 255)
        B = 255;
    if (B < 0)
        B = 0;

    rgb[MULT + 3] = 255;
    rgb[MULT + 0] = (byte)B;
    rgb[MULT + 1] = (byte)G;
    rgb[MULT + 2] = (byte)R;
    }

7 个答案:

答案 0 :(得分:3)

首先

您需要从循环内部删除开始/停止和秒表业务

在紧密的循环中重置秒表640x会使数字偏斜。更好地使用分析器或测量粗粒度性能。

此外,这些语句的存在可能会阻止编译器优化(循环平铺和循环展开看起来是非常好的候选者,但是JITter可能无法使用它们,因为寄存器被破坏以调用秒表函数。

数据结构:

我觉得你应该能够使用'扁平'数据结构,而不是在那里新建所有锯齿状数组。也就是说,我不知道你正在为它提供什么样的API,而且我没有对它进行过多的考虑。

我确实认为让RGB_Conversion'只是'返回RGB部分而不是让它写入数组可能会给编译器带来优化的优势。

其他想法:

  • 查看RGB_Conversion(其中/如何定义?)。也许你可以把它拉成内线。

  • 使用unchecked block阻止所有数组索引操作检查溢出

  • 考虑使用/不安全代码(here)来避免边界检查

答案 1 :(得分:3)

你可以做很多事情:

  1. 从外部循环中删除“新”分配。
  2. 预分配并固定所有缓冲区
  3. 摆脱Marshal.Copy并替换为不安全的dword副本或win32 rtlcopymemory
  4. 内联RGB_Conversion
  5. 不要在外部循环中调用新的IntPtr,而只是将指针递增到固定缓冲区。
  6. 我确信还有更多,但这就是我第一眼看到的。我认为你最好还是重构或重写整个例程,或者甚至用C ++ .NET DLL重写它,或者至少在当前版本中使用不安全的代码来避免.NET的所有漏洞。

答案 2 :(得分:2)

一,我确保你没有在调试器中运行它,否则优化完全关闭,并且插入了大量NOP操作码以给调试器锚点等等。

二,你正在写磁盘。如果它被缓冲,那么有时会很快,而如果写入触发刷新则非常慢。这可能不是CPU使用量在这里杀了你。你可以通过运行任务管理器并告诉我们你的cpu使用情况来确认吗?

如果您仍想将中间JPG写入磁盘,我建议您做的是设置两个线程,它们之间有一个线程安全的循环队列。线程1是您上面执行所有处理的代码;一旦完成,它会将BMP对象保存到队列中并立即移动到下一次迭代。线程2会将您的BMP对象从队列中读出并将它们写入磁盘。

我建议使用阻塞队列(或者使用计数信号量从队列中创建自己的阻塞队列)如果写入最终花费的时间超过帧。

第二,你有一台多核的机器吗?您可以进一步批量计算。下面是一个粗略示例,因为在采用这样的方法时你需要做很多考虑(涉及更多的锁定,找到一个好的读写器循环队列实现,处理无序处理,处理生成JPG的速度较大的抖动,导致整个流有更多延迟,但吞吐量更高。)

线程A:从视频源读取YUV帧作为数组,将序列号分配给数组,将数组+ sn填充到队列A中。

线程B,C,D:从队列A读取对象,计算BMP对象,将具有相同序列号的BMP填充到队列B中。队列B将以随机顺序具有BMP对象,例如0,5,6,2 ,3,9,4,...因为你有多个线程写入它,但由于你有标有序列号的线程,你可以稍后重新排序。

线程E:从队列B读取,重新排序帧,写入磁盘。

所有队列当然都需要是线程安全的。

更进一步,为什么不摆脱中间JPG文件?将这些内容写入磁盘仅仅是为了在其他程序中或之后的某个步骤中读取它们需要做很多额外的工作,这可能是一个巨大的性能瓶颈。为什么不在内存中完全生成电影流?

其他性能注意事项:您是否以“正确”方式阅读阵列?这是cpu缓存问题。 简单的回答:尝试倒转哪个for-loop是内部的,看看你是否获得了更好的性能。

答案很长:如果按线性顺序读取字节,则CPU缓存数据会更好。 让我们举个例子。你有一个矩形的1000x1000阵列,它在行内存中线性排列 - 行0是前1000个字节,第一行是下一个,等等。如果你按行逐列读取数据,那么你'按此顺序读取字节: 0,1000,2000,....,999000,1,1001,1001,...,999001等。 CPU不喜欢这样,因为每次读取每次都在不同的页面中,这意味着更多的缓存行未命中。你可以在内存中进行糖果剥离,而不是线性阅读。

答案 3 :(得分:1)

这里有一些想法:

1)确保没有内存分配。否则你将获得垃圾收集,你将丢弃数据。我认为你的其余代码是干净的,但我严重怀疑save jpeg例程。您可能必须将代码的实时部分移动到另一种语言。

2)线程。我会把它变成一个线程。提供它可以填充的缓冲池,压缩和保存在另一个线程中执行。这允许一些余量。

3)RGB转换的输入实际上是3个字节。这意味着它有1600万个可能的输入值,我认为它从它们返回一个uint32。预先计算这只是64mb。这将从代码中最关键时间的部分中删除大部分代码,并删除边界检查的6个分支。

答案 4 :(得分:0)

假设RGB_Conversion非常快,我认为这里的主要瓶颈是保存jpg。如果是这样,请尝试寻找一个不同的(更快的)jpeg库。此外,请务必测量创建新位图(1280,720)所需的时间,并考虑在帧之间重新使用位图。

答案 5 :(得分:0)

您是否考虑使用任务并行库和管道模式来并行化此代码。您可以对图像处理进行分层,以便将图像N的磁盘写入与图像N + 1的计算并行运行。这可能会为您提供一些加速,但基本上您的问题似乎是磁盘绑定。

这里有一个使用TPL并行处理图像的例子,包括示例应用和权衡的讨论。

http://msdn.microsoft.com/en-us/library/ff963548.aspx(讨论)

http://parallelpatterns.codeplex.com/releases/view/50473(代码)

我也同意有关使用分析器来衡量这一点的评论。它可能更准确,不会影响结果。

顺便说一句,我已经用C#和C ++编写了这个例子,而C ++版的速度要快得多,主要是因为你可以直接访问内存。如果你可以将你的字节操作变成更大的字符,那么这可能会给你带来很大的改进。

答案 6 :(得分:0)

正如本杰克逊所指出的,色彩空间转换是非常不必要的。快速浏览一下,我没有看到在MSDN文档中保存YUV图像数据的方法,但是libjpeg库确实支持从YUV(YCbCr)数据开始,并且http://bitmiracle.com/libjpeg/处有一个.NET版本

由于您的性能要求,http://www.libjpeg-turbo.org/的libjpeg-turbo库可能是更好的选择,尽管使用C#代码中的基于C的DLL可能很麻烦。