Итак, здесь есть пара проблем.Другие уже прокомментировали кеширование ввода-вывода в Windows, а также фактический аппаратный кеш, поэтому я оставлю это в покое.
Другая проблема заключается в том, что вы измеряете комбинированные операции read () + parse ()и сравнивая это со скоростью только что прочитанного ().По сути, вам нужно осознать тот факт, что A + B всегда будет больше, чем A (при условии, что он не отрицательный).
Поэтому, чтобы выяснить, связаны ли вы с IO, вам необходимо выяснить, сколько времени это займет.прочитать файл.Вы сделали это.На моей машине ваш тест выполняется примерно на 220 мс для чтения файла.
Теперь вам нужно измерить, сколько нужно времени, чтобы проанализировать столько разных строк.Это немного сложнее изолировать.Итак, давайте просто скажем, что мы оставляем их вместе и вычитаем время, необходимое для чтения из времени разбора.Кроме того, мы не пытаемся измерить, что вы делаете с данными, а просто анализируете, поэтому выкиньте List и List и давайте просто проанализируем.Запуск этого на моей машине дает около 1000 мс, меньше 220 мс для чтения, ваш код синтаксического анализа занимает около 780 мс на 1 млн строк.
Так почему же он такой медленный (в 3-4 раза медленнее, чем чтение)?Опять давайте уберем некоторые вещи.Комментируем int.Parse и double.Parse и запускаем снова.Это намного лучше, 460 мс меньше, чем время чтения 220, теперь у нас 240 мсек для анализа.Конечно, parse вызывает только string.Split ().Hrmmm выглядит как string.Split будет стоить вам столько же, сколько и дисковый ввод-вывод, что неудивительно, если учесть, как .NET работает со строками.
Так может ли C # анализировать так быстро или быстрее, чем чтение с диска?Ну, да, может, но вам придется стать противным.Вы видите int.Parse и double.Parse страдают от того, что они осведомлены о культуре.Из-за этого и того факта, что эти процедуры синтаксического анализа имеют дело со многими форматами, они несколько дорогостоящие в зависимости от вашего примера.Я имею в виду, что мы анализируем double и int каждую микросекунду (миллионную долю секунды), что обычно неплохо.
Таким образом, чтобы соответствовать скорости чтения с диска (и, следовательно, быть привязанным к вводу-выводу), нам нужно переписать способ обработки текстовой строки.Вот неприятный пример, но он работает для вашего примера ...
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');
}
Запуск этого дерьмового кода дает время выполнения около 450 мс, или примерно 2n времени чтения.Итак, притворившись на мгновение, что вы думаете, что приведенный выше фрагмент кода является приемлемым (что, боже, я надеюсь, вы не делаете), вы можете иметь один поток, читающий строки, а другой - синтаксический анализ, и вы будете близки к ограничению ввода-вывода.Поместите два потока на разборе, и вы будете связаны IO. Если вы сделаете это, это еще один вопрос.
Итак, давайте вернемся к вашему первоначальному вопросу:
Известно, что если вы читаетеданные с диска связаны с вводом-выводом, и вы можете обрабатывать / анализировать прочитанные данные гораздо быстрее, чем вы можете прочитать их с диска.
Но это обычная мудрость (миф?)
Ну нет, я бы не назвал это мифом.На самом деле, я бы поспорил, что ваш оригинальный код все еще является IO Bound.Вы проводите свой тест изолированно, поэтому воздействие будет небольшим в 1/6 от времени, затраченного на чтение с устройства.Но подумайте, что произойдет, если этот диск занят?Что если ваш антивирусный сканер просматривает каждый файл?Проще говоря, ваша программа будет замедляться из-за повышенной активности жесткого диска, и она может стать IO Bound.
ИМХО, причина этой "общей мудрости" заключается в следующем:
Легче связать ввод-вывод при записи, чем при чтении.
Запись на устройство занимает больше времени и, как правило, обходится дороже, чем получение данных. Если вы хотите увидеть IO Bound в действии, посмотрите на ваш метод «CreateTestData». Ваш метод CreateTestData занимает в 2 раза больше времени для записи данных на диск, чем просто вызов String.Format (...). И это при полном кешировании. Отключите кэширование ( 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;
}
Это только для начала, если вы действительно хотите связать ввод-вывод, начните использовать прямой ввод-вывод. См. Документацию по CreateFile с использованием FILE_FLAG_NO_BUFFERING. Запись становится намного медленнее, когда вы начинаете обходить аппаратные кэши и ждете завершения ввода-вывода. Это одна из основных причин, по которой традиционная база данных очень медленно записывается. Они должны заставить аппаратное обеспечение завершить запись и ждать его. Только тогда они могут назвать транзакцию «совершенной», данные находятся в файле на физическом устройстве.
ОБНОВЛЕНО
Хорошо, Алоис, кажется, ты просто ищешь, как быстро ты можешь идти. Чтобы двигаться быстрее, вам нужно перестать иметь дело со строками и символами и убрать выделение, чтобы идти быстрее. Следующий код улучшает синтаксический анализатор строк / символов, описанный выше, примерно на порядок (прибавляя около 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;
}