C# Web服务调用的多线程性能问题

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/507089/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-04 06:06:18  来源:igfitidea点击:

multithread performance problem for web service call

c#performancemultithreadingservice

提问by

Here is my sample program for web service server side and client side. I met with a strnage performance problem, which is, even if I increase the number of threads to call web services, the performance is not improved. At the same time, the CPU/memory/network consumption from performance panel of task manager is low. I am wondering what is the bottleneck and how to improve it?

这是我的 Web 服务服务器端和客户端的示例程序。我遇到了一个strnage性能问题,就是即使增加线程调用web服务,性能也没有提高。同时,任务管理器性能面板的CPU/内存/网络消耗较低。我想知道瓶颈是什么以及如何改进它?

(My test experience, double the number of threads will almost double the total response time)

(我的测试经验,线程数增加一倍几乎会使总响应时间增加一倍)

Client side:

客户端:

class Program
{
    static Service1[] clients = null;
    static Thread[] threads = null;

    static void ThreadJob (object index)
    {
        // query 1000 times
        for (int i = 0; i < 100; i++)
        {
            clients[(int)index].HelloWorld();
        }
    }

    static void Main(string[] args)
    {
        Console.WriteLine("Specify number of threads: ");
        int number = Int32.Parse(Console.ReadLine());

        clients = new Service1[number];
        threads = new Thread[number];

        for (int i = 0; i < number; i++)
        {
            clients [i] = new Service1();
            ParameterizedThreadStart starter = new ParameterizedThreadStart(ThreadJob);
            threads[i] = new Thread(starter);
        }

        DateTime begin = DateTime.Now;

        for (int i = 0; i < number; i++)
        {
            threads[i].Start(i);
        }

        for (int i = 0; i < number; i++)
        {
            threads[i].Join();
        }

        Console.WriteLine("Total elapsed time (s): " + (DateTime.Now - begin).TotalSeconds);

        return;
    }
}

Server side:

服务器端:

    [WebMethod]
    public double HelloWorld()
    {
        return new Random().NextDouble();
    }

thanks in advance, George

提前致谢,乔治

回答by gbjbaanb

My experience is generally that locking is the problem: I had a massively parallel server once that spent more time context switching than it did performing work.

我的经验通常是锁定是问题所在:我曾经有一个大规模并行服务器,它花在上下文切换上的时间比执行工作要多。

So - check your memory and process counters in perfmon, if you look at context switches and its high (more than 4000 per second) then you're in trouble.

因此 - 检查 perfmon 中的内存和进程计数器,如果您查看上下文切换及其高(每秒超过 4000),那么您就有麻烦了。

You can also check your memory stats on the server too - if its spending all its time swapping, or just creating and freeing strings, it'll appear to stall also.

您还可以检查服务器上的内存统计信息 - 如果它花费所有时间交换,或者只是创建和释放字符串,它似乎也会停止。

Lastly, check disk I/O, same reason as above.

最后,检查磁盘 I/O,原因同上。

The resolution is to remove your locks, or hold them for a minimum of time. Our problem was solved by removing the dependence on COM BSTRs and their global lock, you'll find that C# has plenty of similar synchronisation bottlenecks (intended to keep your code working safely). I've seen performance drop when I moved a simple C# app from a single-core to a multi-core box.

解决方案是移除您的锁,或将它们保留最少的时间。我们的问题是通过消除对 COM BSTR 及其全局锁的依赖来解决的,您会发现 C# 有很多类似的同步瓶颈(旨在让您的代码安全运行)。当我将一个简单的 C# 应用程序从单核转移到多核机器时,我看到了性能下降。

If you cannot remove the locks, the best option is not to create as many threads :) Use a thread pool instead to let the CPU finish one job before starting another.

如果您无法移除锁,最好的选择是不要创建尽可能多的线程 :) 使用线程池来让 CPU 在开始另一项工作之前完成一项工作。

回答by bruno conde

Well, in this case, you're not really balancing your work between the chosen n.o of threads... Each Thread you create will be performing the same Job. So if you create n threads and you have a limited parallel processing capacity, the performance naturally decreases. Another think I notice is that the required Job is a relatively fast operation for 100 iterations and even if you plan on dividing this Job through multiple threads you need to consider that the time spent in context switching, thread creation/deletion will be an important factor in the overall time.

那么,在这种情况下,您并没有真正在所选线程数之间平衡您的工作......您创建的每个线程都将执行相同的作业。所以如果你创建n个线程并且你的并行处理能力有限,那么性能自然会下降。我注意到的另一个想法是,所需的 Job 是 100 次迭代的相对较快的操作,即使您计划将此 Job 划分为多个线程,您也需要考虑上下文切换、线程创建/删除所花费的时间将是一个重要因素在总的时间内。

