异步I / O密集型代码的运行速度比非异步慢,为什么?

石匠

我正在重构应用程序,并尝试添加现有功能的异步版本以改善ASP.NET MVC应用程序中的性能。我知道异步函数会涉及开销,但是我希望经过足够的迭代,从数据库中加载数据的I / O密集型特性将远远超过开销方面的补偿,并且我将获得显着的性能提升。

TermusRepository.LoadByTermusId函数通过从数据库中检索一堆数据表来加载数据(使用ADO.NET和Oracle Managed Client),填充模型并返回模型。TermusRepository.LoadByTermusIdAsync与之类似,只是它异步执行,在要检索多个数据表时,加载数据表下载任务的方法略有不同。

public async Task<ActionResult> AsyncPerformanceTest()
{
    var vm = new AsyncPerformanceTestViewModel();
    Stopwatch watch = new Stopwatch();
    watch.Start();
    for (int i = 0; i < 60; i++)
    {
        TermusRepository.LoadByTermusId<Termus2011_2012EndYear>("1");
        TermusRepository.LoadByTermusId<Termus2011_2012EndYear>("5");
        TermusRepository.LoadByTermusId<Termus2011_2012EndYear>("6");
        TermusRepository.LoadByTermusId<Termus2011_2012EndYear>("7");
    }
    watch.Stop();
    vm.NonAsyncElapsedTime = watch.Elapsed;
    watch.Reset();
    watch.Start();
    var tasks = new List<Task<Termus2011_2012EndYear>>();
    for (int i = 0; i < 60; i++)
    {
        tasks.Add(TermusRepository.LoadByTermusIdAsync<Termus2011_2012EndYear>("1"));
        tasks.Add(TermusRepository.LoadByTermusIdAsync<Termus2011_2012EndYear>("5"));
        tasks.Add(TermusRepository.LoadByTermusIdAsync<Termus2011_2012EndYear>("6"));
        tasks.Add(TermusRepository.LoadByTermusIdAsync<Termus2011_2012EndYear>("7"));               
    }
    await Task.WhenAll(tasks.ToArray());
    watch.Stop();
    vm.AsyncElapsedTime = watch.Elapsed;            
    return View(vm);
}

public static async Task<T> LoadByTermusIdAsync<T>(string termusId) where T : Appraisal
{
    var AppraisalHeader = new OracleCommand("select tu.termus_id, tu.manager_username, tu.evaluee_name, tu.evaluee_username, tu.termus_complete_date, termus_start_date, tu.termus_status, tu.termus_version, tn.managername from tercons.termus_users tu left outer join tercons.termus_names tn on tu.termus_id=tn.termus_id where tu.termus_id=:termusid");
    AppraisalHeader.BindByName = true;
    AppraisalHeader.Parameters.Add("termusid", termusId);
    var dt = await Database.GetDataTableAsync(AppraisalHeader);
    T Termus = Activator.CreateInstance<T>();
    var row = dt.AsEnumerable().Single();
    Termus.TermusId = row.Field<decimal>("termus_id").ToString();
    Termus.ManagerUsername = row.Field<string>("manager_username");
    Termus.EvalueeUsername = row.Field<string>("evaluee_username");
    Termus.EvalueeName = row.Field<string>("evaluee_name");
    Termus.ManagerName = row.Field<string>("managername");
    Termus.TERMUSCompleteDate = row.Field<DateTime?>("termus_complete_date");
    Termus.TERMUSStartDate = row.Field<DateTime>("termus_start_date");
    Termus.Status = row.Field<string>("termus_status");
    Termus.TERMUSVersion = row.Field<string>("termus_version");
    Termus.QuestionsAndAnswers = new Dictionary<string, string>();

    var RetrieveQuestionIdsCommand = new OracleCommand("select termus_question_id from tercons.termus_questions where termus_version=:termus_version");
    RetrieveQuestionIdsCommand.BindByName = true;
    RetrieveQuestionIdsCommand.Parameters.Add("termus_version", Termus.TERMUSVersion);
    var QuestionIdsDt = await Database.GetDataTableAsync(RetrieveQuestionIdsCommand);
    var QuestionIds = QuestionIdsDt.AsEnumerable().Select(r => r.Field<string>("termus_question_id"));

    //There's about 60 questions/answers, so this should result in 60 calls to the database. It'd be a good spot to combine to a single DB call, but left it this way so I could see if async would speed it up for learning purposes.
    var DownloadAnswersTasks = new List<Task<DataTable>>();
    foreach (var QuestionId in QuestionIds)
    {
        var RetrieveAnswerCommand = new OracleCommand("select termus_response, termus_question_id from tercons.termus_responses where termus_id=:termus_id and termus_question_id=:questionid");
        RetrieveAnswerCommand.BindByName = true;
        RetrieveAnswerCommand.Parameters.Add("termus_id", termusId);
        RetrieveAnswerCommand.Parameters.Add("questionid", QuestionId);
        DownloadAnswersTasks.Add(Database.GetDataTableAsync(RetrieveAnswerCommand));
    }
    while (DownloadAnswersTasks.Count > 0)
    {
        var FinishedDownloadAnswerTask = await Task.WhenAny(DownloadAnswersTasks);
        DownloadAnswersTasks.Remove(FinishedDownloadAnswerTask);
        var AnswerDt = await FinishedDownloadAnswerTask;
        var Answer = AnswerDt.AsEnumerable().Select(r => r.Field<string>("termus_response")).SingleOrDefault();
        var QuestionId = AnswerDt.AsEnumerable().Select(r => r.Field<string>("termus_question_id")).SingleOrDefault();
        if (!String.IsNullOrEmpty(Answer))
        {
            Termus.QuestionsAndAnswers.Add(QuestionId, System.Net.WebUtility.HtmlDecode(Answer));
        }
    }
    return Termus;
}

