【译】Copilot Profiler Agent —— 分析任务交由 AI,应用性能不受影响
在 Visual Studio 2026 中,我们推出了 Copilot Profiler Agent,这是一款新的人工智能驱动的助手,可帮助您分析和优化代码中的性能瓶颈。通过将 GitHub Copilot 的功能与 Visual Studio 的性能分析器相结合,您现在可以用自然语言询问有关性能的问题,深入了解热点路径,并快速发现优化机会。让我们来看一个真实的例子,了解这款工具如何帮助您实现有
在 Visual Studio 2026 中,我们推出了 Copilot Profiler Agent,这是一款新的人工智能驱动的助手,可帮助您分析和优化代码中的性能瓶颈。通过将 GitHub Copilot 的功能与 Visual Studio 的性能分析器相结合,您现在可以用自然语言询问有关性能的问题,深入了解热点路径,并快速发现优化机会。让我们来看一个真实的例子,了解这款工具如何帮助您实现有意义的性能改进。
对实际项目进行基准测试
为了展示 Copilot Profiler Agent 的功能,让我们对一个广受欢迎的开源项目 CsvHelper 进行优化。您可以按照以下步骤操作:克隆我的代码仓库分支,然后通过“git checkout 435ff7c”命令切换到我修复之前的版本,我们将在下文详细介绍该修复。
在我之前的一篇博客文章中,我添加了一个 CsvHelper.Benchmarks 项目,其中包含一个用于读取 CSV 记录的基准测试。这次我想看看我们是否可以优化 CSV 记录的写入。通常,我会通过为想要优化的代码创建基准测试来开始这项研究,不过虽然我们仍然会这样做,但我们可以让 Copilot 来承担这些繁重的工作。在 Copilot 聊天窗口中,我可以问@Profiler “帮我为 #WriteRecords 方法编写一个基准测试”。@Profiler 让我们直接与 Copilot Profiler Agent 对话,而 #WriteRecords 则明确告诉它我们要进行基准测试的方法。

从这里开始,Copilot 着手创建我们的新基准测试,它会询问我们是否可以安装分析器的 NuGet 包,以便在运行基准测试时从中提取信息。它还会根据找到的任何现有基准测试来构建新的基准测试模型,因此生成的基准测试与我们已经编写的非常相似,从而保持与存储库风格的一致性。最后,它会启动构建过程,以确保一切正常。

完成后,它会提供一些有用的后续提示来启动调查。我们可以点击其中一个来展开调查,不过我想对基准测试做些细微的修改。

我对基准测试做了些调整,增加了几个供我们写入的字段,这里具体是 2 个整数字段和 2 个字符串字段。在为这篇博客撰写内容之前,我最初让 Copilot 来做这件事时,它每次都是写入一个新的内存流,而不是同一个内存流。写入同一个内存流或许是更好的做法,这次算你赢了 Copilot,但在我给 CsvHelper 提交的最初的拉取请求中,我并没有这么做,不过应该也没什么问题。
public class BenchmarkWriteCsv
{
private const int entryCount = 10000;
private readonly List records = new(entryCount);
public class Simple
{
public int Id1 { get; set; }
public int Id2 { get; set; }
public string Name1 { get; set; }
public string Name2 { get; set; }
}
[GlobalSetup]
public void GlobalSetup()
{
var random = new Random(42);
var chars = new char[10];
string getRandomString()
{
for (int i = 0; i < 10; ++i)
chars[i] = (char)random.Next('a', 'z' + 1);
return new string(chars);
}
for (int i = 0; i < entryCount; ++i)
{
records.Add(new Simple
{
Id1 = random.Next(),
Id2 = random.Next(),
Name1 = getRandomString(),
Name2 = getRandomString(),
});
}
}
[Benchmark]
public void WriteRecords()
{
using var stream = new MemoryStream();
using var streamWriter = new StreamWriter(stream);
using var writer = new CsvHelper.CsvWriter(streamWriter, CultureInfo.InvariantCulture);
writer.WriteRecords(records);
streamWriter.Flush();
}
}
深入了解基准测试
现在开始分析,我既可以让 Profiler Agent 运行基准测试,也可以直接点击后续提示“@Profiler Run the benchmark and analyze results”。从这里开始,Copilot 会编辑我的主方法,乍一看可能有些奇怪,但查看所做的更改后,我发现它为了使用 BenchmarkSwitcher 进行了必要的修改,这样就能选择要运行的基准测试了:
static void Main(string[] args)
{
// Use assembly-wide discovery so all benchmarks in this assembly are run,
// including the newly added BenchmarkWriteRecords.
_ = BenchmarkSwitcher.FromAssembly(typeof(BenchmarkEnumerateRecords).Assembly).Run(args);
}
然后它启动了一次基准测试运行,完成后会给我一个诊断会话,我可以在其中开始调查。
使用 Copilot Profiler Agent 来查找瓶颈
现在到了令人兴奋的部分。运行基准测试后,Profiler Agent 会分析跟踪信息,并突出显示时间的消耗位置。我可以向 Profiler Agent 询问有关跟踪的问题,让它解释代码为什么运行缓慢,或者某些优化为何会有帮助。它已经指出,大部分时间都花在委托编译和调用上,这是针对 CSV 记录中的每个字段进行的。对于一个有 4 个字段、被写入 10,000 次的记录来说,这意味着会有 40,000 次委托调用。每次调用都有开销,而这在分析器中显示为一个热点路径。

