- c - 在位数组中找到第一个零
- linux - Unix 显示有关匹配两种模式之一的文件的信息
- 正则表达式替换多个文件
- linux - 隐藏来自 xtrace 的命令
众所周知,如果您从磁盘读取数据,您将受到 IO 限制,并且您可以比从磁盘读取数据更快地处理/解析读取的数据。
但是这种常识(神话?)并没有反射(reflect)在我的测试中。当我读取一个文本文件时,每行中用空格分隔的 double 和 int 都比我的物理磁盘速度慢得多(因子 6)。
文本文件看起来像这样
1,1 0
2,1 1
3,1 2
Did native read 179,0MB in 0,4s, 484,2MB/s
Did read 10.000.000 lines in 1,6s, 112,7MB/s
Did parse and read unsafe 179,0MB in 2,3s, 76,5MB/s
Did parse and read unsafe char buf 179,0MB in 2,8s, 63,5MB/s
Did read and parse 179,0MB in 9,3s, 19,3MB/s
using System;
using System.Collections.Generic;
using System.Text;
using System.IO;
using System.Diagnostics;
using System.Runtime.InteropServices;
using Microsoft.Win32.SafeHandles;
using System.ComponentModel;
namespace IOBound
{
class Program
{
static void Main(string[] args)
{
string data = @"C:\Source\IOBound\NumericData.txt";
if (!File.Exists(data))
{
CreateTestData(data);
}
int MB = (int) (new FileInfo(data).Length/(1024*1024));
var sw = Stopwatch.StartNew();
uint bytes = ReadFileIntoByteBuffer(data);
sw.Stop();
Console.WriteLine("Did native read {0:F1}MB in {1:F1}s, {2:F1}MB/s",
bytes/(1024*1024), sw.Elapsed.TotalSeconds, MB / sw.Elapsed.TotalSeconds);
sw = Stopwatch.StartNew();
int n = CountLines(data);
sw.Stop();
Console.WriteLine("Did read {0:N0} lines in {1:F1}s, {2:F1}MB/s",
n, sw.Elapsed.TotalSeconds, MB / sw.Elapsed.TotalSeconds);
sw = Stopwatch.StartNew();
ParseLinesUnsafe(data);
sw.Stop();
Console.WriteLine("Did parse and read unsafe {0:F1}MB in {1:F1}s, {2:F1}MB/s",
MB, sw.Elapsed.TotalSeconds, MB / sw.Elapsed.TotalSeconds);
sw = Stopwatch.StartNew();
ParseLinesUnsafeCharBuf(data);
sw.Stop();
Console.WriteLine("Did parse and read unsafe char buf {0:F1}MB in {1:F1}s, {2:F1}MB/s",
MB, sw.Elapsed.TotalSeconds, MB / sw.Elapsed.TotalSeconds);
sw = Stopwatch.StartNew();
ParseLines(data);
sw.Stop();
Console.WriteLine("Did read and parse {0:F1}MB in {1:F1}s, {2:F1}MB/s",
MB, sw.Elapsed.TotalSeconds, MB / sw.Elapsed.TotalSeconds);
}
private unsafe static uint ReadFileIntoByteBuffer(string data)
{
using(var stream = new FileStream(data, FileMode.Open))
{
byte[] buf = new byte[200 * 1024 * 1024];
fixed(byte* pBuf = &buf[0])
{
uint dwRead = 0;
if (ReadFile(stream.SafeFileHandle, pBuf, 200 * 1000 * 1000, out dwRead, IntPtr.Zero) == 0)
{
throw new Win32Exception();
}
return dwRead;
}
}
}
private static int CountLines(string data)
{
using (var reader = new StreamReader(data))
{
string line;
int count = 0;
while ((line = reader.ReadLine()) != null)
{
count++;
}
return count;
}
}
unsafe private static void ParseLinesUnsafeCharBuf(string data)
{
var dobules = new List<double>();
var ints = new List<int>();
using (var reader = new StreamReader(data))
{
double d = 0;
long a = 0, b = 0;
int i = 0;
char[] buffer = new char[10*1000*1000];
int readChars = 0;
int startIdx = 0;
fixed(char *ln = buffer)
{
while ((readChars = reader.Read(buffer, startIdx, buffer.Length - startIdx)) != 0)
{
char* pEnd = ln + readChars + startIdx;
char* pCur = ln;
char* pLineStart = null;
while (pCur != pEnd)
{
a = 0;
b = 0;
while (pCur != pEnd && *pCur == '\r' || *pCur == '\n')
{
pCur++;
}
pLineStart = pCur;
while(pCur != pEnd && char.IsNumber(*pCur))
{
a = a * 10 + (*pCur++ - '0');
}
if (pCur == pEnd || *pCur == '\r')
{
goto incompleteLine;
}
if (*pCur++ == ',')
{
long div = 1;
while (pCur != pEnd && char.IsNumber(*pCur))
{
b += b * 10 + (*pCur++ - '0');
div *= 10;
}
if (pCur == pEnd || *pCur == '\r')
{
goto incompleteLine;
}
d = a + ((double)b) / div;
}
else
{
goto skipRest;
}
while (pCur != pEnd && char.IsWhiteSpace(*pCur))
{
pCur++;
}
if (pCur == pEnd || *pCur == '\r')
{
goto incompleteLine;
}
i = 0;
while (pCur != pEnd && char.IsNumber(*pCur))
{
i = i * 10 + (*pCur++ - '0');
}
if (pCur == pEnd)
{
goto incompleteLine;
}
dobules.Add(d);
ints.Add(i);
continue;
incompleteLine:
startIdx = (int)(pEnd - pLineStart);
Buffer.BlockCopy(buffer, (int)(pLineStart - ln) * 2, buffer, 0, 2 * startIdx);
break;
skipRest:
while (pCur != pEnd && *pCur != '\r')
{
pCur++;
}
continue;
}
}
}
}
}
unsafe private static void ParseLinesUnsafe(string data)
{
var dobules = new List<double>();
var ints = new List<int>();
using (var reader = new StreamReader(data))
{
string line;
double d=0;
long a = 0, b = 0;
int ix = 0;
while ((line = reader.ReadLine()) != null)
{
int len = line.Length;
fixed (char* ln = line)
{
while (ix < len && char.IsNumber(ln[ix]))
{
a = a * 10 + (ln[ix++] - '0');
}
if (ln[ix] == ',')
{
ix++;
long div = 1;
while (ix < len && char.IsNumber(ln[ix]))
{
b += b * 10 + (ln[ix++] - '0');
div *= 10;
}
d = a + ((double)b) / div;
}
while (ix < len && char.IsWhiteSpace(ln[ix]))
{
ix++;
}
int i = 0;
while (ix < len && char.IsNumber(ln[ix]))
{
i = i * 10 + (ln[ix++] - '0');
}
dobules.Add(d);
ints.Add(ix);
}
}
}
}
private static void ParseLines(string data)
{
var dobules = new List<double>();
var ints = new List<int>();
using (var reader = new StreamReader(data))
{
string line;
char[] sep = new char[] { ' ' };
while ((line = reader.ReadLine()) != null)
{
var parts = line.Split(sep);
if (parts.Length == 2)
{
dobules.Add( double.Parse(parts[0]));
ints.Add( int.Parse(parts[1]));
}
}
}
}
static void CreateTestData(string fileName)
{
FileStream fstream = new FileStream(fileName, FileMode.Create);
using (StreamWriter writer = new StreamWriter(fstream, Encoding.UTF8))
{
for (int i = 0; i < 10 * 1000 * 1000; i++)
{
writer.WriteLine("{0} {1}", 1.1d + i, i);
}
}
}
[DllImport("kernel32.dll", SetLastError = true)]
unsafe static extern uint ReadFile(SafeFileHandle hFile, [Out] byte* lpBuffer, uint nNumberOfBytesToRead, out uint lpNumberOfBytesRead, IntPtr lpOverlapped);
}
}
最佳答案
所以这里有几个问题。其他人已经评论了 Windows 的 IO 缓存以及实际的硬件缓存,所以我将不理会它。
另一个问题是您测量 read() + parse() 的组合操作,并将其与 read() 的速度进行比较。本质上,您需要意识到 A + B 总是大于 A(假设非负)这一事实。
因此,要确定您是否受 IO 限制,您需要了解读取文件需要多长时间。你已经做到了。在我的机器上,您的测试运行时间约为 220 毫秒以读取文件。
现在您需要测量解析这么多不同的字符串需要多长时间。这有点难以隔离。因此,假设我们将它们放在一起并从解析时间中减去读取所需的时间。此外,我们不是要衡量您对数据的处理,而只是分析,所以扔掉 List 和 List,让我们进行分析。在我的机器上运行它大约需要 1000 毫秒,减去 220 毫秒的读取时间,您的解析代码每 100 万行大约需要 780 毫秒。
那么为什么它这么慢(比读取慢 3-4 倍)?再次让我们消除一些东西。注释掉 int.Parse 和 double.Parse 并再次运行。这比 220 的读取时间少 460 毫秒好多了,我们现在解析时间为 240 毫秒。当然,'parse' 只是调用 string.Split()。 Hrmmm 看起来像 string.Split 将花费您与磁盘 IO 一样多的成本,考虑到 .NET 如何处理字符串也就不足为奇了。
那么 C# 解析的速度可以比从磁盘读取更快吗?嗯,是的,它可以,但你将不得不变得讨厌。您会看到 int.Parse 和 double.Parse 受制于它们具有文化意识的事实。由于这一点以及这些解析例程处理许多格式的事实,它们在您的示例中有些昂贵。我的意思是说我们每微秒(百万分之一秒)解析一个 double 和 int ,这通常还不错。
因此,为了匹配磁盘读取的速度(因此受 IO 限制),我们需要重写处理文本行的方式。这是一个讨厌的例子,但它适用于你的例子......
int len = line.Length;
fixed (char* ln = line)
{
double d;
long a = 0, b = 0;
int ix = 0;
while (ix < len && char.IsNumber(ln[ix]))
a = a * 10 + (ln[ix++] - '0');
if (ln[ix] == '.')
{
ix++;
long div = 1;
while (ix < len && char.IsNumber(ln[ix]))
{
b += b * 10 + (ln[ix++] - '0');
div *= 10;
}
d = a + ((double)b)/div;
}
while (ix < len && char.IsWhiteSpace(ln[ix]))
ix++;
int i = 0;
while (ix < len && char.IsNumber(ln[ix]))
i = i * 10 + (ln[ix++] - '0');
}
It is known that if you read data from disc you are IO bound and you can process/parse the read data much faster than you can read it from disc.
But this common wisdom (myth?)
嗯,不,我不会把这称为神话。事实上,我会争论你的原始代码仍然是 IO Bound。您碰巧在孤立地运行测试,因此影响很小,仅为从设备读取时间的 1/6。但是考虑一下如果该磁盘繁忙会发生什么?如果您的防病毒扫描程序扫描每个文件怎么办?简单地说,您的程序会随着 HDD 事件的增加而变慢,并且可能成为 IO Bound。
恕我直言,这种“常识”的原因是:
It's easier to get IO bound on writes than on reads.
写入设备需要更长的时间,并且通常比生成数据更昂贵。如果您想查看 IO Bound 的实际效果,请查看您的“CreateTestData”方法。您的 CreateTestData 方法将数据写入磁盘所需的时间是调用 String.Format(...) 的 2 倍。这是完全缓存。关闭缓存 ( FileOptions.WriteThrough ) 并重试...现在 CreateTestData 慢了 3-4 倍。使用以下方法自己尝试一下:
static int CreateTestData(string fileName)
{
FileStream fstream = new FileStream(fileName, FileMode.Create, FileAccess.Write, FileShare.None, 4096, FileOptions.WriteThrough);
using (StreamWriter writer = new StreamWriter(fstream, Encoding.UTF8))
{
for (int i = 0; i < linecount; i++)
{
writer.WriteLine("{0} {1}", 1.1d + i, i);
}
}
return linecount;
}
static int PrintTestData(string fileName)
{
for (int i = 0; i < linecount; i++)
{
String.Format("{0} {1}", 1.1d + i, i);
}
return linecount;
}
这仅适用于初学者,如果您真的想获得 IO 绑定(bind),您可以开始使用直接 IO。请参阅 CreateFile 上的文档使用 FILE_FLAG_NO_BUFFERING。当您开始绕过硬件缓存并等待 IO 完成时,写入速度会变慢。这是传统数据库写入速度非常慢的一个主要原因。他们必须强制硬件完成写入并等待它。只有这样他们才能将事务称为“已提交”,数据在物理设备上的文件中。
更新
好的阿洛伊斯,看来你只是在寻找你能走多快。为了更快,您需要停止处理字符串和字符并删除分配以加快速度。以下代码将上面的行/字符解析器改进了大约一个数量级(仅计算行数增加了大约 30 毫秒),同时仅在堆上分配了一个缓冲区。
警告 你需要意识到我正在证明它可以快速完成。我是 不是 建议你走这条路。这段代码有一些严重的限制和/或错误。就像当你以“1.2589E+19”的形式击中双倍时会发生什么?坦率地说,我认为您应该坚持使用原始代码,而不必担心尝试优化它。或者将文件格式更改为二进制而不是文本(请参阅 BinaryWriter )。如果您使用的是二进制文件,您可以使用以下代码的变体 BitConvert.ToDouble/ ToInt32它会更快。
private static unsafe int ParseFast(string data)
{
int count = 0, valid = 0, pos, stop, temp;
byte[] buffer = new byte[ushort.MaxValue];
const byte Zero = (byte) '0';
const byte Nine = (byte) '9';
const byte Dot = (byte)'.';
const byte Space = (byte)' ';
const byte Tab = (byte) '\t';
const byte Line = (byte) '\n';
fixed (byte *ptr = buffer)
using (Stream reader = File.OpenRead(data))
{
while (0 != (temp = reader.Read(buffer, valid, buffer.Length - valid)))
{
valid += temp;
pos = 0;
stop = Math.Min(buffer.Length - 1024, valid);
while (pos < stop)
{
double d;
long a = 0, b = 0;
while (pos < valid && ptr[pos] >= Zero && ptr[pos] <= Nine)
a = a*10 + (ptr[pos++] - Zero);
if (ptr[pos] == Dot)
{
pos++;
long div = 1;
while (pos < valid && ptr[pos] >= Zero && ptr[pos] <= Nine)
{
b += b*10 + (ptr[pos++] - Zero);
div *= 10;
}
d = a + ((double) b)/div;
}
else
d = a;
while (pos < valid && (ptr[pos] == Space || ptr[pos] == Tab))
pos++;
int i = 0;
while (pos < valid && ptr[pos] >= Zero && ptr[pos] <= Nine)
i = i*10 + (ptr[pos++] - Zero);
DoSomething(d, i);
while (pos < stop && ptr[pos] != Line)
pos++;
while (pos < stop && !(ptr[pos] >= Zero && ptr[pos] <= Nine))
pos++;
}
if (pos < valid)
Buffer.BlockCopy(buffer, pos, buffer, 0, valid - pos);
valid -= pos;
}
}
return count;
}
关于c# - 如何在 C# 中解析文本文件并进行 io 绑定(bind)?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/7153315/
我有一个简单的 pyparsing 构造,用于提取日志消息的部分内容。看起来像这样 log_line = 时间戳 + task_info + Suppress(LineEnd()) 此结构可以很好地解
我想定义一个函数 scaryDict(),它接受一个参数(textfile)并返回 textfile 中的单词按字母顺序排列,基本上生成字典但不打印任何一个或两个字母的单词。 这是我目前所拥有的……不
我正在尝试弄清楚如何包含对外部数据文件(文本形式)的引用,我希望通过 Web Start (JNLP) 与我的应用程序一起分发该文件。筛选 JNLP 结构的文档,我发现您可以包含对 JAR、nativ
我尝试将 Java 程序从 Eclipse 导出到 .jar 文件,但遇到了问题。它运行良好,但由于某种原因它没有找到它应该从中获取数据的文本文件。如果有人能帮忙解决这个问题,我将非常感激。 最佳答案
在过去的 20 个小时里,我试图解决以下问题,所以在开始考虑跳出窗外之前我想,我最好在这里寻求帮助: I have a text file with following content: ID 1 T
今天我试图删除一个简单文本文件中的重复行,例如: input (list.txt): hello hello try output (list.txt): try 我尝试使用 Notepad++ 删除
我将一个文本文件添加到我的项目中,如下路径所示: Myproject/WebPages/stopwords.txt 图片: http://s7.postimg.org/w65vc3lx7/Untitl
所以我在我的程序上工作,现在我无法找到解决方案。我需要在 fext 文件中替换更多的符号,目前程序只将“TIT”替换为代码“*245$a”,如果我想用同样的方式替换其他字母,程序不会改变。有人知道如何
这是一个非常简单的问题,但无论我看哪里,我都会得到不同的答案(这是因为它在 c++0x 中已经改变还是将要改变?): 在 C++ 中,我如何从一个文本文件中读取两个数字并将它们输出到另一个文本文件中?
我有一个 C++ 项目应该添加 到每一行的开头和到每一行的末尾。这适用于普通英文文本,但我有一个中文文本文件,我想这样做,但它不起作用。我通常使用 .txt 文件,但为此我必须使用 .rtf 来保存中
所以我的驱动看起来像这样: #include "problem2.h" #include "problem1.h" #include "problem3.h" #include #include
我有一个包含字符串标识符的 ascii 数字文本文件(>50k 行),可以将其视为数据 vector 的集合。根据用户输入,应用程序在运行时只需要这些数据 vector 之一。 据我所知,我有 3 个
关闭。这个问题不符合Stack Overflow guidelines .它目前不接受答案。 要求提供代码的问题必须表现出对所解决问题的最低限度理解。包括尝试过的解决方案、为什么它们不起作用,以及预
这个问题在这里已经有了答案: 关闭 12 年前。 Possible Duplicate: Any decent text diff/merge engine for .NET ? 我有两个文本文件,
我正在尝试将对话选择器中的唤醒时间和 sleep 时间记录到这样的文本文件中,但是对方法 commitToFile2 的调用不会 append 文本文件“savedData.txt”。 我知道这段代码
我开发了一个 android webview 并尝试在单击 webview 中的链接时下载生成的数据:文本文件。 webView.setDownloadListener(new Downloa
我在一个文本文件中有 250 张图像/天 4000*3000 像素。 file '/home/user/camdata/nonseqdata.jpg' file '/home/user/camdata
我曾多次尝试将此配置文件转换为多维数组,这意味着我必须读取 config.txt 文件,然后必须将其转换为多维数组。我需要帮助或一些建议。 配置文件: id=www session.timeout=1
我正在尝试使用 sublime text 3 打开文件,我想用光标在具体行号处打开它。 我一直在查subl --help但我找不到混凝土线的选择。因此我只是使用:subl filename 有没有办法
我想在我的应用程序中快速显示一个大文本文件的内容,而不是将整个文件加载到内存中。 其他人是怎么做的? Total Commander是一个很棒的工具,它有一个很棒的内部查看器可以做到这一点。无论文件有
我是一名优秀的程序员,十分优秀!