C# — более быстрая альтернатива Convert.ToSingle()

Я работаю над программой, которая читает миллионы чисел с плавающей запятой из текстового файла. Эта программа запускается внутри игры, которую я разрабатываю, поэтому мне нужно, чтобы она работала быстро (я загружаю файл obj). Пока что загрузка относительно небольшого файла занимает около минуты (без предварительной компиляции) из-за медленной скорости Convert.ToSingle(). Есть ли более быстрый способ сделать это?

РЕДАКТИРОВАТЬ: вот код, который я использую для анализа файла Obj

http://pastebin.com/TfgEge9J

using System;
using System.IO;
using System.Collections.Generic;
using OpenTK.Math;
using System.Drawing;
using PlatformLib;

public class ObjMeshLoader
{
    public static StreamReader[] LoadMeshes(string fileName)
    {
        StreamReader mreader = new StreamReader(PlatformLib.Platform.openFile(fileName));
        MemoryStream current = null;
        List<MemoryStream> mstreams = new List<MemoryStream>();
        StreamWriter mwriter = null;

        if (!mreader.ReadLine().Contains("#"))
        {
            mreader.BaseStream.Close();
            throw new Exception("Invalid header");
        }

        while (!mreader.EndOfStream)
        {
            string cmd = mreader.ReadLine();
            string line = cmd;
            line = line.Trim(splitCharacters);
            line = line.Replace("  ", " ");

            string[] parameters = line.Split(splitCharacters);
            if (parameters[0] == "mtllib")
            {
                loadMaterials(parameters[1]);
            }

            if (parameters[0] == "o")
            {
                if (mwriter != null)
                {
                    mwriter.Flush();
                    current.Position = 0;
                }

                current = new MemoryStream();
                mwriter = new StreamWriter(current);
                mwriter.WriteLine(parameters[1]);
                mstreams.Add(current);
            }
            else
            {
                if (mwriter != null)
                {
                    mwriter.WriteLine(cmd);
                    mwriter.Flush();
                }
            }
        }

        mwriter.Flush();
        current.Position = 0;
        List<StreamReader> readers = new List<StreamReader>();

        foreach (MemoryStream e in mstreams)
        {
            e.Position = 0;
            StreamReader sreader = new StreamReader(e);
            readers.Add(sreader);
        }

        return readers.ToArray();
    }

    public static bool Load(ObjMesh mesh, string fileName)
    {
        try
        {
            using (StreamReader streamReader = new StreamReader(Platform.openFile(fileName)))
            {
                Load(mesh, streamReader);
                streamReader.Close();
                return true;
            }
        }
        catch { return false; }
    }

    public static bool Load2(ObjMesh mesh, StreamReader streamReader, ObjMesh prevmesh)
    {
        if (prevmesh != null)
        {
            //mesh.Vertices = prevmesh.Vertices;
        }

        try
        {
            //streamReader.BaseStream.Position = 0;
            Load(mesh, streamReader);
            streamReader.Close();
#if DEBUG
            Console.WriteLine("Loaded "+mesh.Triangles.Length.ToString()+" triangles and"+mesh.Quads.Length.ToString()+" quadrilaterals parsed, with a grand total of "+mesh.Vertices.Length.ToString()+" vertices.");
#endif
            return true;
        }
        catch (Exception er) { Console.WriteLine(er); return false; }
    }

    static char[] splitCharacters = new char[] { ' ' };
    static List<Vector3> vertices;
    static List<Vector3> normals;
    static List<Vector2> texCoords;
    static Dictionary<ObjMesh.ObjVertex, int> objVerticesIndexDictionary;
    static List<ObjMesh.ObjVertex> objVertices;
    static List<ObjMesh.ObjTriangle> objTriangles;
    static List<ObjMesh.ObjQuad> objQuads;
    static Dictionary<string, Bitmap> materials = new Dictionary<string, Bitmap>();

