繁体   English   中英

如何在多线程应用程序中达到100%的CPU使用率?

[英]How to achive 100% CPU usage in multithreaded application?

我有100个200MB的文本文件,我需要解析它们。 下面的程序加载文件并并行处理它们。 它可以为每个文件创建一个线程或为每个文件创建一个进程。 问题:如果我使用线程,它将永远不会使用100%CPU,并且需要更长的时间才能完成。

THREAD PER FILE
total time: 430 sec
CPU usage 15-20%
CPU frequency 1.2 GHz

PROCESS PER FILE
total time 100 sec
CPU usage 100%
CPU frequency 3.75 GHz

我将E5-1650 v3 Hexa-Core与HT一起使用,因此我一次处理12个文件。

如何实现线程的100%CPU使用率?

下面的代码不使用处理结果,因为它不会影响该问题。

using System;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Text;
using System.Threading;

namespace libsvm2tsv
{
    class Program
    {
        static void Main(string[] args)
        {
            var sw = Stopwatch.StartNew();

            switch (args[0])
            {
                case "-t": LoadAll(args[1], LoadFile); break;
                case "-p": LoadAll(args[1], RunChild); break;
                case "-f": LoadFile(args[1]); return;
            }

            Console.WriteLine("ELAPSED: {0} sec.", sw.ElapsedMilliseconds / 1000);
            Console.ReadLine();
        }

        static void LoadAll(string folder, Action<string> algorithm)
        {
            var sem = new SemaphoreSlim(12);
            Directory.EnumerateFiles(folder).ToList().ForEach(f=> {
                sem.Wait();
                new Thread(() => { try { algorithm(f); } finally { sem.Release(); } }).Start();
            });
        }

        static void RunChild(string file)
        {
            Process.Start(new ProcessStartInfo
            {
                FileName = Assembly.GetEntryAssembly().Location,
                Arguments = "-f \"" + file + "\"",
                UseShellExecute = false,
                CreateNoWindow = true
            })
            .WaitForExit();
        }

        static void LoadFile(string inFile)
        {
            using (var ins = File.OpenText(inFile))
                while (ins.Peek() >= 0)
                    ParseLine(ins.ReadLine());
        }

        static long[] ParseLine(string line)
        {
            return line
                .Split()
                .Skip(1)
                .Select(r => (long)(double.Parse(r.Split(':')[1]) * 1000))
                .Select(r => r < 0 ? -1 : r)
                .ToArray();
        }
    }
}

我有100个200MB的文本文件,我需要解析它们。

从磁盘读取数据或向磁盘写入数据的最快方法是依次进行操作,以最小化磁盘头寻找数据或将数据写入指定位置所需的时间。 因此,对单个磁盘执行并行IO会降低IO速率-根据实际的IO模式,它可能会大大降低速率。 可以顺序处理100 MB /秒的磁盘每秒只能移动20或30 KB ,同时并行读取/写入小块数据。

如果我正在优化这样一个过程,那么我就不必担心CPU利用率,我会首先优化IO吞吐量。 除非您要进行一些占用大量CPU的解析,否则您将受IO约束。 一旦优化了IO吞吐量,如果您获得100%的CPU利用率,那么您将受CPU限制。 如果您的设计可以很好地扩展,那么您可以添加CPU并可能运行得更快。

为了加快IO速度,首先需要最小化磁盘搜寻,尤其是在使用消费级廉价SATA驱动器的情况下。 有多种方法可以做到这一点。

首先,最简单-消除磁盘头。 将数据放在SSD上。 解决了问题,而无需编写复杂的,容易出错的优化代码。 使用软件使运行速度加快需要多少时间? 您必须设计,测试,调整,调试它,并且重要的是,使其保持正常运行。 这些都不是免费的。 其中一项重要成本是花时间使事情变得更快的机会成本-当您这样做时,您并没有解决任何其他问题。 更快的硬件没有这些成本。 在这种情况下,购买SSD,将其插入,您的速度就会更快。

但是,如果您真的想花几个星期或更长时间来优化您的处理软件,请按以下步骤进行:

  1. 将数据分布在多个磁盘上。 您无法快速对物理磁盘进行并行IO,因为磁盘头搜索会降低性能。 因此,请尽可能多地对不同的磁盘进行读取和写入。
  2. 对于每个磁盘,只有一个读取器或写入器线程或进程将数据馈送到工作池或写入该工作池提供的数据。
  3. 可调数量的工作线程/进程来进行实际的解析。

这样,您可以依次读取文件和写入输出数据,而不会与其他IO进程争用每个磁盘。

终于,我找到了瓶颈。 我正在使用string.Split来解析每一行数据中的数字,所以我得到了数十亿个短字符串。 这些字符串放在堆中。 由于所有线程共享单个堆,因此内存分配是同步的。 由于进程具有单独的堆-不会发生同步,并且工作很快。 那是问题的根源。 因此,我重写了使用IndexOf而不是Split进行的解析,并且线程开始比单独的进程执行得更好。 就像我期望的那样。

