gpt4 book ai didi

c# - CPU使用率未最大化,并且依赖异步/等待的服务器应用程序中的高度同步

转载 作者:太空狗 更新时间:2023-10-29 19:41:14 28 4
gpt4 key购买 nike

我目前正在执行我开发的服务器应用程序的一些基准测试,主要依赖于C 5异步/等待结构。
这是一个控制台应用程序,因此没有同步上下文,也没有在代码中显式创建线程。
应用程序正在尽可能快地将来自msmq队列的请求出列(异步出列循环),并在通过httpclient发送已处理的请求之前处理每个请求。
依赖于async/await的i/o从msmsq出列,读取数据/写入数据到sql server db,最后在链的末尾发送httpclient请求。
目前,对于我的基准测试,数据库是完全伪造的(结果通过task.fromResult直接返回),httpclient也是伪造的(等待一个随机任务,延迟0-50毫秒并返回一个响应),因此真正的唯一I/O是从msmq出列。
我已经看到在gc中花费了大量时间,从而大大提高了应用程序的吞吐量,所以我使用clr profiler找到了可以优化的地方。
我现在想看看我是否还能提高吞吐量,我认为这是可能的。
有两件事我不明白,也许这背后有一些彻底改善的可能性:
1)我有4个CPU内核(实际上只有2个真正的CPU内核……i7 cpu),并且当应用程序运行时,它最多只使用3个cpu内核(在vs2012并发可视化工具中,我可以清楚地看到只有3个内核在使用,在windows perfmon中,我可以看到cpu使用率大约为75/80%)。知道为什么吗?我无法控制线程,因为我没有明确地创建它们,只依赖于任务,那么为什么任务调度器在我的情况下不能最大化CPU使用率?有人经历过吗?
2)使用VS2012 Concurrency Visualizer,我可以看到非常高的同步时间(大约20%的执行和80%的同步)。F.Y.I正在创建大约15个线程。
大约60%的同步来自以下调用堆栈:

clr.dll!ThreadPoolMgr::WorkerThreadStart
clr.dll!CLRSemaphore::Wait
kernelbase.dll!WaitForSingleObjectEx


clr.dll!ThreadPoolMgr::WorkerThreadStart
clr.dll!ThreadPoolMgr::UnfairSemaphore::Wait
clr.dll!CLRSemaphore::Wait
kernelbase.dll!WaitForSingleObjectEx

大约30%的同步来自:
clr.dll!ThreadPoolMgr::CompletionPortThreadStart
kernel32.dll!GetQueueCompletionStatusStub
kernelbase.dll!GetQueuedCompletionStatus
ntdll.dll!ZwRemoveIoCompletion
..... blablabla
ntoskrnl.exe!KeRemoveQueueEx

我不知道这是不是正常的经历如此高的同步。
编辑:基于Stephen Answer,我将添加更多关于我的实现的详细信息:
实际上,我的服务器是完全异步的。然而,为了处理每条消息,一些cpu工作已经完成(我承认不是很多,但仍然有些)。从msmq队列接收到消息后,首先对其进行反序列化(此时似乎会发生大部分cpu/内存开销),然后经过处理/验证的各个阶段,这些阶段会消耗一些cpu,最后到达“管道末端”,在那里处理的消息通过httpclient发送到外部世界。
我的实现不是等待消息被完全处理,然后再将下一条消息从队列中出列。事实上,我的消息泵,队列中的消息出列,非常简单,可以立即“转发”消息,以便下一个消息出列。简化的代码如下所示(操作异常管理、取消…):
while (true)
{
var message = await this.queue.ReceiveNextMessageAsync();
this.DeserializeDispatchMessageAsync();
}

private async void DeserializeDispatchMessageAsync()
{
// Immediately yield to avoid blocking the asynchronous messaging pump
// while deserializing the body which would otherwise impact the throughput.
await Task.Yield();

this.messageDispatcher.DispatchAsync(message).ForgetSafely();
}