回答by markt

Although you are creating a multithreaded client, bear in mind that .NET has a configurable bottleneck of 2 simultaneous calls to a single host. This is by design. Note that this is on the client, not the server.

尽管您正在创建一个多线程客户端,但请记住,.NET 有一个可配置的瓶颈,即 2 个同时调用单个主机。这是设计使然。请注意,这是在客户端上,而不是在服务器上。

Try adjusting your app.config file in the client:

尝试在客户端调整您的 app.config 文件:

<system.net>
<connectionManagement>
    <add address=“*” maxconnection=“20″ />
</connectionManagement></system.net>

There is some more info on this in this short article:

这篇短文中有更多关于此的信息:

回答by markt

As bruno mentioned, your webmethod is a very quick operation. As an experiment, try ensuring that your HelloWorld method takes a bit longer. Throw in a Thread.Sleep(1000) before you return the random double. This will make it more likely that your service is actually forced to process requests in parallel. Then try your client with different amounts of threads, and see how the performance differs.

正如布鲁诺提到的,你的 webmethod 是一个非常快速的操作。作为实验,请尝试确保您的 HelloWorld 方法需要更长的时间。在返回随机双精度值之前放入 Thread.Sleep(1000) 。这将使您的服务更有可能被迫并行处理请求。然后用不同数量的线程尝试你的客户端,看看性能有什么不同。

回答by markt

I don't believe that you are running into a bottleneck at all actually.

我不相信你实际上遇到了瓶颈。

Did you try what I suggested ?

你试过我建议的吗?

Your idea is to add more threads to improve performance, because you are expecting that all of your threads will run perfectly in parallel. This is why you are assuming that doubling the number of threads should not double the total test time.

您的想法是添加更多线程以提高性能,因为您希望所有线程都能完美地并行运行。这就是为什么您假设线程数加倍不应使总测试时间加倍。

Your service takes a fraction of a second to return and your threads will not all start working at exactly the same instant in time on the client.

您的服务需要几分之一秒的时间才能返回,并且您的线程不会在客户端上的同一时刻立即开始工作。

So your threads are not actually working completely in parallel as you have assumed, and the results you are seeing are to be expected.

因此,您的线程实际上并没有像您假设的那样完全并行工作,您所看到的结果是可以预料的。

回答by markt

Of course adding Sleep will not improve performance.

当然,添加 Sleep 不会提高性能。

But the point of the test is to test with a variable number of threads. So, keep the Sleep in your WebMethod.

但测试的重点是使用可变数量的线程进行测试。因此,请在您的 WebMethod 中保留 Sleep。

And try now with 5, 10, 20 threads.

现在尝试使用 5、10、20 个线程。

If there are no other problems with your code, then the increase in time should not be linear as before.

如果你的代码没有其他问题,那么时间的增加应该不像以前那样是线性的。

You realize that in your test, when you double the amount of threads, you are doubling the amount of work that is being done. So if your threads are not truly executing in parallel, then you will, of course, see a linear increase in total time...

您意识到在您的测试中,当您将线程数量增加一倍时,您正在完成的工作量将增加一倍。因此,如果您的线程不是真正并行执行,那么您当然会看到总时间线性增加......

I ran a simple test using your client code (with a sleep on the service). For 5 threads, I saw a total time of about 53 seconds. And for 10 threads, 62 seconds. So, for 2x the number of calls to the webservice, it only took 17% more time.. That is what you are expecting, no ?

我使用您的客户端代码运行了一个简单的测试(在服务上休眠)。对于 5 个线程,我看到的总时间约为 53 秒。对于 10 个线程,62 秒。因此,对于 2 倍的网络服务调用次数,只需要多花 17% 的时间。这正是您所期望的,不是吗?

回答by markt

Try to use some processor consuming task instead of Thread.Sleep. Actually combined approach is the best.

尝试使用一些处理器消耗任务而不是 Thread.Sleep。实际上结合的方法是最好的。

Sleep will just pass thread's time frame to another thread.

睡眠只会将线程的时间范围传递给另一个线程。

回答by Jason Hernandez

You are not seeing any performance gain because there is none to be had. The one line of code in your service (below) probably executes without a context switch most of the time anyway.

您没有看到任何性能提升,因为没有任何性能提升。无论如何,您的服务中的一行代码(如下)在大多数情况下可能会在没有上下文切换的情况下执行。