    static void loadMaterials(string path)
    {
        StreamReader mreader = new StreamReader(Platform.openFile(path));
        string current = "";
        bool isfound = false;

        while (!mreader.EndOfStream)
        {
            string line = mreader.ReadLine();
            line = line.Trim(splitCharacters);
            line = line.Replace("  ", " ");

            string[] parameters = line.Split(splitCharacters);

            if (parameters[0] == "newmtl")
            {
                if (materials.ContainsKey(parameters[1]))
                {
                    isfound = true;
                }
                else
                {
                    current = parameters[1];
                }
            }

            if (parameters[0] == "map_Kd")
            {
                if (!isfound)
                {
                    string filename = "";
                    for (int i = 1; i < parameters.Length; i++)
                    {
                        filename += parameters[i];
                    }

                    string searcher = "\\" + "\\";

                    filename.Replace(searcher, "\\");
                    Bitmap mymap = new Bitmap(filename);
                    materials.Add(current, mymap);
                    isfound = false;
                }
            }
        }
    }

    static float parsefloat(string val)
    {
        return Convert.ToSingle(val);
    }

    int remaining = 0;

    static string GetLine(string text, ref int pos)
    {
        string retval = text.Substring(pos, text.IndexOf(Environment.NewLine, pos));
        pos = text.IndexOf(Environment.NewLine, pos);
        return retval;
    }

    static void Load(ObjMesh mesh, StreamReader textReader)
    {
        //try {
        //vertices = null;
        //objVertices = null;
        if (vertices == null)
        {
            vertices = new List<Vector3>();
        }

        if (normals == null)
        {
            normals = new List<Vector3>();
        }

        if (texCoords == null)
        {
            texCoords = new List<Vector2>();
        }

        if (objVerticesIndexDictionary == null)
        {
            objVerticesIndexDictionary = new Dictionary<ObjMesh.ObjVertex, int>();
        }

        if (objVertices == null)
        {
            objVertices = new List<ObjMesh.ObjVertex>();
        }

        objTriangles = new List<ObjMesh.ObjTriangle>();
        objQuads = new List<ObjMesh.ObjQuad>();

        mesh.vertexPositionOffset = vertices.Count;

        string line;
        string alltext = textReader.ReadToEnd();
        int pos = 0;

        while ((line = GetLine(alltext, pos)) != null)
        {
            if (line.Length < 2)
            {
                break;
            }

            //line = line.Trim(splitCharacters);
            //line = line.Replace("  ", " ");

            string[] parameters = line.Split(splitCharacters);

            switch (parameters[0])
            {

                case "usemtl":
                    //Material specification
                    try
                    {
                        mesh.Material = materials[parameters[1]];
                    }
                    catch (KeyNotFoundException)
                    {
                        Console.WriteLine("WARNING: Texture parse failure: " + parameters[1]);
                    }

                    break;
                case "p": // Point
                    break;
                case "v": // Vertex
                    float x = parsefloat(parameters[1]);
                    float y = parsefloat(parameters[2]);
                    float z = parsefloat(parameters[3]);
                    vertices.Add(new Vector3(x, y, z));
                    break;
                case "vt": // TexCoord
                    float u = parsefloat(parameters[1]);
                    float v = parsefloat(parameters[2]);
                    texCoords.Add(new Vector2(u, v));
                    break;
                case "vn": // Normal
                    float nx = parsefloat(parameters[1]);
                    float ny = parsefloat(parameters[2]);
                    float nz = parsefloat(parameters[3]);
                    normals.Add(new Vector3(nx, ny, nz));
                    break;
                case "f":
                    switch (parameters.Length)
                    {
                        case 4:
                            ObjMesh.ObjTriangle objTriangle = new ObjMesh.ObjTriangle();
                            objTriangle.Index0 = ParseFaceParameter(parameters[1]);
                            objTriangle.Index1 = ParseFaceParameter(parameters[2]);
                            objTriangle.Index2 = ParseFaceParameter(parameters[3]);
                            objTriangles.Add(objTriangle);
                            break;
                        case 5:
                            ObjMesh.ObjQuad objQuad = new ObjMesh.ObjQuad();
                            objQuad.Index0 = ParseFaceParameter(parameters[1]);
                            objQuad.Index1 = ParseFaceParameter(parameters[2]);
                            objQuad.Index2 = ParseFaceParameter(parameters[3]);
                            objQuad.Index3 = ParseFaceParameter(parameters[4]);
                            objQuads.Add(objQuad);
                            break;
                    }
                    break;
            }
        }
        //}catch(Exception er) {
        //  Console.WriteLine(er);
        //  Console.WriteLine("Successfully recovered. Bounds/Collision checking may fail though");
        //}
        mesh.Vertices = objVertices.ToArray();
        mesh.Triangles = objTriangles.ToArray();
        mesh.Quads = objQuads.ToArray();
        textReader.BaseStream.Close();
    }

