【发布时间】:2022-11-09 08:45:28
【问题描述】:
在比较 Windows 和 macOS 中完全相同的 .net 代码时,我遇到了一个有趣的性能问题。我不明白为什么会有如此显着的性能差异,我不确定最好的方法。
该代码适用于我一直在使用 Visual Studio for Mac 在 macOS 上开发的 .net (v6) 控制台应用程序 (C# v9)。这是一款等待用户输入的回合制游戏,仅在提示键盘输入之前重绘控制台窗口。我使用后备存储来执行此操作,并且只更新需要重新绘制的控制台窗口部分(通常只有几个字符)。结果,在 macOS 下性能似乎不错。
然后,我将代码复制到 Windows 并在 Visual Studio 2022 中重新编译。令我惊讶的是,性能非常差 - 无法使用。
因此,我使用 Stopwatch 类开始了一些简单的性能调查,从写入控制台窗口的方法开始:在 Windows 上,更新控制台窗口需要 98-108 毫秒。 macOS 上的相同代码始终被测量为花费 0 毫秒。
显然,0ms 值没有用处,因此为了获得更好的数字,我查看了秒表刻度而不是 ms,并很快确定这些不能直接比较:我在 Windows 上测量了大约 10134729 刻度时的 1000ms 线程延迟,但是macOS 上的 1018704390 滴答声。 MSDN 库说“秒表类使用的计时器取决于系统硬件和操作系统”(在 Windows 和 macOS 中,Stopwatch.IsHighResolutionTimer 为“真”)。假设这个比率应该延续到我在同一个应用程序中使用秒表类的所有其他性能测试(?),我们可以说 - 为了比较 macOS 和 Windows 之间的数字 - 我必须将 macOS 数字除以(大约)100 .
当我以刻度为控制台窗口更新计时时,我得到如下粗略的平均值:
- 窗口:988,000-1,020,000 滴答声
- macOS:595,000-780,000 刻 (请记住,将 macOS 除以 100 以与 Windows 进行比较,即非常粗略地170x性能差异)
笔记:
- 我在 VMWare Fusion 中以访客身份运行 Windows 10。 macOS 是主机。主机和来宾都不应受到资源限制。更新:我尝试在真实硬件上运行下面的最小可重现代码示例,我得到了一致的结果(Windows 比 macOS 慢得多)
- 我正在使用 80x25 控制台窗口进行测试。
- 我尝试在 Windows 中调整控制台窗口属性,但没有任何效果。缓冲区大小与控制台窗口大小相同。
- 应用程序以编程方式将控制台输出编码设置为 UTF8,将光标设置为“不可见”,并将 TreatControlCAsInput 设置为“真”。将所有这些作为默认设置没有任何区别。
- 我没有在 Windows 中使用“旧版控制台”。
- 我尝试在 Windows 下发布专门针对 Windows 和我的计算机体系结构的发布版本。没有明显的区别。
- Windows 中的调试版本针对“任何 CPU”。
- 如果我打开光标,我可以看到它在屏幕上“滑动”(在 Windows 中是从左到右,从上到下)。
这似乎不是我可以优化掉的那种差异(无论如何,我想理解它)。鉴于两个操作系统上的代码相同,什么可以解释如此显着的性能差异?有人遇到过这种情况么?
有问题的代码如下(两种方法):
private void FlushLine (int y)
{
ConsoleColor? lastForegroundColour = null;
ConsoleColor? lastBackgroundColour = null;
int lastX = -1, lastY = -1;
for (int x = 0; x < Math.Min (this.Width, this.currentLargestWindowWidth); ++x)
{
// write only when the current backing store is different from the previous backing store
if (ConsoleWindow.primary.characters[y][x] != ConsoleWindow.previous.characters[y][x]
|| ConsoleWindow.primary.foreground[y][x] != ConsoleWindow.previous.foreground[y][x]
|| ConsoleWindow.primary.background[y][x] != ConsoleWindow.previous.background[y][x])
{
// only change the current console foreground and/or background colour
// if necessary because it's expensive
if (!lastForegroundColour.HasValue || lastForegroundColour != ConsoleWindow.primary.foreground[y][x])
{
Console.ForegroundColor = ConsoleWindow.primary.foreground[y][x];
lastForegroundColour = ConsoleWindow.primary.foreground[y][x];
}
if (!lastBackgroundColour.HasValue || lastBackgroundColour != ConsoleWindow.primary.background[y][x])
{
Console.BackgroundColor = ConsoleWindow.primary.background[y][x];
lastBackgroundColour = ConsoleWindow.primary.background[y][x];
}
// only set the cursor position if necessary because it's expensive
if (x != lastX + 1 || y != lastY)
{
Console.SetCursorPosition(x, y);
lastX = x; lastY = y;
}
Console.Write(ConsoleWindow.primary.characters[y][x]);
ConsoleWindow.previous.foreground[y][x] = ConsoleWindow.primary.foreground[y][x];
ConsoleWindow.previous.background[y][x] = ConsoleWindow.primary.background[y][x];
ConsoleWindow.previous.characters[y][x] = ConsoleWindow.primary.characters[y][x];
}
}
}
public void FlushBuffer ()
{
int cursorX = Console.CursorLeft;
int cursorY = Console.CursorTop;
for (int y = 0; y < Math.Min (this.Height, this.currentLargestWindowHeight); ++y)
{
this.FlushLine (y);
}
Console.SetCursorPosition (cursorX, cursorY);
}
一个最小可重复的示例 - 用字母“A”填充控制台窗口
using System.Diagnostics;
Stopwatch stopwatch = new ();
stopwatch.Restart ();
Thread.Sleep (1000);
Debug.WriteLine ($"Thread Sleep 1000ms = {stopwatch.ElapsedTicks} ticks");
while (true)
{
stopwatch.Restart ();
for (int y = 0; y < Console.WindowHeight; ++y)
{
Console.SetCursorPosition (0, y);
for (int x = 0; x < Console.WindowWidth; ++x)
{
Console.Write ('A');
}
}
stopwatch.Stop ();
Debug.WriteLine ($"{stopwatch.ElapsedTicks}");
}
【问题讨论】:
-
你能加一个minimal reproducible example吗?
-
还可以尝试在“本机”Win 10 机器上运行该应用程序。从理论上讲,这可能是一个虚拟化问题。
-
我添加了一个最小的可重现示例,并通过在同事的开发计算机上进行测试,确认虚拟化不是一个因素 - Windows 下的性能不佳仍然存在。
-
有关的; google.com/search?q=+windows+console+Grand+Overhaul 但是 AFAIK C# 仍在使用旧的 api。
标签: c# .net macos performance console