【问题标题】:Looking for info to improve code speed寻找信息以提高代码速度
【发布时间】:2011-08-26 22:28:26
【问题描述】:

我有一些代码可以以 720p 和 24fps 的速度从摄像机流式传输视频。我试图在代码中捕获这个流,并最终通过将压缩的 jpeg 组合成 mjpeg 等来创建它的视频。我遇到的问题是这个整体代码不够快,无法以 24 fps 或 0.04 秒的速度创建每个图像。

使用

Stopwatch();

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

外部 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]; 

到在程序启动时获取 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;
    }

【问题讨论】:

  • 您是否尝试过单独对循环的外部部分进行计时?一个电话可能会占用大部分时间。
  • 是的。我一直在慢慢地这样做,并挑选出减缓它的因素,要么将其移除,要么在不会影响这次的更全球性的空间中进行。仍在尝试这样做,但让其他人也看看会有所帮助,因为我自己越来越沮丧。一次代码需要 x 秒才能完成,下一次,相同的东西需要 100x 秒 =\
  • 我认为这可能是在最后调用Save - 磁盘访问。
  • 还可以查看编译器使用反射器或 ilspy 生成的内容;有时您认为是优化,编译器会忽略并按其方式进行操作,即所有这些(一 * 2560 + 4 * 二)甚至可能变成一个变量。我还发现编组很昂贵,尝试使用 BitConvertor 处理 ints 等简单类型。
  • 您是否意识到 JPEG 通常是 YUV 并且只会将您的色彩空间转换回来?

标签: c# performance optimization


【解决方案1】:

首先

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

在紧密循环中重置秒表 640x 会使数据出现偏差。最好使用分析器或测量粗粒度性能。

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

数据结构:

我有一种感觉,您应该能够使用“平面”数据结构,而不是在那里更新所有锯齿状数组。就是说,我不知道您将其输入到什么 API,而且我也没有很关注它。

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

其他想法:

  • 查看RGB_Conversion(它在哪里/如何定义?)。也许你可以将它内联。

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

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

【讨论】:

  • 删除了除顶级计时器1之外的所有内容。也使位图全局时间从 0.22 下降到 0.11
  • "unchecked" 是 C# 中的默认设置,但需要在项目设置中进行检查。请注意,unchecked 不会删除数组边界检查。
  • 好点,将 ref 添加到“不安全”标志以避免边界检查
【解决方案2】:

你可以做很多事情:

  1. 从外部循环中删除“新”分配。
  2. 预分配和固定所有缓冲区
  3. 摆脱 Marshal.Copy 并替换为不安全的 dword 副本或 win32 rtlcopymemory
  4. 内嵌 RGB_Conversion
  5. 不要在外部循环中调用 new IntPtr,而只是增加一个指向固定缓冲区的指针。

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

【讨论】:

    【解决方案3】:

    一,我会确保你没有在调试器中运行它,否则优化会完全关闭,并且会插入大量 NOP 操作码来为调试器提供大括号等的锚点。

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

    如果您仍想将中间 JPG 写入磁盘,我建议您设置两个线程,在它们之间设置一个线程安全的循环队列。线程一是您上面的代码,它执行所有处理;一旦完成,它会将 BMP 对象保存到队列中并立即移动到下一次迭代。线程二将从队列中读取您的 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 循环是内部的,看看你是否获得了更好的性能。

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

    【讨论】:

    • 在发布时运行它,它下降到平均 0.07 秒,使用原始 bmp 保存代码每秒大约 10~12 jpegs
    • 确保你在优化的情况下运行它(默认版本有它,但你可以在调试中打开它),并在调试器之外完全运行它 - 如果你在调试器中运行它,它仍然会在调试代码或不良jitting(无内联等)的情况下运行。
    【解决方案4】:

    这里有一些想法:

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

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

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

    【讨论】:

      【解决方案5】:

      假设 RGB_Conversion 真的很快,我预计这里的主要瓶颈是保存 jpg。如果是这样,请尝试寻找不同(更快)的 jpeg 库。另外,请务必测量创建新 Bitmap(1280, 720) 所需的时间,并考虑在帧之间重新使用 Bitmap。

      【讨论】:

      • 将位图推送到全局,每次都重用它
      • 注释掉保存代码会将时间从 0.10~.18 降到 0.07~
      【解决方案6】:

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

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

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

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

      我也同意 cmets 关于使用分析器进行测量的观点。它可能更准确,不会影响结果。

      顺便说一句,我用 C# 和 C++ 编写了这个示例,而 C++ 的示例要快得多,这主要是因为您可以直接访问内存。如果你可以将你的字节操作合并成更大的东西,这可能会给你带来显着的改进。

      【讨论】:

        【解决方案7】:

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

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

        【讨论】:

        • 我试过了,我以为我已经成功了,但是我的颜色都歪了,没有太多的例子可以检查
        猜你喜欢
        • 2020-11-19
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2021-09-18
        • 2021-07-30
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多