    public static void Clear()
    {
        objVerticesIndexDictionary = null;
        vertices = null;
        normals = null;
        texCoords = null;
        objVertices = null;
        objTriangles = null;
        objQuads = null;
    }

    static char[] faceParamaterSplitter = new char[] { '/' };

    static int ParseFaceParameter(string faceParameter)
    {
        Vector3 vertex = new Vector3();
        Vector2 texCoord = new Vector2();
        Vector3 normal = new Vector3();

        string[] parameters = faceParameter.Split(faceParamaterSplitter);

        int vertexIndex = Convert.ToInt32(parameters[0]);

        if (vertexIndex < 0) vertexIndex = vertices.Count + vertexIndex;
        else vertexIndex = vertexIndex - 1;

        //Hmm. This seems to be broken.
        try
        {
            vertex = vertices[vertexIndex];
        }
        catch (Exception)
        {
            throw new Exception("Vertex recognition failure at " + vertexIndex.ToString());
        }

        if (parameters.Length > 1)
        {
            int texCoordIndex = Convert.ToInt32(parameters[1]);

            if (texCoordIndex < 0) texCoordIndex = texCoords.Count + texCoordIndex;
            else texCoordIndex = texCoordIndex - 1;

            try
            {
                texCoord = texCoords[texCoordIndex];
            }
            catch (Exception)
            {
                Console.WriteLine("ERR: Vertex " + vertexIndex + " not found. ");
                throw new DllNotFoundException(vertexIndex.ToString());
            }
        }

        if (parameters.Length > 2)
        {
            int normalIndex = Convert.ToInt32(parameters[2]);

            if (normalIndex < 0) normalIndex = normals.Count + normalIndex;
            else normalIndex = normalIndex - 1;

            normal = normals[normalIndex];
        }

        return FindOrAddObjVertex(ref vertex, ref texCoord, ref normal);
    }

    static int FindOrAddObjVertex(ref Vector3 vertex, ref Vector2 texCoord, ref Vector3 normal)
    {
        ObjMesh.ObjVertex newObjVertex = new ObjMesh.ObjVertex();
        newObjVertex.Vertex = vertex;
        newObjVertex.TexCoord = texCoord;
        newObjVertex.Normal = normal;

        int index;

        if (objVerticesIndexDictionary.TryGetValue(newObjVertex, out index))
        {
            return index;
        }
        else
        {
            objVertices.Add(newObjVertex);
            objVerticesIndexDictionary[newObjVertex] = objVertices.Count - 1;
            return objVertices.Count - 1;
        }
    }
}

person bbosak    schedule 23.04.2011    source источник
comment
почему вы используете текстовый файл в первую очередь? Рассмотрим двоичный файл, тогда вы можете напрямую читать поплавок.   -  person BrokenGlass    schedule 24.04.2011
comment
да. Ни float.Parse, ни Single.Parse не работают достаточно быстро.   -  person bbosak    schedule 24.04.2011
comment
Потому что Blender экспортирует в текстовые файлы (формат obj). Я сделал конвертер obj-to-binary, но мне было интересно, есть ли лучший способ сделать это.   -  person bbosak    schedule 24.04.2011
comment
@Heandel: float.Parse совпадает с Single.Parse, что вызывает Convert.ToSingle при получении строки.   -  person David Brown    schedule 24.04.2011
comment
Вы не получите лучших результатов от других методов синтаксического анализа строк. Перейти на двоичный формат.   -  person Ekin Koc    schedule 24.04.2011
comment
Я знаю, что могу повысить производительность, потому что я использовал аналогичный текстовый анализатор OBJ для ActionScript, который загрузил его почти мгновенно.   -  person bbosak    schedule 24.04.2011
comment
Вы должны иметь доступ ко всем поплавкам прямо сейчас? В противном случае вы можете читать по мере необходимости   -  person BrokenGlass    schedule 24.04.2011
comment
Да, мне нужен немедленный доступ ко всем поплавкам, чтобы я мог загрузить их в GPU и выполнить проверку коллизий. Я видел программы ActionScript, которые делают это, но синтаксический анализатор C#, преобразующий строку в число с плавающей запятой, намного медленнее.   -  person bbosak    schedule 24.04.2011
comment
@Ekin Koc: да, вы получите лучшие результаты от пользовательских парсеров, которые не обладают гибкостью для миллиона различных факторов (показатель степени против десятичного, локально-зависимое форматирование и т. д.).   -  person André Caron    schedule 24.04.2011
comment
@IDWMaster: К вашему сведению, скорость в 30 раз быстрее, чем у моей версии, физически невозможна (без многопоточности). Я вынул все данные синтаксического анализа и просто сказал return 0; в своем коде, и он все еще не превзошел сам себя в 30 раз. Я думаю, проблема в том, как вы читаете файл, а не анализируете его; если вы используете что-то вроде StreamReader.ReadLine (или что-то еще, что выделяет строку или массив), это значительно снизит производительность. Не могли бы вы опубликовать пример кода, чтобы мы увидели, что вы делаете?   -  person user541686    schedule 24.04.2011
comment
Я тоже сомневаюсь, что проблема в вашем parseFloat методе. Вы можете проверить это, просто вернув 0. Я подозреваю, что проблема не в чтении или анализе, а в необходимости изменять размер ваших коллекций по мере их роста. Я думаю, что @Paja здесь на правильном пути.   -  person Jim Mischel    schedule 24.04.2011


Ответы (5)


Основываясь на вашем описании и коде, который вы разместили, я готов поспорить, что ваша проблема не в чтении, анализе или способе добавления вещей в свои коллекции. Наиболее вероятная проблема заключается в том, что ваша структура ObjMesh.Objvertex не переопределяет GetHashCode. (Я предполагаю, что вы используете код, аналогичный http://www.opentk.com/files/ObjMesh.cs.

Если вы не переопределяете GetHashCode, то ваш objVerticesIndexDictionary будет работать как линейный список. Это объясняет проблему с производительностью, с которой вы столкнулись.

Я предлагаю вам рассмотреть вопрос о создании хорошего метода GetHashCode для вашего класса ObjMesh.Objvertex.

См. Почему ValueType.GetHashCode() реализован так, как есть? для получения информации о реализации GetHashCode по умолчанию для типов значений и о том, почему она не подходит для использования в хэш-таблице или словаре.

person Jim Mischel    schedule 24.04.2011
comment
Спасибо! Это помогло TON! Теперь эта сетка загружается почти мгновенно! - person bbosak; 24.04.2011

Редактировать 3: проблема НЕ в разборе.

Это связано с тем, как вы читаете файл. Если бы вы прочитали это правильно, это было бы быстрее; однако кажется, что вы читаете необычно медленно. Мое первоначальное подозрение заключалось в том, что это произошло из-за избыточных выделений, но похоже, что с вашим кодом могут быть и другие проблемы, поскольку это не объясняет всего замедления.

Тем не менее, вот фрагмент кода, который я сделал, полностью избегая выделения всех объектов:

static void Main(string[] args)
{
    long counter = 0;
    var sw = Stopwatch.StartNew();
    var sb = new StringBuilder();
    var text = File.ReadAllText("spacestation.obj");
    for (int i = 0; i < text.Length; i++)
    {
        int start = i;
        while (i < text.Length &&
            (char.IsDigit(text[i]) || text[i] == '-' || text[i] == '.'))
        { i++; }
        if (i > start)
        {
            sb.Append(text, start, i - start); //Copy data to the buffer

            float value = Parse(sb); //Parse the data

            sb.Remove(0, sb.Length); //Clear the buffer
            counter++;
        }
    }
    sw.Stop();
    Console.WriteLine("{0:N0}", sw.Elapsed.TotalSeconds); //Only a few ms
}

с этим парсером:

const int MIN_POW_10 = -16, int MAX_POW_10 = 16,
    NUM_POWS_10 = MAX_POW_10 - MIN_POW_10 + 1;
static readonly float[] pow10 = GenerateLookupTable();
static float[] GenerateLookupTable()
{
    var result = new float[(-MIN_POW_10 + MAX_POW_10) * 10];
    for (int i = 0; i < result.Length; i++)
        result[i] = (float)((i / NUM_POWS_10) *
                Math.Pow(10, i % NUM_POWS_10 + MIN_POW_10));
    return result;
}
static float Parse(StringBuilder str)
{
    float result = 0;
    bool negate = false;
    int len = str.Length;
    int decimalIndex = str.Length;
    for (int i = len - 1; i >= 0; i--)
        if (str[i] == '.')
        { decimalIndex = i; break; }
    int offset = -MIN_POW_10 + decimalIndex;
    for (int i = 0; i < decimalIndex; i++)
        if (i != decimalIndex && str[i] != '-')
            result += pow10[(str[i] - '0') * NUM_POWS_10 + offset - i - 1];
        else if (str[i] == '-')
            negate = true;
    for (int i = decimalIndex + 1; i < len; i++)
        if (i != decimalIndex)
            result += pow10[(str[i] - '0') * NUM_POWS_10 + offset - i];
    if (negate)
        result = -result;
    return result;
}

это происходит за небольшую долю секунды.

Конечно, этот парсер плохо протестирован и имеет следующие текущие ограничения (и многое другое):

  • Не пытайтесь анализировать больше цифр (десятичных и целых), чем предусмотрено в массиве.

  • Никакой обработки ошибок.

  • Разбирает только десятичные дроби, не степени! то есть он может анализировать 1234.56, но не 1.23456E3.

  • Плевать на глобализацию/локализацию. Ваш файл имеет только один формат, поэтому нет смысла заботиться о таких вещах, потому что вы, вероятно, все равно используете английский язык для его хранения.

Кажется, что вам не обязательно понадобится столько излишеств, но взгляните на свой код и попытайтесь выяснить узкое место. Вроде бы ни чтение, ни разбор.

person user541686    schedule 23.04.2011
comment
Хорошая работа над парсером, но он все еще недостаточно быстр для моих нужд. - person bbosak; 24.04.2011
comment
@IDWMaster: Просто любопытно, во сколько раз вам это нужно быстрее? - person user541686; 24.04.2011
comment
Примерно в 30 раз быстрее. На данный момент он загружается довольно медленно (минута для загрузки простого файла OBJ, такого как этот dl.dropbox.com/u/1854457/spacestation.obj). Я работаю над шутером от первого лица в реальном времени, поэтому он должен загружаться быстро. - person bbosak; 24.04.2011
comment
@IDWMaster: Сколько поплавков в вашем файле? (Это немного сложно вычислить, когда я смотрю на это прямо сейчас.) - person user541686; 24.04.2011
comment
@IDWMaster: Кстати, я думаю, что узким местом является чтение строки, а не ее разбор. Вы не создаете новую строку каждый раз, не так ли? Если вы используете Substring, то это очень большая ошибка. - person user541686; 24.04.2011
comment
Я разместил свой код для фактического чтения файла. Полный код импортера Obj. Смотрите ссылку pastebin в моем ответе. - person bbosak; 24.04.2011
comment
@IDWMaster: Видишь? Вы используете ReadLine, который смехотворно медленный, потому что каждый раз создает новую строку. Смотрите мою правку. - person user541686; 24.04.2011
comment
Как мне его разобрать, не используя подстроку ReadLine ИЛИ. Каков самый быстрый способ загрузить файл Obj без этих методов? - person bbosak; 24.04.2011
comment
@IDWMaster: я сделал это без них; см. мою правку. Вы должны использовать один StringBuilder повсюду и никогда не создавать новую строку в цикле. - person user541686; 24.04.2011
comment
Проблема не в чтении файла. Я создал файл с 1 миллионом строк, по 10 двойников в каждой. Полученный файл имеет размер 170 МБ. На моей машине (процессор Core 2 2,0 ГГц) он читает и анализирует файл за 8,2 секунды. Он анализирует 1,2 миллиона двойных значений в секунду. Проблема не в разборе ИЛИ чтении. В моем тесте используется double.Parse. - person Jim Mischel; 24.04.2011
comment
@ Джим: Я думаю, это могло быть что-то еще, но это была моя догадка. Я думаю, вы могли бы попытаться выяснить реальное узкое место, но я почти уверен, что использование string.Split или StreamReader.ReadLine является узким местом по сравнению с синтаксическим анализом, если у вас нет другого неэффективного кода (которого, я думаю, может и не быть). здесь дело обстоит именно так, но я никогда не запускал его код для проверки). Я действительно заслужил отрицательный голос? :( - person user541686; 24.04.2011
comment
@Mehrdad: я полагаю, что отрицательный голос был излишним. Как ни странно, SO говорит мне, что я не могу удалить его, если вы не отредактируете ответ. Странный. В любом случае нет никаких сомнений в том, что использование readline и split не оптимально, но это очень небольшой процент времени выполнения. Оптимизация чтения и синтаксического анализа не решит его проблему. - person Jim Mischel; 24.04.2011
comment
@Jim: Ха-ха, тогда я отредактирую эту часть о чтении и анализе :) - person user541686; 24.04.2011
comment
@Mehrdad: Вероятно, проблема заключается в отсутствии метода GetHashCode для типа вершины, что приводит к тому, что его поиск в словаре является линейным поиском. - person Jim Mischel; 24.04.2011

Вы определили, что проблема со скоростью действительно вызвана Convert.ToSingle?

В коде, который вы включили, я вижу, что вы создаете списки и словари следующим образом:

normals = new List<Vector3>();
texCoords = new List<Vector2>();
objVerticesIndexDictionary = new Dictionary<ObjMesh.ObjVertex, int>();

А затем, когда вы читаете файл, вы добавляете в коллекцию по одному элементу. Одной из возможных оптимизаций было бы сохранение общего количества нормалей, texCoords, индексов и всего остального в начале файла, а затем инициализация этих коллекций этими числами. Это предварительно распределит буферы, используемые коллекциями, поэтому добавление элементов в них будет довольно быстрым.

Итак, создание коллекции должно выглядеть так:

// These values should be stored at the beginning of the file
int totalNormals = Convert.ToInt32(textReader.ReadLine());
int totalTexCoords = Convert.ToInt32(textReader.ReadLine());
int totalIndexes = Convert.ToInt32(textReader.ReadLine());

normals = new List<Vector3>(totalNormals);
texCoords = new List<Vector2>(totalTexCoords);
objVerticesIndexDictionary = new Dictionary<ObjMesh.ObjVertex, int>(totalIndexes);

См. Конструктор List‹T› (Int32) и Словарь‹TKey, TValue› Конструктор (Int32).