由于.NET没有默认工具可以从字符串中的特定位置解析实数,因此我使用了以下一种工具: https : //codereview.stackexchange.com/questions/75791/optimize-custom-double-parse进行了少量修改。

using System;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Threading;
using System.Threading.Tasks;

namespace libsvm2tsv
{
    class Program
    {

        static void Main(string[] args)
        {
            var sw = Stopwatch.StartNew();

            switch (args[0])
            {
                case "-t": LoadAll(args[1], LoadFile); break;
                case "-p": LoadAll(args[1], RunChild); break;
                case "-f": LoadFile(args[1]); return;
            }

            Console.WriteLine("ELAPSED: {0} sec.", sw.ElapsedMilliseconds / 1000);
            Console.ReadLine();
        }

        static void LoadAll(string folder, Action<string> algorithm)
        {
            Parallel.ForEach(
                Directory.EnumerateFiles(folder),
                new ParallelOptions { MaxDegreeOfParallelism = 12 },
                f => algorithm(f));
        }

        static void RunChild(string file)
        {
            Process.Start(new ProcessStartInfo
            {
                FileName = Assembly.GetEntryAssembly().Location,
                Arguments = "-f \"" + file + "\"",
                UseShellExecute = false,
                CreateNoWindow = true
            })
            .WaitForExit();
        }

        static void LoadFile(string inFile)
        {
            using (var ins = File.OpenText(inFile))
                while (ins.Peek() >= 0)
                    ParseLine(ins.ReadLine());
        }

        static long[] ParseLine(string line)
        {
            // first, count number of items
            var items = 1;
            for (var i = 0; i < line.Length; i++)
                if (line[i] == ' ') items++;

            //allocate memory and parse items
            var all = new long[items];
            var n = 0;
            var index = 0;
            while (index < line.Length)
            {
                var next = line.IndexOf(' ', index);
                if (next < 0) next = line.Length;
                if (next > index)
                {
                    var v = (long)(parseDouble(line, line.IndexOf(':', index) + 1, next - 1) * 1000);
                    if (v < 0) v = -1;
                    all[n++] = v;

                }
                index = next + 1;
            }

            return all;
        }

        private readonly static double[] pow10Cache;
        static Program()
        {
            pow10Cache = new double[309];

            double p = 1.0;
            for (int i = 0; i < 309; i++)
            {
                pow10Cache[i] = p;
                p /= 10;
            }
        }

        static double parseDouble(string input, int from, int to)
        {
            long inputLength = to - from + 1;
            long digitValue = long.MaxValue;
            long output1 = 0;
            long output2 = 0;
            long sign = 1;
            double multiBy = 0.0;
            int k;

            //integer part
            for (k = 0; k < inputLength; ++k)
            {
                digitValue = input[k + from] - 48; // '0'

                if (digitValue >= 0 && digitValue <= 9)
                {
                    output1 = digitValue + (output1 * 10);
                }
                else if (k == 0 && digitValue == -3 /* '-' */)
                {
                    sign = -1;
                }
                else if (digitValue == -2 /* '.' */ || digitValue == -4 /* ',' */)
                {
                    break;
                }
                else
                {
                    return double.NaN;
                }
            }

            //decimal part
            if (digitValue == -2 /* '.' */ || digitValue == -4 /* ',' */)
            {
                multiBy = pow10Cache[inputLength - (++k)];

                for (; k < inputLength; ++k)
                {
                    digitValue = input[k + from] - 48; // '0'

                    if (digitValue >= 0 && digitValue <= 9)
                    {
                        output2 = digitValue + (output2 * 10);
                    }
                    else
                    {
                        return Double.NaN;
                    }
                }

                multiBy *= output2;
            }

            return sign * (output1 + multiBy);
        }
    }
}

我会考虑用Parallel.ForEach替换ForEach并删除您对Threads的显式使用。 使用https://stackoverflow.com/a/5512363/34092设置将其限制为的线程数。

static void LoadAll(string folder, Action<string> algorithm)
{
    Parallel.ForEach(Directory.EnumerateFiles(folder), algorithm);
}

正如其他人指出的那样,IO最终可能会成为瓶颈,并且获得100%CPU使用率确实无关紧要。 不过,我觉得它们缺少了一些东西:与线程相比,使用进程确实可以获得更高的吞吐量,这意味着IO并不是唯一的瓶颈。 原因是CPU的进程运行频率更高,并且您希望它在不等待IO时以峰值速度运行! 那么,你该怎么做呢?

最简单的方法是从电源选项中手动设置电源配置文件。 编辑电源选项,并将最小和最大处理器状态都设置为100%。 那应该做的。

如果要从程序中执行此操作,请查看如何禁用动态频率缩放? 可能有一个不使用本机代码的.NET相似API,但我现在找不到它。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM