Сервер останавливается из-за большого экземпляра StringBuilder в памяти

Итак, у меня есть этот код, который берет кучу данных из базы данных и выполняет кучу вычислений с этими данными. Однако не это вызывает остановку. Остановка наступает, когда я беру все эти «окончательные» данные, которые были подготовлены, и записываю их в текстовый файл.

Каждая строка в текстовом файле создается из данных, для которых были выполнены вычисления. Каждая строка «соответствует» записи из базы данных. Пользователю придется выполнять экспорт этого файла обычно примерно для 30-40 тысяч записей за раз. Теперь, хотя я использую StringBuilder, это все еще останавливается из-за чистого количества записей.

В настоящее время в коде я создаю один StringBuilder, добавляю к нему все данные (после вычислений), преобразую его в строку, добавляю его в список (строки), который будет возвращен из функции, а затем очищает StringBuilder , и делаю то же самое для следующей строки, и я делаю это до тех пор, пока каждая строка не будет создана и добавлена, преобразована, а затем вставлена ​​в список.

Затем другой класс получает этот список (строки), берет каждую строку и в конечном итоге добавляет ее в файл.

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

Спасибо заранее!

Редактировать:

r = Class.ExportFile(ID)
data = Encoding.ASCII.GetBytes(r.ResponseString)

Return File(data, "text/plain", r.DefaultFileName)

-Скотт


person Scott    schedule 02.08.2010    source источник


Ответы (4)


Возвратите IEnumerable вместо большой строки или списка строк и запишите строку за раз в файл. Таким образом, вы можете лениво оценивать данные, поэтому вам не нужно хранить огромные строки. например

IEnumerable<string> Process() {
   var rows = QueryTheDatabase();
   foreach(var row in rows) {
      yield return ProcessARecord(row);//process and build a string of
                                         //one row
    }
 }

Вызывающий просто выполняет foreach для возвращаемого IEnumerable и записывает каждую строку в файл.

person nos    schedule 02.08.2010
comment
Итак, у нас есть строка, возвращаемая другому методу, где он вызывает -- data = Encoding.ASCII.GetBytes(OURSTRING) -- а затем позже преобразует ее в файл, выполнив -- File(data, text/plain, filename) -- Как это должно измениться? - person Scott; 03.08.2010
comment
Вы возвращаете одну гигантскую строку, представляющую собой конкатенацию всех данных, или вы возвращаете список строк, по одной строке для каждой из ваших 40 тыс. записей? В последнем случае вам не нужно ничего делать в этом отношении, и что вы делаете с результатом - просто записываете его в файл? - person nos; 03.08.2010
comment
Один метод создает список строк. затем этот метод передает этот список другому методу, который добавляет каждый из них в построитель строк. затем он преобразует все это в строку и передает ее тому, что вы видите выше. - person Scott; 03.08.2010
comment
Он использует MVC FileContentResult из контроллера, чтобы наконец вернуть файл. Если это поможет. Кто-то упоминал запуск процесса в отдельном .exe? Я понятия не имею, как это сделать. - person Scott; 03.08.2010
comment
Я добавил редактирование того, как работает наш код. Все вычисления и добавления происходят в вызове ExportFile, который создает результат с огромной строкой, а также с именем файла. - person Scott; 03.08.2010
comment
В этом случае вам может быть лучше записать файл в файловую систему, а не обслуживать его из памяти, и вернуть перенаправление на этот физический файл. - person nos; 03.08.2010

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

person Will A    schedule 02.08.2010
comment
На самом деле я бы предпочел сделать что-то столь же простое, потому что это не потребует слишком больших изменений в коде, однако мы используем довольно отдельную архитектуру ASP.NET MVC, и они как бы отделили логику от классов, которые я использую. чтобы вернуть строку, которая будет записана в файл, и классы, которые ее фактически записывают (они используются в нескольких местах) - person Scott; 03.08.2010
comment
@Scott: Затем передайте поток в свой класс, куда должны быть записаны данные. Затем вы можете заменить FileStream или MemoryStream или что-то еще, и код, который выполняет запись, не должен меняться. - person Billy ONeal; 03.08.2010
comment
Хм, нет смысла. Ваш код может возвращать (!) IEnumerable‹string› внешнему классу, который он может использовать для получения результирующей строки строка за строкой и ее записи. Разделение соблюдено, но полная строка все еще не хранится в памяти. - person TomTom; 03.08.2010
comment
@Scott: Или вы можете передать IEnumerable<string> классу, который выполняет запись, если он всегда должен записывать строки строк и использовать блок итератора для генерации каждой строки по мере их написания. - person Michael Petito; 03.08.2010

Вероятно, вы сталкиваетесь с проблемами фрагментации памяти. К этому приводит игра с ДЕЙСТВИТЕЛЬНО большими динамическими структурами данных в 32-битном мире. Лично я столкнулся с этим только со строкой в ​​300 МБ, но обычно я избегаю попадания в ситуацию, так что это не слишком много говорит.

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

person Loren Pechtel    schedule 02.08.2010

Используйте блок IEnumerable/итератор, а не список. Преобразование довольно простое: измените возвращаемый тип функции с List<string> на IEnumerable<string>, а затем, где бы вы ни вызывали метод .Add() вашего списка, измените строку, чтобы вместо этого использовать yield return. Затем также измените свой другой класс, чтобы он принимал IEnumerable<string>, а не List<string>. Возможно, вам придется сделать аналогичное преобразование и для других методов, чтобы вся цепочка из базы данных обрабатывалась таким образом.

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

person Joel Coehoorn    schedule 02.08.2010