寻找信息,以提高代码速度

本文关键字:代码 速度 高代码 信息 寻找 | 更新日期: 2023-09-27 18:06:51

我有一些代码,将流视频从一个相机在720p和24fps。我试图在代码中捕获这个流,并最终通过将压缩的jpeg扔到mjpeg或类似的文件中来创建它的视频。我遇到的问题是,这个整体代码不够快,无法以24 fps或每张图像0.04秒的速度创建东西。

使用

Stopwatch();

我发现内部for循环每个循环需要0.0000000022秒。

外部for循环需要0.0000077秒来完成每个循环。

和我发现整个函数从开始到图像保存每次运行运行0.21秒。

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

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

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

.0000077 x 360 = .002772 seconds

如果我可以创建一个与这些时间相关的图像,我将被设置,但是运行整个代码的代码需要0.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]; 

到一个全局变量,在程序启动时初始化,而不是每次调用/运行函数,速度没有明显的提高。

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;
    }

寻找信息,以提高代码速度

first

您需要从循环内部删除Start/Stop和stopwatch业务

复位秒表640x在一个紧密的循环会使数字倾斜。最好使用分析器或测量粗粒度的性能。

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

数据结构:

我有一种感觉,你应该能够使用"平坦"的数据结构,而不是在那里新建所有参差不齐的数组。也就是说,我不知道你给它输入了什么API,我也没有过多地关注它。

我确实觉得让RGB_Conversion '只是'返回RGB部分,而不是让它写进数组可能真的给编译器一个优势来优化事情。

其他想法:

  • 查看RGB_Conversion(在哪里/如何定义?)。

  • 使用unchecked块来防止所有数组索引操作来检查溢出

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

你可以做很多事情:

  1. 从外部循环中删除'new'分配。
  2. 预分配和引脚所有缓冲区
  3. 摆脱元帅。复制并替换为不安全的dword副本或win32 rtlcopymemory
  4. 内联RGB_Conversion
  5. 不要在外部循环中调用new IntPtr,而只是增加指向固定缓冲区的指针。

我肯定还有更多,但那是我第一眼看到的。我认为你最好重构或重写整个例程,或者甚至用c++重写它。.NET DLL或至少在当前版本中使用不安全代码来避免。NET的所有错误。

第一,我要确保你没有在调试器中运行这个,否则优化是完全关闭的,并且插入了许多NOP操作码来为大括号等提供调试器锚点

2,你在写磁盘。如果有缓冲,有时会很快,但如果写入触发刷新,有时会非常非常慢。在这里,可能不是CPU使用导致了您的死亡。你能通过运行任务管理器并告诉我们你的cpu使用情况来确认吗?

如果你仍然想要将中间的jpg文件写入磁盘,我建议你在两个线程之间设置一个线程安全的循环队列。线程1是上面的代码,它完成所有的处理;完成后,它将BMP对象保存到队列中,并立即移动到下一个迭代。线程2将从队列中读取BMP对象并将其写入磁盘。

我建议使用阻塞队列(或从queue中创建自己的队列,带有计数信号量),如果写最终花费的时间比帧长。

第二,你有多核的机器吗?你可以进一步批量计算。下面是一个粗略的示例,因为在采用这种方法时,您需要考虑很多因素(涉及更多的锁定,找到一个好的读写器循环队列实现,处理乱序处理,处理生成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循环是内部的,看看你是否能得到更好的性能。

长话短说:如果你按线性顺序读取字节,CPU的数据缓存工作得更好。让我们举个例子。你有一个1000x1000的矩形数组,它在内存中按行线性排列——第0行是前1000个字节,第1行是下一个字节,以此类推。如果你先按列读取,然后按行读取,那么你就会按照这个顺序读取字节:0,1000,2000, ...., 999000, 1,1001, 2001,…、999001等。cpu不喜欢这样,因为每次读取都在不同的页面上,这意味着更多的缓存线丢失。你会像糖果一样在记忆中穿行,而不是线性地阅读。

一些想法:

1)确保没有内存分配。否则你会得到垃圾收集,你会丢失数据。我认为剩下的代码是干净的,但我严重怀疑保存jpeg例程是。您可能需要将代码的实时部分移到另一种语言中。

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

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

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

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

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

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

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

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

顺便说一下,我用c#和c++写了这个例子,c++的速度要快得多,很大程度上是因为你可以直接访问内存。如果您可以将字节操作凝聚成更大的内容,这可能会给您带来显著的改进。

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

有一个。net版本。

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