Process.Start() 比在控制台执行显著慢

12

我使用Process.Start()执行一个.exe时,遇到了性能问题。在.NET中执行的时间大约比在控制台中执行的时间慢了5倍。这是什么原因造成的?下面是一个测试程序:

  public static void Main(string[] argv)
  {       
     for (int i = 0; i < 10; i++)
     {
        ProcessStartInfo psi = new ProcessStartInfo(ExePath, Args);
        Process ps = new Process {StartInfo = psi};
        Stopwatch sw = Stopwatch.StartNew();
        ps.Start();
        ps.WaitForExit();
        sw.Stop();
        Console.WriteLine(i+" Elapsed time: " + sw.ElapsedMilliseconds + "ms.");
        Thread.Sleep(1000);
     }
  }

结果如下:

 0 Elapsed time 4310ms.
 1 Elapsed time 4330ms.
 2 Elapsed time 4280ms.
 ...

在cmd窗口运行它会几乎立即返回(执行时间小于1秒)。尝试使用控制台计时:

> powershell Measure-Command { cmd /c start /wait %EXE% %ARGS% } 

执行时间约为750ms,比原来快了5-6倍。我不确定是否正确,但750ms似乎是一个可能的执行时间。

一开始我读取标准输出并认为与此有关,参见例如Process takes longer to finish than in CMD等类似问题。显然,在简单的测试程序中,我现在没有读取任何输出,只是执行。

我已经排除了以下可能导致执行时间无差别的原因:

  • 调试器/无调试器
  • .NET主机进程的Debug/Release版本
  • 工作目录
  • 主机.NET进程的平台Any/x86/x64 (exe是本地x64)
  • UseShellExecute true/false

我知道可执行文件(它是Rust语句完成工具“racer”https://github.com/phildawes/racer)会打开很多文件。当从.NET主机来时,这是否会有影响,例如在安全性方面导致了减速?还有什么其他原因会导致巨大的性能差异?


2
你正在计时的不仅是开始过程。 - leppie
2
@leppie那会是什么?我在那儿没有看到更多的东西。 - usr
3
我无法想象创建这两个对象会花费多秒钟的时间。更像是微秒级别。 - usr
2
如果创建进程(对象)需要3秒钟或更长时间,那么这将是我问题的答案。在软件之外创建进程的差异微不足道,这是可以预料的。正在编辑代码示例。 - Anders Forsgren
4
哎呀,你说对了!这是二进制文件的略微不同版本,性能差异巨大。真是自惭形秽。我希望你有一个我可以接受的答案,至少可以给你点数。感谢你的帮助! - Anders Forsgren
显示剩余18条评论
3个回答

4
在这种情况下,运行时间的差异是由于执行的(racer.exe)文件版本不同,并且与一个正在从.NET进程执行,而另一个正在从命令行执行无关。
当从.NET运行可执行文件时,不应有任何差异,因为它只是使用系统调用来执行程序。

1
就是这样。任何性能差异都应该只是由于输出流的读取方式(缓冲/阻塞)而引起的。 - Anders Forsgren
其实我也很惊讶遇到了这个问题。基本上是通过process.start启动exiftool进程,但是同样的版本在内部更新一个文件需要更长时间,而在cmd中只需要一秒钟。肯定有什么问题。如果需要,我可以提供更多信息。 - user1715559

2
    ps.WaitForExit();

这是一个关键的陈述。您的cmd.exe命令不等待进程终止。要比较苹果和橙子,请删除WaitForExit()或使用"cmd /c start /wait %EXE% %ARGS%"。

换句话说,您没有测量启动进程所需的时间,而是测量了进程运行的时间。


谢谢,这只是微小的差别。它仍然在<1秒内返回。我会更新问题,以免混淆。 - Anders Forsgren
PowerShell 本身会等待吗?可能不会,通过删除 WaitForExit() 语句来获取“快速”版本。而 Thread.Sleep() 是无意义的。 - Hans Passant
我真正想做的是获取时间,直到它完成,即(它将一些输出写入stdout,然后退出)。如果我在控制台上“凭眼”操作,我至少可以看到需要多长时间才能将stdout写入并返回给我控制台,本机大约需要1秒,而上述.NET包装器则需要> 4秒。查看后者的procmon,我可以看到从.NET调用本机的实际执行(其事件的时间戳)exe需要超过3秒。如果我没有WaitForExit(),它将报告一些微不足道的时间,然后等待4秒并写入stdout。 - Anders Forsgren
这与您启动的程序有关。如果不知道任何相关信息,我无法做出任何合理的猜测,但许多程序需要将ProcessStartInfo.WorkingDirectory属性设置为包含.exe文件的同一目录才能正常工作。 - Hans Passant
是的,至少在工作目录和环境变量方面,我在相同的环境中运行。 :( - Anders Forsgren
你是完全正确的:这两个二进制文件的时间不完全相同(以某种我尚未理解但将努力弄清楚的方式)。想象一下,经过所有那些麻烦修复相等的环境,然后对完全独立的程序进行基准测试。我讨厌编程。抱歉浪费了您的时间。 - Anders Forsgren

0
一个非常晚的评论,但今天是2023年,似乎情况仍然如此。在nvidia jetson上运行,使用4GB内存时,从C#运行速度慢了3倍...
我已经在Python中运行了一些平台函数,告诉我有多少内存等等。这里是一段代码片段。
我现在没有时间解决问题,使代码正确执行是一个巨大的挑战,因为大多数示例,甚至codegpt都无法生成性能和准确性都不错的ssim的最佳代码。
也许有人看到这个评论,并且有解决这个问题的绝招。
ProcessStartInfo processStartInfo = new(_pathHelper.PythonPath.Value, arguments)
    {
        WorkingDirectory = workingDirectory,
        RedirectStandardOutput = true,
        RedirectStandardError = true,
    };

    using var proc = Process.Start(processStartInfo)!;
    proc.BeginOutputReadLine();
    proc.BeginErrorReadLine();

    proc.OutputDataReceived += (_, e) => streamStdOutWriter.WriteIfNotNull(e.Data);
    proc.ErrorDataReceived += (_, e) => errors.AddIfNotNull(e.Data);

    try
    {
        _logger.LogInformation($"{_pathHelper.PythonPath.Value} {arguments}");
        await proc.WaitForExitAsync(two.Token);

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接