我有代码从相机获取帧然后将其保存到磁盘。代码的结构是:多个线程malloc并将它们的帧复制到新的内存中,将内存排入队列。最后,另一个线程从队列中删除帧并将它们(使用ffmpeg API,原始视频无压缩)写入其文件(实际上我正在使用自己的内存池,因此只在需要更多缓冲区时调用malloc)。我可以同时打开多达8个文件/摄像头。
问题是,在前45秒内一切正常:队列中的帧数永远不会超过一帧。但是在我的队列被备份之后,处理只需要几毫秒的时间就会导致ram使用量的增加,因为我无法快速保存帧,因此我必须使用malloc更多的内存来存储它们。
我有一个8核,16GB RAM Windows 7 64位计算机(NTFS,第二个磁盘驱动器中有大量可用空间)。该磁盘应该能够写入高达6Gbits / sec。为了及时保存我的数据,我需要能够以50 MB /秒的速度写入数据。我使用“PassMark PerformanceTest”测试了磁盘速度,我有8个线程同时写文件,就像ffmpeg保存文件(同步,未缓存的I / O)一样,它能够达到100MB /秒。那么为什么我的写作不能实现呢?
以下是ffmpeg写入处理监视器日志的方式:
Time of Day Operation File# Result Detail 2:30:32.8759350 PM WriteFile 8 SUCCESS Offset: 749,535,120, Length: 32,768 2:30:32.8759539 PM WriteFile 8 SUCCESS Offset: 749,567,888, Length: 32,768 2:30:32.8759749 PM WriteFile 8 SUCCESS Offset: 749,600,656, Length: 32,768 2:30:32.8759939 PM WriteFile 8 SUCCESS Offset: 749,633,424, Length: 32,768 2:30:32.8760314 PM WriteFile 8 SUCCESS Offset: 749,666,192, Length: 32,768 2:30:32.8760557 PM WriteFile 8 SUCCESS Offset: 749,698,960, Length: 32,768 2:30:32.8760866 PM WriteFile 8 SUCCESS Offset: 749,731,728, Length: 32,768 2:30:32.8761259 PM WriteFile 8 SUCCESS Offset: 749,764,496, Length: 32,768 2:30:32.8761452 PM WriteFile 8 SUCCESS Offset: 749,797,264, Length: 32,768 2:30:32.8761629 PM WriteFile 8 SUCCESS Offset: 749,830,032, Length: 32,768 2:30:32.8761803 PM WriteFile 8 SUCCESS Offset: 749,862,800, Length: 32,768 2:30:32.8761977 PM WriteFile 8 SUCCESS Offset: 749,895,568, Length: 32,768 2:30:32.8762235 PM WriteFile 8 SUCCESS Offset: 749,928,336, Length: 32,768, Priority: Normal 2:30:32.8762973 PM WriteFile 8 SUCCESS Offset: 749,961,104, Length: 32,768 2:30:32.8763160 PM WriteFile 8 SUCCESS Offset: 749,993,872, Length: 32,768 2:30:32.8763352 PM WriteFile 8 SUCCESS Offset: 750,026,640, Length: 32,768 2:30:32.8763502 PM WriteFile 8 SUCCESS Offset: 750,059,408, Length: 32,768 2:30:32.8763649 PM WriteFile 8 SUCCESS Offset: 750,092,176, Length: 32,768 2:30:32.8763790 PM WriteFile 8 SUCCESS Offset: 750,124,944, Length: 32,768 2:30:32.8763955 PM WriteFile 8 SUCCESS Offset: 750,157,712, Length: 32,768 2:30:32.8764072 PM WriteFile 8 SUCCESS Offset: 750,190,480, Length: 4,104 2:30:32.8848241 PM WriteFile 4 SUCCESS Offset: 750,194,584, Length: 32,768 2:30:32.8848481 PM WriteFile 4 SUCCESS Offset: 750,227,352, Length: 32,768 2:30:32.8848749 PM ReadFile 4 END OF FILE Offset: 750,256,128, Length: 32,768, I/O Flags: Non-cached, Paging I/O, Synchronous Paging I/O, Priority: Normal 2:30:32.8848989 PM WriteFile 4 SUCCESS Offset: 750,260,120, Length: 32,768 2:30:32.8849157 PM WriteFile 4 SUCCESS Offset: 750,292,888, Length: 32,768 2:30:32.8849319 PM WriteFile 4 SUCCESS Offset: 750,325,656, Length: 32,768 2:30:32.8849475 PM WriteFile 4 SUCCESS Offset: 750,358,424, Length: 32,768 2:30:32.8849637 PM WriteFile 4 SUCCESS Offset: 750,391,192, Length: 32,768 2:30:32.8849880 PM WriteFile 4 SUCCESS Offset: 750,423,960, Length: 32,768, Priority: Normal 2:30:32.8850400 PM WriteFile 4 SUCCESS Offset: 750,456,728, Length: 32,768 2:30:32.8850727 PM WriteFile 4 SUCCESS Offset: 750,489,496, Length: 32,768, Priority: Normal
这看起来非常有效,但是,从DiskMon实际的磁盘写入看起来非常碎片来回写入,这可能解释了这种慢速。根据此数据(~5MB / s)查看写入速度图表。
TIme Write duration Sector Length MB/sec 95.6 0.00208855 1490439632 896 0.409131784 95.6 0.00208855 1488197000 128 0.058447398 95.6 0.00009537 1482323640 128 1.279965529 95.6 0.00009537 1482336312 768 7.679793174 95.6 0.00009537 1482343992 384 3.839896587 95.6 0.00009537 1482350648 768 7.679793174 95.6 0.00039101 1489278984 1152 2.809730729 95.6 0.00039101 1489393672 896 2.185346123 95.6 0.0001812 1482349368 256 1.347354443 95.6 0.0001812 1482358328 896 4.715740549 95.6 0.0001812 1482370616 640 3.368386107 95.6 0.0001812 1482378040 256 1.347354443 95.6 0.00208855 1488197128 384 0.175342193 95.6 0.00208855 1488202512 640 0.292236989 95.6 0.00208855 1488210320 1024 0.467579182 95.6 0.00009537 1482351416 256 2.559931058 95.6 0.00009537 1482360120 896 8.959758703 95.6 0.00009537 1482371896 640 6.399827645 95.6 0.00009537 1482380088 256 2.559931058 95.7 0.00039101 1489394568 1152 2.809730729 95.7 0.00039101 1489396744 352 0.858528834 95.7 0.00039101 1489507944 544 1.326817289 95.7 0.0001812 1482378296 768 4.042063328 95.7 0.0001812 1482392120 768 4.042063328 95.7 0.0001812 1482400568 512 2.694708885 95.7 0.00208855 1488224144 768 0.350684386 95.7 0.00208855 1488232208 384 0.175342193
我非常有信心这不是我的代码,因为我计算了所有内容,例如enqueing需要一些我们建议线程不会卡在等待彼此。它必须是磁盘写入。所以问题是如何改进我的磁盘写入以及如何分析实际磁盘写入(请记住,我依靠FFmpeg dll来保存,因此我无法直接访问低级写入功能)。如果我无法弄清楚,我会将所有帧转储到一个顺序二进制文件中(这应该会提高I / O速度),然后在处理后将其拆分为视频文件。
我不知道我的磁盘I / O缓存多少(CacheSet只显示磁盘C缓存大小),但是性能监视器中的以下图像在视频的0和45秒处(在我的队列开始之前)堆积起来对我来说很奇怪。基本上,修改后的设置和备用设置从非常小的增长到这个大的值。这是缓存的数据吗?是否有可能只有45秒的数据才开始写入磁盘,所以突然一切都变慢了? (仅供参考,LabVIEW是加载我的DLL的程序。)
我会感激任何帮助 微米。
答案 0 :(得分:3)
使用CreateFile
,您似乎想要其中一个或两个参数:
http://msdn.microsoft.com/en-us/library/cc644950(v=vs.85).aspx
当操作系统开始将数据推送到磁盘时,会发生延迟性能损失。
6Gb / s是SATA 2总线的性能,而不是连接的实际设备或下面的物理盘片或闪存柱。
AV系统的一个常见问题是不断写入大量数据可能会被磁盘开销任务定期中断。过去有一些特殊的AV磁盘你可以购买但不能这样做,现在你可以购买具有特殊高吞吐量性能固件的磁盘,用于安全视频录制。
http://seagate.custkb.com/seagate/crm/selfservice/search.jsp?DocId=210671&NewLang=en
答案 1 :(得分:2)
问题在于重复的malloc
和free
会给系统带来负担。我建议创建一个缓冲池,即在初始化阶段分配N个缓冲区并重用它们而不是mallocing和释放内存。由于您已经提到了ffmpeg,为了举例说明多媒体,在gstreamer中,缓冲区管理以buffer-pools的形式发生,并且在gstreamer管道中,缓冲区通常从缓冲池中获取并传递。大多数多媒体系统都是这样做的。
关于:
The problem is that for the first 45 sec everything works fine: there's never more than one frame on queue. But after that my queue gets backed up, processing takes just a few ms longer resulting in increased ram usage because I cannot save the frames fast enough so I have to malloc more memory to store them.
此时应用程序为trashing。此时拨打malloc
会让事情变得更糟。我建议实现一个生产者 - 消费者模型,其中一个根据具体情况等待。在您的情况下,设置N个缓冲区的阈值。如果队列中有N个缓冲区,则在处理现有缓冲区之前,不会将来自摄像机的新帧排入队列。
另一个想法,而不是写原始帧为什么不写编码数据?假设你想要一个视频,你至少可以写一个基本的H264流(ffmpeg带有一个好的H264编码器!),如果你有权访问Mpeg-4多路复用器,你可以更好地作为mp4文件?这将显着降低内存需求和IO负载。
答案 2 :(得分:0)
我要将包含100,000多个文件的目录复制到exFat磁盘上。复制开始于大约60 MB / s,然后降级到大约1-2 Mb / s。对我来说,快速而肮脏的解决方法是在操作过程中让计算机进入睡眠状态,然后将其唤醒。原始速度立即返回。不太复杂,但是对我有用。
答案 3 :(得分:0)
我认为这最终是使用“不明智的”写入模式的结果。
为什么它可以在您的综合测试中编写8个线程?好吧,因为在这种情况下,线程阻塞几乎没有关系。您正在将数据推向驱动器满油门。这样获得100 MB / s的速度就不足为奇了,这大约是您所报告的驱动器的实际速度。
“真实”程序会发生什么?您有8个生产者和一个将数据推送到磁盘的消费者。您使用非缓冲的同步写入。这意味着只要驱动器接收所有数据(写入线程被阻塞),它什么都不会做。生产者继续生产。好吧,你说。您只需要50 MB / s左右,驱动器就可以达到100 MB / s。
是的,除了...这是同时进行多个线程锤击驱动器的最大值。您的并发率为零,因此您尚未获得那些100 MB / s的速度。也许你会做一段时间。
然后,驱动器需要执行某事。有事吗东西,任何东西,无论说什么,都是寻求。或者,您被安排了一下时间,或者由于某种原因而被调度,无论控制器或操作系统是什么原因,或者是由于什么原因在十毫秒内没有推送数据,而当您再次查看时,磁盘已经旋转了,因此您需要等待一转(这不是开玩笑!)。也许知道,碎片磁盘上可能只有一个或两个扇区。没事...发生什么 。
举例来说,这需要1到10毫秒的时间,并且您的线程在这段时间内没有执行任何操作,它仍在等待阻塞WriteFile
调用完成。生产者继续生产并不断从池中拉出内存块。该池用完了内存块,并分配了更多内存。生产者不断生产,不断生产。同时,您的编写器线程将缓冲区保持锁定状态,并且什么也不做。
看看这是怎么回事?那不可能永远持续下去。即使它暂时恢复,也总是在艰难地挣扎。输了。
缓存(也称为写缓存)无缘无故。无论是在带宽还是在延迟方面,现实世界中的事物并没有总是像我们希望的那样快。
在Internet上,我们可以轻松地将带宽提高到千兆位上行链路,但是受延迟的束缚,对此我们绝对无能为力。这就是为什么跨大西洋通讯仍然像30年前一样糟糕,并且它们仍将在30年后失败(光速很可能不会改变)。
在计算机内部,情况非常相似。您可以通过PCIe总线轻松地每秒将两位数的千兆字节推入数据,但是这样做首先要花费很长时间。图形程序员对此非常了解。
磁盘同样如此,采用完全相同的策略来解决问题也就不足为奇了:重叠传输,异步进行。这实际上是每个操作系统默认执行的操作。数据被复制到缓冲区,然后延迟和异步地写出。
但是,这正是您使用无缓冲写入禁用的功能。几乎总是(几乎没有例外)是一个坏主意。
由于某些原因,人们一直遵循DMA的这种思维方式来做魔术,并且在速度方面完全如此优越。在遥远的某个时候甚至可能是正确的,在一些非常罕见的极端情况下(例如从光盘一次读取)也许仍然如此。在所有其他情况下,这都是灾难性的反优化。 可以并行运行的事物按顺序运行。延迟加起来。线程阻塞,什么也不做。
仅使用“正常”写入就意味着将数据复制到缓冲区,并且WriteFile
几乎立即返回(嗯,延迟等于memcpy
)。驱动器永远不会饿死(希望,永远不能100%保证),并且您的编写器线程也不会阻塞,什么也不做。内存块不会堆积在队列中,并且池不会用完,它们不需要分配更多内存。