person Paya    schedule 23.04.2011
comment
ReadLine вообще не следует использовать, я думаю, это реальная проблема. - person user541686; 24.04.2011
comment
Заменил ReadLine на подстроку - person bbosak; 24.04.2011
comment
Это сработало бы, если бы я ЗНАЛ общее число в начале файла. Я не знаю, сколько Vertices, texcoords, normals, quads и т. д. в начале файла согласно спецификации Obj. - person bbosak; 24.04.2011
comment
@Mehrdad: хотя чтение из текстового файла будет не таким быстрым, как чтение из двоичного файла, нет никакого способа, чтобы чтение файла размером 600 КБ заняло больше минуты. Я загружаю XML-файлы размером в гигабайт за меньшее время, и синтаксический анализ XML намного сложнее, чем синтаксический анализ с плавающей запятой. - person Jim Mischel; 24.04.2011
comment
Ну, вы примерно знаете количество предметов? Это совершенно случайно или обычно около 100 000? Вы можете инициализировать коллекцию до некоторой предполагаемой емкости. Или создайте свою собственную коллекцию и заставьте ее работать аналогично StringBuffer. - person Paya; 24.04.2011
comment
@Jim, @Paja: Смотрите мою отредактированную версию. Обратите внимание, что я никогда не создаю новые объекты внутри каких-либо циклов и как сильно увеличивается скорость (т.е. всего несколько миллисекунд). Проблема не в том, текстовый это файл или двоичный файл, проблема в том, что он создает слишком много новых объектов, вот и все. - person user541686; 24.04.2011
comment
Но я действительно рекомендую измерить почти все, что происходит в вашем парсере, и посмотреть, где находится производительность. Попробуйте использовать профайлер. - person Paya; 24.04.2011
comment
@Paja: использование профилировщика очень излишне. Просто запустите программу, приостановите ее пару раз и посмотрите, где она остановится. Это узкое место. Конечно, в этом случае нет необходимости профилировать — просто избегайте создания новой строки для каждого отдельного числа с плавающей запятой, и это должно значительно ускориться. - person user541686; 24.04.2011
comment
@Mehrdad: На самом деле использование профилировщика требует меньше усилий, чем приостановка программы несколько раз (по крайней мере, для меня), и гораздо надежнее. - person Paya; 24.04.2011
comment
@Mehrdad: я не согласен. Используя Readline и String.Split, я могу прочитать и проанализировать файл из 1 миллиона строк (по 10 двойников в строке) за 8,2 секунды. Наверняка проблема не в чтении файла или разборе чисел. Проблема совсем в другом. - person Jim Mischel; 24.04.2011
comment
@Jim: Может быть, это что-то еще, но лично я бы избегал string.Split как чумы, потому что он выделяет так много объектов. Я думаю, он опубликовал свой код; Я не собираюсь пытаться выяснить узкое место, но это было мое предположение. Хотя может быть где-то еще, я думаю. - person user541686; 24.04.2011

Этот связанный вопрос относится к C++, но его определенно стоит прочитать.

Для максимально быстрого чтения вы, вероятно, захотите отобразить файл в память, а затем проанализировать его с помощью какого-нибудь пользовательского парсера с плавающей запятой, особенно если вы знаете, что числа всегда в определенном формате ( т.е. вы тот, кто генерирует входные файлы в первую очередь).

person André Caron    schedule 23.04.2011
comment
отображение памяти слишком велико для этой проблемы - person David Heffernan; 24.04.2011
comment
Пробовал это. Загрузил весь файл в память с помощью ReadToEnd(), затем проанализировал подстроки этого текста. - person bbosak; 24.04.2011

Я один раз тестировал синтаксический анализ строки .Net, и самой быстрой функцией для синтаксического анализа текста была старая функция VB Val(). Вы можете вытащить соответствующие части из Microsoft.VisualBasic.Conversion Val(string)

Converting String to numbers

Comparison of relative test times (ms / 100000 conversions)
Double  Single  Integer    Int(w/ decimal point)
14      13      6          16                 Val(Str)
14      14      6          16                 Cxx(Val(Str)) e.g., CSng(Val(str))
22      21      17          e!                Convert.To(str)
23      21      16          e!                XX.Parse(str) e.g. Single.Parse()
30      31      31         32                 Cxx(str)

Val: fastest, part of VisualBasic dll, skips non-numeric,
ConvertTo and Parse: slower, part of core, exception on bad format (including decimal point)
Cxx: slowest (for strings), part of core, consistent times across formats
person Jon B    schedule 11.05.2011