zoukankan      html  css  js  c++  java
  • .net异步性能测试(包括ASP.NET MVC WebAPI异步方法)

    很久没有写博客了,今年做的产品公司这两天刚刚开了发布会,稍微清闲下来,想想我们做的产品还有没有性能优化空间,于是想到了.Net的异步可以优化性能,但到底能够提升多大的比例呢?恰好有一个朋友正在做各种语言的异步性能测试(有关异步和同步的问题,请参考客《AIO与BIO接口性能对比》),于是我今天写了一个C#的测试程序。

    首先,建一个 ASP.NET MVC WebAPI项目,在默认的控制器 values里面,增加两个方法:

     // GET api/values?sleepTime=10
            [HttpGet]
            public async Task<string> ExecuteAIO(int sleepTime)
            {
                await Task.Delay(sleepTime);
                return  "Hello world,"+ sleepTime;
            }
    
            [HttpGet]
            // GET api/values?sleepTime2=10
            public string ExecuteBIO(int sleepTime2)
            {
                System.Threading.Thread.Sleep(sleepTime2);
                return "Hello world," + sleepTime2;
            }

    然后,建立一个控制台程序,来测试这个web API:

     class Program
        {
            static void Main(string[] args)
            {
                Console.WriteLine("按任意键开始测试 WebAPI:http://localhost:62219/api/values?sleepTime={int}");
                Console.Write("请输入线程数:");
                int threadNum = 100;
                int.TryParse(Console.ReadLine(), out threadNum);
                while (Test(threadNum)) ;
    
                Console.ReadLine();
                Console.ReadLine();
            }
    
            private static bool Test(int TaskNumber)
            {
                Console.Write("请输入此API方法的睡眠时间(毫秒),输入非数字内容退出:");
                string input = Console.ReadLine();
                int SleepTime = 50;
                if (!int.TryParse(input, out SleepTime))
                    return false;
                HttpClient client = new HttpClient();
                client.BaseAddress = new Uri("http://localhost:62219/");
                var result = client.GetStringAsync("api/values?sleepTime=" + input).Result;
                Console.WriteLine("Result:{0}", result);
                //int TaskNumber = 1000;
    
    
                Console.WriteLine("{0}次 BIO(同步)测试(睡眠{1} 毫秒):", TaskNumber, SleepTime);
                System.Diagnostics.Stopwatch sw = new System.Diagnostics.Stopwatch();
    
    
                sw.Start();
                Task[] taskArr = new Task[TaskNumber];
                for (int i = 0; i < TaskNumber; i++)
                {
                    Task task = client.GetStringAsync("api/values?sleepTime2=" + SleepTime);
                    taskArr[i] = task;
    
                }
                Task.WaitAll(taskArr);
                sw.Stop();
                double useTime1 = sw.Elapsed.TotalSeconds;
                Console.WriteLine("耗时(秒):{0},QPS:{1,10:f2}", useTime1, TaskNumber/useTime1);
                sw.Reset();
    
                Console.WriteLine("{0}次 AIO(异步)测试(睡眠{1} 毫秒):", TaskNumber, SleepTime);
                sw.Start();
                for (int i = 0; i < TaskNumber; i++)
                {
                    Task task = client.GetStringAsync("api/values?sleepTime=" + SleepTime);
                    taskArr[i] = task;
                }
                Task.WaitAll(taskArr);
                sw.Stop();
                double useTime2 = sw.Elapsed.TotalSeconds;
                Console.WriteLine("耗时(秒):{0},QPS:{1,10:f2}", useTime2, TaskNumber / useTime2);
                return true;
            }
        }
    View Code

    其实主要是下面几行代码:

    HttpClient client = new HttpClient();
    client.BaseAddress = new Uri("http://localhost:62219/");
    var result = client.GetStringAsync("api/values?sleepTime=" + input).Result;

    注意,你可能需要使用Nuget添加下面这个包:

    Microsoft.AspNet.WebApi.Client

    最后,运行这个测试,结果如下:

    按任意键开始测试 WebAPI:http://localhost:62219/api/values?sleepTime={int}
    请输入线程数:1000
    请输入此API方法的睡眠时间(毫秒),输入非数字内容退出:10
    Result:"Hello world,10"
    1000次 BIO(同步)测试(睡眠10 毫秒):
    耗时(秒):1.2860545,QPS:    777.57
    1000次 AIO(异步)测试(睡眠10 毫秒):
    耗时(秒):0.4895946,QPS:   2042.51
    请输入此API方法的睡眠时间(毫秒),输入非数字内容退出:100
    Result:"Hello world,100"
    1000次 BIO(同步)测试(睡眠100 毫秒):
    耗时(秒):8.2769307,QPS:    120.82
    1000次 AIO(异步)测试(睡眠100 毫秒):
    耗时(秒):0.5435111,QPS:   1839.89

    本来想尝试测试10000个线程,但报错了。

    上面的测试结果,QPS并不高,但由于使用的是IISExpress,不同的Web服务器软件性能不相同,所以还得对比下进程内QPS结果,于是新建一个控制台程序,代码如下:

     class Program
        {
            static void Main(string[] args)
            {
                Console.WriteLine("按任意键开始测试 ");
                Console.Write("请输入线程数:");
                int threadNum = 100;
                int.TryParse(Console.ReadLine(), out threadNum);
                while (Test(threadNum)) ;
    
                Console.ReadLine();
                Console.ReadLine();
            }
    
            private static bool Test(int TaskNumber)
            {
                Console.Write("请输入此API方法的睡眠时间(毫秒),输入非数字内容退出:");
                string input = Console.ReadLine();
                int SleepTime = 50;
                if (!int.TryParse(input, out SleepTime))
                    return false;
    
                var result = ExecuteAIO(SleepTime).Result;
                Console.WriteLine("Result:{0}", result);
                //int TaskNumber = 1000;
    
    
                Console.WriteLine("{0}次 BIO(同步)测试(睡眠{1} 毫秒):", TaskNumber, SleepTime);
                System.Diagnostics.Stopwatch sw = new System.Diagnostics.Stopwatch();
    
    
                sw.Start();
                Task[] taskArr = new Task[TaskNumber];
                for (int i = 0; i < TaskNumber; i++)
                {
                    Task task = Task.Run<string>(()=> ExecuteBIO(SleepTime));
                    taskArr[i] = task;
    
                }
                Task.WaitAll(taskArr);
                sw.Stop();
                double useTime1 = sw.Elapsed.TotalSeconds;
                Console.WriteLine("耗时(秒):{0},QPS:{1,10:f2}", useTime1, TaskNumber / useTime1);
                sw.Reset();
    
                Console.WriteLine("{0}次 AIO(异步)测试(睡眠{1} 毫秒):", TaskNumber, SleepTime);
                sw.Start();
                for (int i = 0; i < TaskNumber; i++)
                {
                    Task task = ExecuteAIO(SleepTime);
                    taskArr[i] = task;
                }
                Task.WaitAll(taskArr);
                sw.Stop();
                double useTime2 = sw.Elapsed.TotalSeconds;
                Console.WriteLine("耗时(秒):{0},QPS:{1,10:f2}", useTime2, TaskNumber / useTime2);
                return true;
            }
    
            public static async Task<string> ExecuteAIO(int sleepTime)
            {
                await Task.Delay(sleepTime);
                return "Hello world," + sleepTime;
            }
    
            public static string ExecuteBIO(int sleepTime2)
            {
                System.Threading.Thread.Sleep(sleepTime2);
                //不能在非异步方法里面使用 Task.Delay,否则可能死锁
                //Task.Delay(sleepTime2).Wait();
                return "Hello world," + sleepTime2;
            }
        }
    View Code

    注意,关键代码只有下面两个方法:

     public static async Task<string> ExecuteAIO(int sleepTime)
            {
                await Task.Delay(sleepTime);
                return "Hello world," + sleepTime;
            }
    
            public static string ExecuteBIO(int sleepTime2)
            {
                System.Threading.Thread.Sleep(sleepTime2);
                //不能在非异步方法里面使用 Task.Delay,否则可能死锁
                //Task.Delay(sleepTime2).Wait();
                return "Hello world," + sleepTime2;
            }

    这两个方法跟WebAPI的测试方法代码是一样的,但是调用代码稍微不同:

    同步调用:

     Task[] taskArr = new Task[TaskNumber];
                for (int i = 0; i < TaskNumber; i++)
                {
                    Task task = Task.Run<string>(()=> ExecuteBIO(SleepTime));
                    taskArr[i] = task;
    
                }
                Task.WaitAll(taskArr);

    异步调用:

     for (int i = 0; i < TaskNumber; i++)
                {
                    Task task = ExecuteAIO(SleepTime);
                    taskArr[i] = task;
                }
                Task.WaitAll(taskArr);

    可见,这里测试的时候,同步和异步调用,客户端代码都是使用的多线程,主要的区别就是异步方法使用了 async/await 语句。

    下面是非Web的进程内异步多线程和同步多线程的结果:

    请输入线程数:1000
    请输入此API方法的睡眠时间(毫秒),输入非数字内容退出:10
    Result:Hello world,10
    1000次 BIO(同步)测试(睡眠10 毫秒):
    耗时(秒):1.3031966,QPS:    767.34
    1000次 AIO(异步)测试(睡眠10 毫秒):
    耗时(秒):0.026441,QPS:  37820.05
    请输入此API方法的睡眠时间(毫秒),输入非数字内容退出:100
    Result:Hello world,100
    1000次 BIO(同步)测试(睡眠100 毫秒):
    耗时(秒):9.8502858,QPS:    101.52
    1000次 AIO(异步)测试(睡眠100 毫秒):
    耗时(秒):0.1149469,QPS:   8699.67
    
    请输入线程数:10000
    请输入此API方法的睡眠时间(毫秒),输入非数字内容退出:10
    Result:Hello world,10
    10000次 BIO(同步)测试(睡眠10 毫秒):
    耗时(秒):7.7966125,QPS:   1282.61
    10000次 AIO(异步)测试(睡眠10 毫秒):
    耗时(秒):0.083922,QPS: 119158.27
    请输入此API方法的睡眠时间(毫秒),输入非数字内容退出:100
    Result:Hello world,100
    10000次 BIO(同步)测试(睡眠100 毫秒):
    耗时(秒):34.3646036,QPS:    291.00
    10000次 AIO(异步)测试(睡眠100 毫秒):
    耗时(秒):0.1721833,QPS:  58077.64

    结果表示,.NET程序开启10000个任务(不是10000个原生线程,需要考虑线程池线程),异步方法的QPS超过了10万,而同步方法只有1000多点,性能差距还是很大的。

    注:以上测试结果的测试环境是 

    Intel i7-4790K CPU,4核8线程,内存 16GB,Win10 企业版

    总结:

    不论是普通程序还是Web程序,使用异步多线程,可以极大的提高系统的吞吐量。

    后记:

    感谢网友“双鱼座” 的提示,我用信号量和都用线程Sleep的方式,对同步和异步方法进行了测试,结果如他所说,TPL异步方式,开销很大,下面是测试数据:

    使用 semaphoreSlim 的情况:
    
    请输入线程数:1000
    请输入此API方法的睡眠时间(毫秒),输入非数字内容退出:10
    Result:Hello world,10
    1000次 BIO(同步)测试(睡眠10 毫秒):
    耗时(秒):1.2486964,QPS:    800.84
    1000次 AIO(异步)测试(睡眠10 毫秒):
    耗时(秒):10.5259443,QPS:     95.00
    请输入此API方法的睡眠时间(毫秒),输入非数字内容退出:100
    Result:Hello world,100
    1000次 BIO(同步)测试(睡眠100 毫秒):
    耗时(秒):12.2754003,QPS:     81.46
    1000次 AIO(异步)测试(睡眠100 毫秒):
    耗时(秒):100.5308431,QPS:      9.95
    请输入此API方法的睡眠时间(毫秒),输入非数字内容退出:1000
    Result:Hello world,1000
    1000次 BIO(同步)测试(睡眠1000 毫秒):
    耗时(秒):54.0055828,QPS:     18.52
    1000次 AIO(异步)测试(睡眠1000 毫秒):
    耗时(秒):1000.4749124,QPS:      1.00

    使用线程 Sleep的代码改造:

      public static async Task<string> ExecuteAIO(int sleepTime)
            {
                //await Task.Delay(sleepTime);
                //return "Hello world," + sleepTime;
                //await Task.Delay(sleepTime);
                //semaphoreSlim.Wait(sleepTime);
                System.Threading.Thread.Sleep(sleepTime);
                return await Task.FromResult("Hello world," + sleepTime);
            }
    
            public static string ExecuteBIO(int sleepTime2)
            {
                System.Threading.Thread.Sleep(sleepTime2);
                //semaphoreSlim.Wait(sleepTime2);
                //不能在非异步方法里面使用 Task.Delay,否则可能死锁
                //Task.Delay(sleepTime2).Wait();
                return "Hello world," + sleepTime2;
            }

    运行结果如下:

    请输入线程数:1000
    请输入此API方法的睡眠时间(毫秒),输入非数字内容退出:10
    Result:Hello world,10
    1000次 BIO(同步)测试(睡眠10 毫秒):
    耗时(秒):1.3099217,QPS:    763.40
    1000次 AIO(异步)测试(睡眠10 毫秒):
    耗时(秒):10.9869045,QPS:     91.02
    请输入此API方法的睡眠时间(毫秒),输入非数字内容退出:100
    Result:Hello world,100
    1000次 BIO(同步)测试(睡眠100 毫秒):
    耗时(秒):8.5861461,QPS:    116.47
    1000次 AIO(异步)测试(睡眠100 毫秒):
    耗时(秒):100.9829406,QPS:      9.90
    请输入此API方法的睡眠时间(毫秒),输入非数字内容退出:1000
    Result:Hello world,1000
    1000次 BIO(同步)测试(睡眠1000 毫秒):
    耗时(秒):27.0158904,QPS:     37.02
    1000次 AIO(异步)测试(睡眠1000 毫秒):

    在每次睡眠1秒的异步方法测试中,很久都没有出来结果,不用考虑,QPS肯定低于一秒了。

    经验教训:

    在异步方法中,不要使用 Thread.Sleep;在同步方法中,不要使用Task.Delay ,否则可能出现线程死锁,结果难出来。

  • 相关阅读:
    PHP_Code_challenge(代码审计)
    超全局变量$GLOBALS
    upload-labs(文件上传)
    CTF bugku 论剑场 web20
    多次Printf()是否使用用同一栈帧的参数?
    (转载于度盘)小迪安全渗透-学习讲义
    SQL数据库操作练习(2)
    .htaccess绕过
    PHP序列化思考(9.14已修改)
    SQL数据库操作练习(1)
  • 原文地址:https://www.cnblogs.com/bluedoctor/p/7562705.html
Copyright © 2011-2022 走看看