c # двойное преобразование в массив символов или альтернативу

У меня есть программа, которая хранит кучу экземпляров структур, содержащих множество членов типа double. Время от времени я выгружаю их в файл, что я делал с помощью построителя строк, например. :

 StringBuilder builder = new StringBuilder(256);

 builder.AppendFormat("{0};{1};{2};", x.A.ToString(), x.B.ToString(), x.C.ToString()); 

где 'x' - это экземпляр моего типа, а A, B, C - члены X типа double. Я вызываю ToString () для каждого из них, чтобы избежать бокса. Однако эти вызовы ToString по-прежнему выделяют много памяти в контексте моего приложения, и я хотел бы уменьшить это. Я думаю, что нужно иметь массив символов и записывать каждый член прямо в него, а затем создавать одну строку из этого массива символов и выгружать ее в файл. Несколько вопросов:

1) Разумно ли то, что я собираюсь делать? Есть ли что-нибудь, что уже могло бы достичь чего-то подобного?

2) Есть ли уже что-то встроенное для преобразования двойного в массив символов (что, я думаю, будет с некоторой параметризованной точностью?). В идеале я хочу передать свой массив и некоторый индекс и начать писать туда.

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

Ура А


person user555265    schedule 06.06.2012    source источник
comment
StringBuilder - это перебор для таких простых конкатенаций.   -  person asawyer    schedule 06.06.2012
comment
Почему бы вам просто не использовать вместо этого: builder.AppendFormat("{0};{1};{2};", x.A, x.B, x.C)?   -  person Tim Schmelter    schedule 06.06.2012
comment
@asawyer на самом деле членов намного больше, это только для примера   -  person user555265    schedule 06.06.2012
comment
Если вы будете поступать так, как Тим Шмелтер, советую вам не вводить бокс. С другой стороны, вызов ToString() на double выделит новый объект в куче, эффективно упаковывая двойное значение в строку, чего вы и хотите избежать.   -  person Martin Liversage    schedule 06.06.2012
comment
@MartinLiversage - разве то, что предложил Тим, сначала упаковать в двойную упаковку, а затем вызвать ToString для этого упакованного объекта?   -  person user555265    schedule 06.06.2012
comment
@MartinLiversage Да, он будет упаковывать двойника перед вызовом ToString. Да, и кстати, если вы действительно используете строки, я предлагаю вам использовать CultureInfo.InvariantCulture. Но опять же, см. Мой ответ ниже.   -  person Kris Vandermotten    schedule 06.06.2012
comment
Упаковка двойника, вероятно, будет дешевле, чем создание временной строки.   -  person Joe White    schedule 06.06.2012
comment
@JoeWhite, разве он не упаковал бы, а затем позвонил бы ToString? .. так что по-прежнему создается временная строка ..   -  person user555265    schedule 06.06.2012
comment
@ user555265, вызывающий ToString () для int, должен помещать его в коробку, поскольку он, скорее всего, переопределяет реализацию object.ToString (). См. stackoverflow.com/questions/3499651/boxing -прошлое   -  person Slugart    schedule 06.06.2012
comment
@Slugart имеет смысл, и его следует оптимизировать, чтобы избежать бокса ... я думаю, это то, что было добавлено позже в истории .NET, потому что я вижу много таких статей, как: andyfrench.info/2010/07/, из которых можно предположить, что бокс продолжается. . Ваше здоровье!   -  person user555265    schedule 06.06.2012
comment
@KrisVandermotten: вызов Double.ToString() отменяет метод базового класса и не вызывает бокса. См. Также: stackoverflow.com/questions/436363/   -  person Martin Liversage    schedule 06.06.2012
comment
@ user555265 в этой статье происходит упаковка по причине того, что параметр для AppendFormat имеет значение Object / Object []. Если вы вызываете int.ToString () перед передачей его в AppendFormat, вы передаете строку, которая уже находится в куче.   -  person Slugart    schedule 06.06.2012
comment
@MartinLiversage Я знаю, но не Double.ToString () вызывает бокс в builder.AppendFormat ({0}; {1}; {2} ;, xA, xB, xC), это StringBuilder.AppendFormat (string, object []) (msdn.microsoft.com/en-us/library/cazfhf32). Также не забывайте, что мы передаем здесь массив. Этот массив также размещается в куче в дополнение к двойным числам в штучной упаковке.   -  person Kris Vandermotten    schedule 06.06.2012


Ответы (5)


Файл должен быть в текстовом формате?

В противном случае наиболее эффективным способом сделать это является использование BinaryWriter (и BinaryReader для их обратного чтения).

См. http://msdn.microsoft.com/en-us/library/system.io.binarywriter.aspx для получения дополнительной информации.