public static async Task<DataTable> GetDataTableAsync(OracleCommand command)
{
    DataTable dt = new DataTable();
    using (var connection = GetDefaultOracleConnection())
    {
        command.Connection = connection;
        await connection.OpenAsync();
        dt.Load(await command.ExecuteReaderAsync());
    }
    return dt;
}

public static T LoadByTermusId<T>(string TermusId) where T : Appraisal
{
    var RetrieveAppraisalHeaderCommand = new OracleCommand("select tu.termus_id, tu.manager_username, tu.evaluee_name, tu.evaluee_username, tu.termus_complete_date, termus_start_date, tu.termus_status, tu.termus_version, tn.managername from tercons.termus_users tu left outer join tercons.termus_names tn on tu.termus_id=tn.termus_id where tu.termus_id=:termusid");
    RetrieveAppraisalHeaderCommand.BindByName = true;
    RetrieveAppraisalHeaderCommand.Parameters.Add("termusid", TermusId);
    var AppraisalHeaderDt = Database.GetDataTable(RetrieveAppraisalHeaderCommand);
    T Termus = Activator.CreateInstance<T>();
    var AppraisalHeaderRow = AppraisalHeaderDt.AsEnumerable().Single();
    Termus.TermusId = AppraisalHeaderRow.Field<decimal>("termus_id").ToString();
    Termus.ManagerUsername = AppraisalHeaderRow.Field<string>("manager_username");
    Termus.EvalueeUsername = AppraisalHeaderRow.Field<string>("evaluee_username");
    Termus.EvalueeName = AppraisalHeaderRow.Field<string>("evaluee_name");
    Termus.ManagerName = AppraisalHeaderRow.Field<string>("managername");
    Termus.TERMUSCompleteDate = AppraisalHeaderRow.Field<DateTime?>("termus_complete_date");
    Termus.TERMUSStartDate = AppraisalHeaderRow.Field<DateTime>("termus_start_date");
    Termus.Status = AppraisalHeaderRow.Field<string>("termus_status");
    Termus.TERMUSVersion = AppraisalHeaderRow.Field<string>("termus_version");
    Termus.QuestionsAndAnswers = new Dictionary<string, string>();

    var RetrieveQuestionIdsCommand = new OracleCommand("select termus_question_id from tercons.termus_questions where termus_version=:termus_version");
    RetrieveQuestionIdsCommand.BindByName = true;
    RetrieveQuestionIdsCommand.Parameters.Add("termus_version", Termus.TERMUSVersion);
    var QuestionIdsDt = Database.GetDataTable(RetrieveQuestionIdsCommand);
    var QuestionIds = QuestionIdsDt.AsEnumerable().Select(r => r.Field<string>("termus_question_id"));
    //There's about 60 questions/answers, so this should result in 60 calls to the database. It'd be a good spot to combine to a single DB call, but left it this way so I could see if async would speed it up for learning purposes.
    foreach (var QuestionId in QuestionIds)
    {
        var RetrieveAnswersCommand = new OracleCommand("select termus_response from tercons.termus_responses where termus_id=:termus_id and termus_question_id=:questionid");
        RetrieveAnswersCommand.BindByName = true;
        RetrieveAnswersCommand.Parameters.Add("termus_id", TermusId);
        RetrieveAnswersCommand.Parameters.Add("questionid", QuestionId);
        var AnswersDt = Database.GetDataTable(RetrieveAnswersCommand);
        var Answer = AnswersDt.AsEnumerable().Select(r => r.Field<string>("termus_response")).SingleOrDefault();
        if (!String.IsNullOrEmpty(Answer))
        {
            Termus.QuestionsAndAnswers.Add(QuestionId, System.Net.WebUtility.HtmlDecode(Answer));
        }
    }
    return Termus;
}

public static DataTable GetDataTable(OracleCommand command)
{
    DataTable dt = new DataTable();
    using (var connection = GetDefaultOracleConnection())
    {
        command.Connection = connection;
        connection.Open();
        dt.Load(command.ExecuteReader());
    }
    return dt;
}

public static OracleConnection GetDefaultOracleConnection()
{
    return new OracleConnection(ConfigurationManager.ConnectionStrings[connectionstringname].ConnectionString);
}

60次迭代的结果是:

Non Async 18.4375460 seconds

Async     19.8092854 seconds

该测试的结果是一致的。无论我在AsyncPerformanceTest()操作中使用for循环进行了多少次迭代,异步内容的运行时间都比非异步运行时间慢约1秒。(我连续运行测试多次以解决JITter的预热问题。)我在做错什么,导致异步比非异步慢?我是否误解了有关编写异步代码的基本知识?

务实的

没有并发时,异步版本将始终比同步版本慢。它执行与非异步版本相同的所有工作,但是添加了少量开销来管理异步。

通过提高可用性,异步在性能方面是有利的。每个单独的请求都会变慢,但是如果您同时发出1000个请求,则异步实现将能够更快地处理它们(至少在某些情况下)。

发生这种情况是因为异步解决方案允许分配给处理请求的线程返回到池中并处理其他请求,而同步解决方案则迫使线程坐在那里并在等待异步操作完成时不执行任何操作。以允许释放线程来执行其他工作的方式来构造程序,会产生开销,但是好处是该线程能够执行其他工作。在您的程序中,线程没有其他工作要做,因此最终导致净损失。

本文收集自互联网,转载请注明来源。

如有侵权,请联系[email protected] 删除。

编辑于
0

我来说两句

0条评论
登录后参与评论

相关文章

来自分类Dev

在Haskell中,异步代码的运行速度比同步版本慢

来自分类Dev

为什么Hadoop被认为是I / O密集型的?

来自分类Dev

为什么异步I / O需要缓冲?

来自分类Dev

异步版本比非异步版本运行慢

来自分类Dev

异步I / O和性能

来自分类Dev

异步JavaScript和I / O

来自分类Dev

异步I / O和性能

来自分类Dev

为什么我的异步代码没有运行异步?

来自分类Dev

为什么异步I / O需要事件循环

来自分类Dev

为什么此TAP异步/等待代码比TPL版本慢?

来自分类Dev

为什么异步/等待不异步运行?

来自分类Dev

为什么异步会减慢独立的后续代码的速度?

来自分类Dev

异步I / O是否占用线程?

来自分类Dev

同步和异步I / O之间的区别

来自分类Dev

异步I / O是否占用线程?

来自分类Dev

java8 进行异步 I/O

来自分类Dev

如何异步处理子进程的 I/O?

来自分类Dev

具有异步功能的非阻塞I / O

来自分类Dev

React PHP如何处理异步非阻塞I / O?

来自分类常见问题

从非异步代码调用异步方法

来自分类Dev

异步编程,为什么不执行代码

来自分类Dev

为什么这个异步代码不停止?

来自分类Dev

为什么这个 Javascript 代码是异步的?

来自分类Dev

为什么异步并行队列最后运行

来自分类Dev

为什么SignInAsync是异步的?

来自分类Dev

为什么“创建”是异步的?

来自分类Dev

为什么.json()是异步的?

来自分类Dev

为什么SignInAsync是异步的?

来自分类Dev

为什么“创建”是异步的?