Следующий пример будет считываться из целых чисел ASCII из файла с отображением памяти самым быстрым способом без создания каких-либо строк. Решение, предлагаемое MiMo, намного медленнее. Он работает со скоростью 5 Мбайт/с, что вам не поможет. Самая большая проблема в решении MiMo заключается в том, что он вызывает метод (Read) для каждого символа, который стоит критического коэффициента производительности 15. Интересно, почему вы приняли его решение, если ваша оригинальная проблема заключалась в том, что у вас была проблема с производительностью. Вы можете получить 20 МБ/с с помощью немого строкового считывателя и разбора строки в целое число. Чтобы получить каждый байт с помощью вызова метода, он разрушает вашу возможную производительность чтения.
Код ниже отображает файл в 200 МБ фрагментов, чтобы предотвратить заполнение 32-разрядного адресного пространства. Затем он просматривает буфер через указатель байта, который очень быстрый. Целочисленный анализ легко, если вы не учитываете локализацию. Интересно, что если я создаю представление для отображения, то единственный способ получить указатель на буфер просмотра не позволит мне начать с отображаемой области.
Я бы счел это bug in the .NET Framwork, который до сих пор не исправлен в .NET 4.5. Буфер SafeMemoryMappedViewHandle выделяется с помощью гранулярности выделения ОС. Если вы переходите к некоторому смещению, вы получаете указатель назад, который все еще указывает на начало буфера.Это очень неудачно, потому что это делает разницу между 5MB/s и 77MB/s в производительности синтаксического анализа.
Did read 258.888.890 bytes with 77 MB/s
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.IO.MemoryMappedFiles;
using System.Runtime.InteropServices;
unsafe class Program
{
static void Main(string[] args)
{
new Program().Start();
}
private void Start()
{
var sw = Stopwatch.StartNew();
string fileName = @"C:\Source\BigFile.txt";//@"C:\Source\Numbers.txt";
var file = MemoryMappedFile.CreateFromFile(fileName);
var fileSize = new FileInfo(fileName).Length;
int viewSize = 200 * 100 * 1000;
long offset = 0;
for (; offset < fileSize-viewSize; offset +=viewSize) // create 200 MB views
{
using (var accessor = file.CreateViewAccessor(offset, viewSize))
{
int unReadBytes = ReadData(accessor, offset);
offset -= unReadBytes;
}
}
using (var rest = file.CreateViewAccessor(offset, fileSize - offset))
{
ReadData(rest, offset);
}
sw.Stop();
Console.WriteLine("Did read {0:N0} bytes with {1:F0} MB/s", fileSize, (fileSize/(1024 * 1024))/sw.Elapsed.TotalSeconds);
}
List<int> Data = new List<int>();
private int ReadData(MemoryMappedViewAccessor accessor, long offset)
{
using(var safeViewHandle = accessor.SafeMemoryMappedViewHandle)
{
byte* pStart = null;
safeViewHandle.AcquirePointer(ref pStart);
ulong correction = 0;
// needed to correct offset because the view handle does not start at the offset specified in the CreateAccessor call
// This makes AquirePointer nearly useless.
// http://connect.microsoft.com/VisualStudio/feedback/details/537635/no-way-to-determine-internal-offset-used-by-memorymappedviewaccessor-makes-safememorymappedviewhandle-property-unusable
pStart = Helper.Pointer(pStart, offset, out correction);
var len = safeViewHandle.ByteLength - correction;
bool digitFound = false;
int curInt = 0;
byte current =0;
for (ulong i = 0; i < len; i++)
{
current = *(pStart + i);
if (current == (byte)' ' && digitFound)
{
Data.Add(curInt);
// Console.WriteLine("Add {0}", curInt);
digitFound = false;
curInt = 0;
}
else
{
curInt = curInt * 10 + (current - '0');
digitFound = true;
}
}
// scan backwards to find partial read number
int unread = 0;
if (curInt != 0 && digitFound)
{
byte* pEnd = pStart + len;
while (true)
{
pEnd--;
if (*pEnd == (byte)' ' || pEnd == pStart)
{
break;
}
unread++;
}
}
safeViewHandle.ReleasePointer();
return unread;
}
}
public unsafe static class Helper
{
static SYSTEM_INFO info;
static Helper()
{
GetSystemInfo(ref info);
}
public static byte* Pointer(byte *pByte, long offset, out ulong diff)
{
var num = offset % info.dwAllocationGranularity;
diff = (ulong)num; // return difference
byte* tmp_ptr = pByte;
tmp_ptr += num;
return tmp_ptr;
}
[DllImport("kernel32.dll", SetLastError = true)]
internal static extern void GetSystemInfo(ref SYSTEM_INFO lpSystemInfo);
internal struct SYSTEM_INFO
{
internal int dwOemId;
internal int dwPageSize;
internal IntPtr lpMinimumApplicationAddress;
internal IntPtr lpMaximumApplicationAddress;
internal IntPtr dwActiveProcessorMask;
internal int dwNumberOfProcessors;
internal int dwProcessorType;
internal int dwAllocationGranularity;
internal short wProcessorLevel;
internal short wProcessorRevision;
}
}
void GenerateNumbers()
{
using (var file = File.CreateText(@"C:\Source\BigFile.txt"))
{
for (int i = 0; i < 30 * 1000 * 1000; i++)
{
file.Write(i.ToString() + " ");
}
}
}
}
Если ваши данные ASCII, вам необходимо проанализировать его, прежде чем вы сможете преобразовать его в int. Другой вариант - написать конвертер, который читает ваш файл по строке и записывать целые числа в виде двоичных значений в файл. Затем вы можете использовать свой подход выше, чтобы прочитать целые числа. –
@Alois: хороший catch, OP фактически хочет конвертировать из ASCII в двоичное представление. – Vlad
Если скорость ваша главная проблема, это может помочь посмотреть: http://stackoverflow.com/questions/7153315/how-to-parse-a-text-file-in-c-sharp-and-be-io- bound –