person Kris Vandermotten    schedule 06.06.2012
comment
в идеале, к сожалению, текст :-( .. но если я не найду другого решения, я могу изучить это, так что спасибо! - person user555265; 06.06.2012

Если возможна запись в текстовый файл напрямую, Steamwriter можно использовать для написания строго типизированных структур. Я не тестировал использование памяти, но считаю, что они должны быть эффективными

        using (var tw = new System.IO.StreamWriter("filename", true)) //(true to append to file)
        {
            tw.Write(x.A);
            tw.Write(';');
        }

Если требуется построитель строк, строго типизированные перегрузки также могут быть вызваны с помощью:

        builder.Append(x.A) //strongly typed as long as the field is a system type
            .Append(';')
            .Append(x.B)
            .Append(';'); 

Конечно, оба метода выглядели бы лучше, реализуя какой-то цикл или делегатов, но это выходит за рамки логики упаковки.

ИЗМЕНИТЬ пользовательское двойное письмо, опубликованное в другом ответе: C # двойное преобразование в массив символов или альтернативный вариант

person Me.Name    schedule 06.06.2012
comment
tw.Write (xA) будет лежать в основе вызова ToString (), я считаю: msdn. microsoft.com/en-us/library/ek5h49e6.aspx - person user555265; 06.06.2012
comment
Вы правы, как и перегрузка конструктора строк. Я не заглядывал так далеко, предполагая, что строго типизированные будут иметь настраиваемое поведение для повышения производительности. Я немного повозился и добавил еще один ответ с некоторой индивидуальной пробой. - person Me.Name; 07.06.2012

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

using(var writer = new StreamWriter(...))
{
   writer.Write(x.A);
   writer.Write(";");
   writer.Write(x.B);
   writer.Write(";");
   writer.Write(x.C);
}
person Viacheslav Smityukh    schedule 06.06.2012
comment
но это также вызовет ToString, хотя я думаю: msdn.microsoft.com/ en-us / library / ek5h49e6.aspx - person user555265; 06.06.2012
comment
ToString () будет вызываться в любом случае, но этот способ позволяет избежать дополнительных массивов и строк, которые были введены построителем строк. - person Viacheslav Smityukh; 06.06.2012
comment
да, это, наверное, справедливый момент. В моем конкретном случае это действительно (от использования CLRProfiler до выделения профилей) операции ToString (), которые выделяют много памяти, так что это то, что я действительно хочу оптимизировать. - person user555265; 06.06.2012
comment
Да ... я в основном пишу CSV, который позже открывается в Excel - person user555265; 06.06.2012
comment
Я исследовал это, нет возможности написать двойной контент без выделения строк. Вы можете написать свой собственный код для преобразования double в массив char, но я уверен, что это накладные расходы! - person Viacheslav Smityukh; 06.06.2012
comment
давайте продолжим обсуждение в чате - person Viacheslav Smityukh; 06.06.2012

Вы уверены, что это предположительно много вызовов Double.ToString, которые вызывают проблемы с вашей памятью? Каждая строка должна быть собрана при сборке следующего поколения 0, и сборщик мусора .NET довольно эффективен в этом.

Если строки, которые вы создаете, превышают 85 КБ, они будут созданы в куче больших объектов, и это может увеличить общую память, требуемую вашему приложению, даже если большие строки существуют временно (фрагментация кучи больших объектов).

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

StringBuilder - правильный класс для построения строк в памяти, но если вы создаете строки только в памяти, чтобы позже записать их в файл, вы должны вместо этого записывать непосредственно в файл, используя StreamWriter.

StringBuilder потребуется расширить буфер, используемый для хранения строки, и вы можете избежать этих дополнительных накладных расходов, заранее установив емкость StringBuilder (вы уже делаете это в своем примере кода).

Независимо от того, какую перегрузку вы вызываете для форматирования Double в StringBuilder, вызов в конечном итоге приведет к вызову Double.ToString. StringBuilder.AppendFormat форматируется непосредственно в буфер без выделения дополнительной отформатированной строки, поэтому с точки зрения использования памяти StringBuilder.AppendFormat так же хорошо, как и StringBuilder.Append, и обе перегрузки будут выделять строку с отформатированным Double как часть процесса форматирования. Однако StringBuilder.AppendFormat будет упаковывать Double, потому что принимает массив params Object[]. Использование перегрузки StringBuilder.Append, которая принимает Double, не страдает от этой проблемы.

Если вы с уверенностью знаете, что Double.ToString является источником ваших проблем с памятью, я считаю, что вам лучше всего написать собственный код форматирования с плавающей запятой, который может записывать число с плавающей запятой непосредственно в StringBuilder. Задача нетривиальная, но вы можете почерпнуть вдохновение из библиотеки C с открытым исходным кодом.

person Martin Liversage    schedule 06.06.2012
comment
Ага ... в CLR Profiler я вижу Double :: ToString String () как виновника ... Я думаю, что последую вашему предложению и опубликую свой код, как только я найду что-то, предлагающее желаемую производительность ... спасибо! - person user555265; 06.06.2012

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

        double[] test = { 8.99999999, -4, 34.567, -234.2354, 2.34, 500.8 };
        using (var sw = new FileStream(@"c:\temp\test.txt", FileMode.Create))
        {
            using (var bw = new BinaryWriter(sw))
            {
                const byte semicol = 59, minus = 45, dec = 46, b0 = 48;

                Action<double> write = d =>
                {
                    if (d == 0)
                        bw.Write(b0);
                    else
                    {
                        if (d < 0)
                        {
                            bw.Write(minus);
                            d = -d;
                        }

                        double m = Math.Pow(10d, Math.Truncate(Math.Log10(d)));
                        while(true)
                        {
                            var r = ((decimal)(d / m) % 10); //decimal because of floating point errors
                            if (r == 0) break;
                            if (m == 0.1)
                                bw.Write(dec); //decimal point
                            bw.Write((byte)(48 + r));         
                            m /= 10d;
                        }
                    }

                    bw.Write(semicol);
                };

                foreach (var d in test)
                    write(d);
            }
        }
person Me.Name    schedule 06.06.2012
comment
Я думаю, что (r == 0) в цикле while заставит вас выйти, например. если вы писали 8.909 .. в середине 0 до того, как вы написали последние 9 - person user555265; 07.06.2012
comment
К сожалению, это из-за синтаксического анализа до (int) (добавлено, что при тестировании обнаруженных проблем с плавающей запятой перед десятичным преобразованием), модуль всегда должен возвращать хотя бы дробную часть, если еще есть десятичные дроби, которые нужно обработать. Я отредактировал сообщение и удалил приведение (int) из var r = (int) ((decimal) (d / m)% 10); - person Me.Name; 07.06.2012