我可以问 Profiler Agent:“我怎样才能减少委托调用的开销?”或者“为什么委托调用很慢?”,而它会像一位耐心的老师一样解释相关概念并提出修复建议。
实施修复方案
我点击 @Profiler Optimize library to produce a single compiled write delegate (reduce multicast invokes),看看会得到什么结果。 Profiler Agent 会对 ObjectRecordWriter 进行编辑,我可以在聊天窗口中点击它来查看所做更改的差异。
查看当前的实现,代码构建了一个委托列表,每个字段对应一个委托:
var delegates = new List<Action>();
foreach (var memberMap in members)
{
// ... field writing logic ...
delegates.Add(Expression.Lambda<Action>(writeFieldMethodCall, recordParameter).Compile());
}
var action = CombineDelegates(delegates) ?? new Action((T parameter) => { });
return action;
问题在于 CombineDelegates 会创建一个多播委托,该委托会依次单独调用每个独立的委托。相反,Profiler Agent 建议我们在编译前使用 Expression.Block 来组合所有表达式:
var expressions = new List<Expression>(members.Count);
foreach (var memberMap in members)
{
// ... field writing logic ...
expressions.Add(writeFieldMethodCall);
}
if (expressions.Count == 0)
{
return new Action<T>((T parameter) => { });
}
// Combine all field writes into a single block
var block = Expression.Block(expressions);
return Expression.Lambda<Action<T>>(block, recordParameter).Compile();
这一改动虽小却很精妙:我们没有创建多个委托并按顺序调用它们,而是创建了一个包含所有字段写入操作的单个块表达式,然后对其进行一次编译。现在,当我们为每条记录调用委托时,所有字段都会在一次调用中完成写入,不存在额外的委托开销。
衡量影响
做出这一更改后,Copilot 会自动重新运行基准测试以衡量改进效果。结果显示,在此次使用分析器的运行中,性能大约提升了 24%。我们之前为 CsvHelper 准备的分阶段拉取请求显示性能提升了约 15%。CPU 分析器证实,我们已经消除了委托调用的开销,对于每条有 4 个字段的 10,000 条记录,之前需要进行 40,000 次委托调用,而现在只需要 10,000 次委托调用。

对于一个已经经过大量优化的库来说,这是一场意义重大的胜利。对于那些编写包含许多字段的大型 CSV 文件的应用程序而言,这一改进直接意味着 CPU 时间的减少和处理速度的提升。而且,由于 CsvHelper 的下载量高达数百万次,这项优化惠及了大量用户。在此基础上,我继续推进并提交了拉取请求,不过 Copilot 贴心地提供了更多关于类型转换和 ShouldQuote 逻辑的后续提示,以便我能进一步提升性能。
Copilot Profiler Agent 的价值
这个工作流程之所以强大,是因为它将 Visual Studio Profiler 提供的精确性能数据与 Copilot 的分析和代码生成能力相结合。您无需手动深入研究 CPU 跟踪并试图理解热点路径的含义,而是可以提出自然语言问题,获取可执行的见解,并快速测试想法。
该 Agent 不仅会告诉您哪些部分运行缓慢,还会帮助您理解其缓慢的原因,并提出具体的修复方法。在这种情况下,它识别出委托调用的开销是瓶颈,并建议采用 Expression.Block 优化,这正是解决该问题的正确方案。它甚至还重新运行了基准测试来确认该优化的效果!
让我们知道您的想法
我们已经展示了 Copilot Profiler Agent 如何帮助您处理实际项目,通过自然语言查询识别性能瓶颈,并在数据支持下做出有意义的改进。当您能够就性能数据提出问题并获得智能答案时,测量/更改/测量的循环会变得快得多。我们很想听听您的想法!
原文链接:https://devblogs.microsoft.com/visualstudio/delegate-the-analysis-not-the-performance/
更多推荐


所有评论(0)