return new Random().NextDouble();

The overhead involved in the web service call is higher than than the work you are doing inside of it. If you have some substantial work to do inside the service (database calls, look-ups, file access etc) you may begin to see some performance increase. Just parallelizing a task will not automatically make it faster.

Web 服务调用中涉及的开销高于您在其中所做的工作。如果您在服务内部有一些实质性的工作要做(数据库调用、查找、文件访问等),您可能会开始看到一些性能提升。只是并行化一个任务不会自动让它更快。

-Jason

-杰森

回答by user1857942

IIS AppPool "Maximum Worker Processes" is set to 1 by default. For some reason, each worker process is limited to process 10 service calls at a time. My WCF async server-side function does Sleep(10*1000); only. This is what happens when Maximum Worker Processes = 1 http://s4.postimg.org/4qc26cc65/image.png

默认情况下,IIS AppPool“最大工作进程数”设置为 1。出于某种原因,每个工作进程一次只能处理 10 个服务调用。我的 WCF 异步服务器端函数执行 Sleep(10*1000); 只要。这是当最大工作进程数 = 1 http://s4.postimg.org/4qc26cc65/image.png时发生的情况

alternatively

或者

http://i.imgur.com/C5FPbpQ.png?1

http://i.imgur.com/C5FPbpQ.png?1

(First post on SO, I need to combine all pictures into one picture.)

(关于SO的第一篇文章,我需要将所有图片合并为一张图片。)

The client is making 48 async WCF WS calls in this test (using 16 processes). Ideally this should take ~10 seconds to complete (Sleep(10000)), but it takes 52 seconds. You can see 5 horizontal lines in the perfmon picture (above link) (using perfmon for monitoring Web Service Current Connections in server). Each horizontal line lasts 10 seconds (which Sleep(10000) does). There are 5 horizontal lines because the server processes 10 calls each time then closes that 10 connections (this happens 5 times to process 48 calls). Completion of all calls took 52 seconds.

客户端在此测试中进行了 48 个异步 WCF WS 调用(使用 16 个进程)。理想情况下,这应该需要大约 10 秒才能完成 (Sleep(10000)),但需要 52 秒。您可以在 perfmon 图片中看到 5 条水平线(链接上方)(使用 perfmon 监控服务器中的 Web 服务当前连接)。每条水平线持续 10 秒(Sleep(10000) 就是这样)。有 5 条水平线,因为服务器每次处理 10 个调用,然后关闭那 10 个连接(这发生 5 次以处理 48 个调用)。完成所有调用需要 52 秒。

After setting Maximum Worker Processes = 2 (in the same picture given above) This time there are 3 horizontal lines because the server processes 20 calls each time then closes that 20 connections (this happens 3 times to process 48 calls). Took 33 secs.

在设置 Maximum Worker Processes = 2 之后(在上面给出的同一张图中)这次有 3 条水平线,因为服务器每次处理 20 个调用然后关闭那 20 个连接(这发生了 3 次以处理 48 个调用)。耗时 33 秒。

After setting Maximum Worker Processes = 3 (in the same picture given above) This time there are 2 horizontal lines because the server processes 30 calls each time. (happens 2 times to process 48 calls) Took 24 seconds.

设置Maximum Worker Processes = 3后(同上图)这次有2条水平线,因为服务器每次处理30个调用。(发生 2 次处理 48 个电话)花了 24 秒。

After setting Maximum Worker Processes = 8 (in the same picture given above) This time there is 1 horizontal line because the server processes 80 calls each time. (happens once to process 48 calls) Took 14 seconds.

设置Maximum Worker Processes = 8后(同上图)这次有1条水平线,因为服务器每次处理80个调用。(发生一次处理 48 个电话)花了 14 秒。

If you don't care this situation, your parallel (async or threaded) client calls will be queued by 10s in the server, then all of your threaded calls (>10) won't get processed by the server in parallel.

如果您不关心这种情况,您的并行(异步或线程)客户端调用将在服务器中排队 10 秒,然后服务器不会并行处理所有线程调用(> 10)。

PS: I was using Windows 8 x64 with IIS 8.5. The 10 concurrent request limit is for workstation Windows OSes. Server OSes doesn't have that limit according to another post on SO (I can't give link due to rep < 10).

PS:我使用的是带有 IIS 8.5 的 Windows 8 x64。10 个并发请求限制适用于工作站 Windows 操作系统。根据 SO 上的另一篇文章,服务器操作系统没有该限制(由于 rep < 10,我无法提供链接)。