【问题标题】:Fast reading of console input快速读取控制台输入
【发布时间】:2015-10-26 09:28:40
【问题描述】:

我需要从控制台的标准输入流快速读取数据。输入由 100.000 行组成,每行 20 个字符(200 万个字符);用户从剪贴板粘贴它。我的程序运行大约 3 分钟(非常 很慢;目标是 10 秒)。它看起来像:

var inputData = new string[100000]; // 100.000 rows with 20 chars
for (int i = 0; i < 100000; i++) // Cycle duration is about 3 minutes...
{
    inputData[i] = Console.ReadLine();
}
// some processing...

我尝试了什么:

  1. 直接:Console.Read,Console.ReadKey——结果相同

  2. Console.In:Read()、ReadLine()、ReadAsync()、ReadLineAsync()、ReadBlock(具有各种块大小)、ReadBlockAsync( )、ReadToEnd()、ReadToEndAsync() - 结果相同

  3. 具有不同缓冲区和块大小的新 StreamReader(Console.OpenStandardInput(buffer))- 结果相同

  4. 开始阅读时隐藏控制台窗口,阅读结束时显示- 加速 10%

  5. 我尝试从文件中获取输入数据 - 它运行完美且快速。但我需要阅读 __ConsoleStream。

我注意到,在输入读取过程中 - 进程 conhost.exe 主动使用处理器。

如何加快输入的读取速度?

更新:

  1. 增加/减少 Console.BufferHeight 和 Console.BufferWidth 无效

  2. ReadFilemsdn也慢。但我注意到一个有趣的事实

    ReadFile(handle, buffer, bufferSize, out bytesCount, null);
    // bufferSize may be very big, but buffer obtains no more than one row (with \r\n).
    // So, it seems that data passed into InputStream row-by-row syncroniously.
    

【问题讨论】:

  • inputData = Console.ReadLine(); 无法编译,剪贴板究竟如何适应?
  • 阅读 20 MB 的文本需要不到一秒钟的时间。
  • 为什么不直接从剪贴板读取数据呢? stackoverflow.com/questions/3840080/…
  • 不知道玩BufferHeight会不会有什么改变。
  • @HenkHolterman,对不起,必须是inputData[i] = Console.ReadLine();

标签: c# .net input stream console


【解决方案1】:

在您的场景中,尝试显示插入符号会浪费大量时间。您可以禁用在 Windows 中显示的插入符号(我不知道如何在其他平台上执行此操作)。

不幸的是,.NET 没有公开必要的 API(至少在 4.6.1 中)。所以你需要以下 native 方法/常量:

internal class NativeMethods
{
    [DllImport("kernel32.dll", SetLastError = true)]
    internal static extern bool SetConsoleMode(IntPtr hConsoleHandle, int mode);

    [DllImport("kernel32.dll", SetLastError = true)]
    internal static extern bool GetConsoleMode(IntPtr hConsoleHandle, out int mode);

    [DllImport("kernel32.dll", SetLastError = true)]
    internal static extern IntPtr GetStdHandle(int nStdHandle);

    internal const int STD_INPUT_HANDLE = -10;
    internal const int ENABLE_ECHO_INPUT = 0x0004;
}

并在从剪贴板接收数据之前以下列方式使用它们:

var handle = NativeMethods.GetStdHandle(NativeMethods.STD_INPUT_HANDLE);
int mode; 
NativeMethods.GetConsoleMode(handle, out mode);
mode &= ~NativeMethods.ENABLE_ECHO_INPUT; // disable flag
NativeMethods.SetConsoleMode(handle, mode);

当您完成接收剪贴板数据时,不要忘记恢复控制台模式标志。我希望它会减少你的性能问题。 有关控制台模式的更多信息,请访问GetConsoleMode

进一步的优化尝试包括:

  • 重写没有锁的控制台读取代码(因为它在 .NET) 并确保没有任何线程可以与控制台一起使用 片刻。相当昂贵的任务。
  • 尝试找到增加标准输入缓冲区大小的方法。但我不确定这是否可能。
  • 不要忘记在没有调试的情况下在发布版本中进行测试 %)

【讨论】:

    【解决方案2】:

    您在这里的主要减速是 Console.Read() 和 Console.ReadLine() 都在屏幕上“回显”您的文本 - 写入文本的过程会减慢您的速度。那么,您要使用的是 Console.Readkey(true),它不会回显粘贴的文本。这是一个在大约 1 秒内写入 100,000 个字符的示例。它可能需要根据您的目的进行一些修改,但我希望它足以为您提供图片。干杯!

    public void begin()
    
        {   List<string> lines = new List<string>();
            string line = "";
            Console.WriteLine("paste text to begin");
            int charCount = 0;
            DateTime beg = DateTime.Now;
            do
            {
                Chars = Console.ReadKey(true);
                if (Chars.Key == ConsoleKey.Enter)
                {
                    lines.Add(line);
                    line = "";
                }
                else
                {
                    line += Chars.KeyChar;
                    charCount++;
                }
    
    
            } while (charCount < 100000);
            Console.WriteLine("100,000 characters ("+lines.Count.ToString("N0")+" lines) in " + DateTime.Now.Subtract(beg).TotalMilliseconds.ToString("N0")+" milliseconds");
    
        }
    

    我在一台机器上粘贴了一个 5 MB 的文件,其中包含长行文本,所有内核都在做其他事情(99% 的 CPU 负载)并在 1.87 秒内在 1,600 行中获取 100,000 个字符。

    【讨论】:

    • 我相信您已经意识到文件上传和/或 IO 比这快很多......从性能角度来看,这是一个相对较差的选择。
    【解决方案3】:

    使用原生 WinApi 函数:

    1. 获取输入句柄:GetStdHandlemsdn
    2. ReadFile(而不是ReadLinemsdn读取22个字节(带尾行/n/r)

    在 C# 中使用 WinApi 的示例:http://www.pinvoke.net/

    【讨论】:

    • 最后一个想法: - 通过一次 ReadFile 调用读取所有输入到一个内存缓冲区; - 不要使用字符串数组 - 使用一个内存缓冲区(可能会因为字符串对象创建时间过长而导致性能下降)。
    【解决方案4】:

    我认为您不需要维护秩序?如果是这样,请结合使用 Parallel 和 partitioner 类,因为您正在执行小任务:

    例如见When to use Partitioner class?

    这意味着您必须将数据类型更改为 ConcurrentBagConcurrentDictionary

    【讨论】:

      【解决方案5】:

      为什么不使用

      Parallel.For
      

      从控制台读取多线程? 如果没有,请尝试使用

      将其直接从剪贴板中拉出

      https://msdn.microsoft.com/en-us/library/kz40084e(v=vs.110).aspx

      【讨论】:

      • 你为什么会因为我只是给出一个想法而投反对票?
      猜你喜欢
      • 1970-01-01
      • 2017-07-20
      • 1970-01-01
      • 2017-05-08
      • 2012-08-27
      • 1970-01-01
      • 1970-01-01
      • 2011-02-14
      • 1970-01-01
      相关资源
      最近更新 更多