[英]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,将其插入,您的速度就会更快。
但是,如果您真的想花几个星期或更长时间来优化您的处理软件,请按以下步骤进行:
这样,您可以依次读取文件和写入输出数据,而不会与其他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.