ReceiveNextMessageAsync是使用 TaskCompletionSource的自定义方法,因为.net MessageQueue在.net framework 4.5中没有提供任何异步方法。所以我只是用 BeginReceive/ EndReceiveTaskCompletionSource耦合。
这是我的代码中唯一不等待异步方法的地方之一。循环尽可能快地解列。它甚至不等待消息反序列化(当显式访问body属性时,消息的.net fcl实现会惰性地执行消息反序列化)。我立即执行task.yield()将反序列化/消息处理转移到另一个任务,并立即释放循环。
现在,在我的长椅环境中,正如我之前所说的,所有的i/o(仅限db访问)都是伪造的。所有调用异步方法以从数据库中获取数据的调用都只返回一个带有假数据的task.fromResult。在处理消息的过程中,有20 db左右的调用,它们现在都是假的/同步的。唯一的异步点是在消息处理结束时,消息通过httpclient发送。httpclient发送也是伪造的,但此时我正在执行一个随机(0-50ms)“wait task.delay”。无论如何,由于db的伪造,每个消息处理都可以看作一个单独的任务。
对于我的长椅,我将在队列中存储大约30万条消息,然后启动服务器应用程序。它的出列速度相当快,淹没了服务器应用程序,所有消息都被并发处理。这就是为什么我不明白为什么我没有达到100%的CPU和4核,但只有75%和3核使用(同步问题除外)。
当我只在不进行任何反序列化或消息处理的情况下出列(注释掉对 DeserializeDispatchMessageAsync的调用)时,我达到大约20k条消息/秒的吞吐量。
当我完成整个处理过程时,每秒大约会收到10000条消息。
消息从队列中快速出列,消息反序列化+处理在单独的任务中完成,这一事实使我在脑海中看到许多任务(每条消息一个)在任务调度程序(此处为线程池)上排队……没有同步上下文),因此我希望线程池将所有这些消息发送到最大数量的核心,并且所有4个核心都忙于处理所有任务,但我似乎不是这样。
不管怎样,欢迎任何回答,我正在寻找任何想法/建议。

最佳答案

听起来您的服务器几乎是完全异步的(async msmq、async db、async httpclient)。所以在这种情况下,我觉得你的结果并不令人惊讶。
首先,要做的CPU工作很少。我完全希望每个线程池线程大部分时间都在等待工作完成。记住,在自然异步操作期间不使用CPU。
异步msmq/db/Task操作返回的HttpClient不会在线程池线程上执行;它只表示I/O操作的完成。您看到的唯一线程池工作是异步方法内部的少量同步工作,通常只是为I/O安排缓冲区。
就吞吐量而言,你确实有一些空间可以扩展(假设你的测试正在淹没你现有的服务)。可能您的代码只是(异步地)从msmq中检索一个值,然后(异步地)在检索另一个值之前处理它;在这种情况下,您肯定会看到从msmq中连续读取的改进。请记住,async代码是异步的,但它仍然是序列化的;async方法可能会在任何await处暂停。
如果是这样的话,您可能会受益于设置一个tpl数据流管道(将MaxDegreeOfParallelism设置为Unbounded)和运行一个从msmq异步读取并将数据推送到管道中的紧密循环。这将比你自己做重叠处理更容易。
更新以进行编辑:
我有一些建议:
使用Task.Run而不是await Task.YieldTask.Run有更清晰的意图。
Begin/End包装器可以使用Task.Factory.FromAsync而不是tcs,这为您提供了更干净的代码。
但我看不出为什么最后一个核心不被使用的任何原因——除了像profiler或其他让它忙碌的应用程序这样明显的原因。最后应该得到一个async等价的dynamic parallelism,这是.net线程池专门设计用来处理的情况之一。

关于c# - CPU使用率未最大化,并且依赖异步/等待的服务器应用程序中的高度同步,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/17891929/

28 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com