我想并行处理逐帧处理多个视频剪辑的应用程序。每个剪辑的每个帧的序列很重要(显然)。 我决定使用TPL Dataflow,因为我相信这是数据流的一个很好的例子(电影帧是数据)。
所以我有一个从数据库加载帧的进程(比方说,一批500个,全部聚集在一起)
Example sequence:
|mid:1 fr:1|mid:1 fr:2|mid:2 fr:1|mid:3 fr:1|mid:1 fr:3|mid:2 fr:2|mid:2 fr:3|mid:1 fr:4|
并将它们发布到BufferBlock。对于这个BufferBlock,我已经将ActionBlocks与过滤器相关联,每个MovieID都有一个ActionBlock,以便我获得某种数据分区。每个ActionBlock都是顺序的,但理想情况下,多个电影的多个ActionBlock可以并行运行。
我确实让上述网络正常运行并且确实并行运行,但从我的计算中,只有8到10个ActionBlock同时执行。我计算了每个ActionBlock的运行时间和大约100-200ms。 我可以采取哪些步骤来至少实现双重并发?
我确实尝试将操作委托转换为异步方法,并在ActionBlock操作委托中使数据库访问异步,但它没有帮助。
编辑:我实施了额外级别的数据分区:在ServerA上处理具有奇数ID的电影的帧,在ServerB上处理偶数电影的帧。应用程序的两个实例都在同一个数据库中。如果我的问题是DB IO,那么我不会看到总帧处理数量有任何改善(或者很少,低于20%)。但我确实看到它翻了一番。因此,这让我得出结论,Threadpool并没有产生更多线程来并行执行更多帧(两个服务器都是四核,而探查器显示每个应用程序大约25-30个线程)。
答案 0 :(得分:2)
一些假设:
根据您的示例数据,您无法接收电影帧(可能还有电影中的帧)
您的ActionBlock<T>
个实例是通用的;他们都调用相同的处理方法,你只需根据每个电影ID创建一个列表(你事先有一个电影ID列表),如下所示:
// The movie IDs
IEnumerable<int> movieIds = ...;
// The actions.
var actions = movieIds.Select(
i => new { Id = i, Action = new ActionBlock<Frame>(MethodToProcessFrame) });
// The buffer block.
BufferBlock<Frame> buffer = ...;
// Link everything up.
foreach (var action in actions)
{
// Not necessary in C# 5.0, but still, good practice.
// The copy of the action.
var actionCopy = action;
// Link.
bufferBlock.LinkTo(actionCopy.Action, f => f.MovieId == actionCopy.Id);
}
如果是这种情况,您就会创建太多ActionBlock<T>
个实例,而这些实例没有被授予工作;因为您的框架(可能还有电影)是无序的,所以您无法保证所有ActionBlock<T>
实例都有工作要做。
此外,当您创建ActionBlock<T>
实例时,它将以MaxDegreeOfParallelism
为1创建,这意味着它的线程安全,因为只有一个线程可以访问同时阻止。
此外,TPL DataFlow库最终依赖于Task<TResult>
class,它默认在线程池上进行调度。线程池将在这里做一些事情:
确保所有处理器核心都已饱和。这非常与确保您的ActionBlock<T>
个实例已经饱和且此是您应关注的指标
确保在处理器内核饱和时,确保工作均匀分布,并确保不执行太多许多并发任务(上下文切换很昂贵) )。
看起来你处理电影的方法也是通用的,传入电影的帧是什么并不重要(如果 重要,那么你需要更新你的问题,因为它改变了很多东西)。这也意味着它是线程安全的。
此外,如果可以假设一帧的处理不依赖于任何先前帧的处理(或者,看起来电影的帧看起来有序),则可以使用单 ActionBlock<T>
但调整MaxDegreeOfParallelism
值,如下所示:
// The buffer block.
BufferBlock<Frame> buffer = ...;
// Have *one* ActionBlock<T>
var action = new ActionBlock<Frame>(MethodToProcessFrame,
// This is where you tweak the concurrency:
new ExecutionDataflowBlockOptions {
MaxDegreeOfParallelism = 4,
}
);
// Link. No filter needed.
bufferBlock.LinkTo(action);
现在,您的ActionBlock<T>
将始终饱和。当然,任何负责任的任务调度程序(默认情况下是线程池)仍然会限制最大并发数量,但它会尽可能多地同时执行。
为此,如果您的操作真正线程安全,则可以将MaxDegreeOfParallelism
设置为DataflowBlockOptions.Unbounded
,如下所示:
// Have *one* ActionBlock<T>
var action = new ActionBlock<Frame>(MethodToProcessFrame,
// This is where you tweak the concurrency:
new ExecutionDataflowBlockOptions {
// We're thread-safe, let the scheduler determine
// how nuts we can go.
MaxDegreeOfParallelism = DataflowBlockOptions.Unbounded,
}
);
当然,所有这些都假定其他一切都是最优的(I / O读/写等)
答案 1 :(得分:-1)
可能性是最佳的并行化程度。实际上,线程池非常适合确定有效的实际线程的最佳数量。我的猜测是你的硬件可以支持许多实际并行工作的并行进程。如果你添加了更多,你实际上不会增加吞吐量,你只需花费更多的时间在线程之间进行上下文切换,而花费更少的时间来实际处理它们。
如果您注意到,在很长一段时间内,您的CPU负载,内存总线,网络连接,磁盘访问等都在低于容量的情况下运行,那么您可能会遇到问题,并且您需要检查看看实际上是什么瓶颈。有可能某些资源处于某种程度上,并且TPL已经认识到这一点,并确保它不会过度饱和该资源。
答案 2 :(得分:-1)
我怀疑你是IO界限。问题是在哪里?在读或写。你在写更多的数据而不是阅读。 CPU可能低于50%,因为它无法写得更快。
我不是说ActionBlock是错的,但我会考虑使用BlockingCollection的生产者消费者。优化读写数据的方式。
这个不同但我有一个应用程序,我在其中阅读文本块。解析文本,然后将单词写回SQL。我在单个线程上读取,然后并行解析,然后在单个线程上写入。我在一个线程上写,以免破坏索引。如果您是IO绑定,您需要弄清楚什么是最慢的IO然后优化该过程。
告诉我有关IO的更多信息。
在你提出的问题中你也提到了数据库
我会试试BlockingCollections
BlockingCollection Class
并且每个都有尺寸限制,因此您不会记忆
使它足够大,几乎不会变空
最慢步骤后的阻塞集合将变为空。
如果你可以并行处理,那么这样做
我发现表中的平行插入物并不快
让一个过程锁定并保持它并保持软管打开
仔细看看你如何插入。
一次一行很慢
我使用TVP并一次插入10,000个,但很多人喜欢Drapper或BulkInsert
如果删除索引和触发器,则按聚簇索引排序的插入将是最快的。
拿一个锁扣并握住它。
我在10毫秒范围内插入插件
现在更新速度最慢。
看看那个 - 你一次只做一行吗?
看看制片和视频剪辑
除非它是一个丑陋的更新,它不应该比插入更长的时间。