Могут ли строки пула XmlSerializer избежать больших повторяющихся строк? - PullRequest
5 голосов
/ 03 апреля 2009

У меня есть несколько очень больших файлов XML, которые я читаю, используя System.Xml.Serialization.XmlSerializer. Это довольно быстро (ну, достаточно быстро), но я хочу, чтобы он объединял строки, так как некоторые длинные строки встречаются очень часто.

XML выглядит примерно так:

<Report>
    <Row>
        <Column name="A long column name!">hey</Column>
        <Column name="Another long column name!!">7</Column>
        <Column name="A third freaking long column name!!!">hax</Column>
        <Column name="Holy cow, can column names really be this long!?">true</Column>
    </Row>
    <Row>
        <Column name="A long column name!">yo</Column>
        <Column name="Another long column name!!">53</Column>
        <Column name="A third freaking long column name!!!">omg</Column>
        <Column name="Holy cow, can column names really be this long!?">true</Column>
    </Row>
    <!-- ... ~200k more rows go here... -->
</Report>

И классы, на которые XML десериализован, выглядят примерно так:

class Report 
{
    public Row[] Rows { get; set; }
}
class Row 
{
    public Column[] Columns { get; set; }
}
class Column 
{
    public string Name { get; set; }
    public string Value { get; set; }
}

Когда данные импортируются, для каждого имени столбца выделяется новая строка. Я понимаю, почему это так, но, согласно моим расчетам, это означает, что несколько дублированных строк занимают около 50% памяти, используемой импортированными данными. Я считаю очень хорошим компромиссом потратить несколько дополнительных циклов ЦП, чтобы сократить потребление памяти в два раза. Есть ли какой-нибудь способ получить строки пула XmlSerializer, чтобы дубликаты отбрасывались и могли быть возвращены при следующем GC gen0?

<Ч />

Также несколько заключительных замечаний:

  • Я не могу изменить схему XML. Это экспортированный файл от стороннего поставщика.

  • Я знаю, что (теоретически) мог бы создать более быстрый синтаксический анализатор, используя вместо этого XmlReader, и это позволило бы мне не только выполнять собственный пул строк, но и обрабатывать данные во время промежуточного импорта, чтобы не все 200k строк должны быть сохранены в оперативной памяти, пока я не прочитал весь файл. Тем не менее, я бы не стал тратить время на написание и отладку пользовательского парсера. Настоящий XML немного сложнее, чем пример, поэтому это довольно нетривиальная задача. И, как упоминалось выше, XmlSerializer действительно достаточно хорошо работает для моих целей, мне просто интересно, есть ли простой способ немного его настроить.

  • Я мог бы написать собственный пул строк и использовать его в установщике Column.Name, но я бы предпочел не как (1), что означает возиться с автоматически сгенерированным кодом, и (2) он открывается на множество проблем, связанных с параллелизмом и утечками памяти.

  • И нет, под «объединением» я не подразумеваю «интернирование», поскольку это может вызвать утечки памяти.

Ответы [ 4 ]

2 голосов
/ 03 апреля 2009

Лично я не смущался бы вручную проверять сущности - либо принимая на себя владение сгенерированным кодом, либо делая это вручную (и избавляясь от массивов ;-p).

Повторный параллелизм - возможно, у вас есть пул статического потока? AFAIK, XmlSerializer просто использует один поток, так что это должно быть хорошо. Это также позволит вам выбросить бассейн, когда вы закончите. Так что тогда у вас может быть что-то , например , статический пул, но для каждого потока. Тогда, возможно, настройте сеттеры:

class Column 
{
    private string name, value;
    public string Name {
       get { return this.name; }
       set { this.name= MyPool.Get(value); }
    }
    public string Value{
       get { return this.value; }
       set { this.value = MyPool.Get(value); }
    }
}

где статический метод MyPool.Get говорит со статическим полем (предположительно, HashSet<string>), украшенным [ThreadStatic].

1 голос
/ 04 апреля 2009

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

0 голосов
/ 30 января 2019

Я знаю его старую ветку, но нашел хороший способ для этого:

Создайте XmlReader, который переопределяет свойство Value таким образом, что перед возвращением значения вы проверяете, существует ли оно в вашем пуле строк, а затем возвращаете его.

Свойство Value XmlReader из msdn :

Возвращаемое значение зависит от NodeType узла. Следующие В таблице перечислены типы узлов, которые имеют возвращаемое значение. Все остальные узлы типы возвращают String.Empty.

Например, для Attribute NodeType возвращено значение атрибута.

Следовательно, реализация будет выглядеть так:

public class StringPoolXmlTextReader : XmlTextReader
{
    private readonly Dictionary<string, string> stringPool = new Dictionary<string, string>();

    internal StringPoolXmlTextReader(Stream stream)
        : base(stream)
    {
    }

    public override string Value
    {
        get
        {
            if (this.NodeType == XmlNodeType.Attribute)
                return GetOrAddFromPool(base.Value);

            return base.Value;
        }
    }

    private string GetOrAddFromPool(string str)
    {
        if (str == null)
            return null;

        if (stringPool.TryGetValue(str, out var res) == false)
        {
            res = str;
            stringPool.Add(str, str);
        }

        return res;
    }
}

Как использовать:

using (var stream = File.Open(@"..\..\Report.xml", FileMode.Open))
{
   var reader = new StringPoolXmlTextReader(stream);
   var ser = new XmlSerializer(typeof(Report));
   var data = (Report)ser.Deserialize(reader);
}

Производительность: Я проверил производительность для строк 200К со случайными значениями столбцов и обнаружил, что время десериализации было одинаковым, а объем памяти Report уменьшился с 78 551 460 байт до 48 890 016 байт (уменьшился на ~ 38%).

Примечания:

  1. Пример наследуется от XmlTextReader, но вы можете наследовать от любого XmlReader
  2. Вы также можете использовать пул строк для значений столбцов путем переопределения свойства Value, например, public override string Value => GetOrAddFromPool(base.Value);, но это может увеличить время десериализации примерно на 20%, когда значения не дублируются (как в моем тесте, когда они случайные) .
0 голосов
/ 03 апреля 2009

Вы можете использовать OnDeserializedAttribute для определения метода, который вызывается после десериализации экземпляра, если вы используете сериализатор DataContract (как используется WCF) вместо использования XmlSerializer.

С другой стороны, если XML не намного сложнее, чем в примере, то почему бы не реализовать собственную десериализацию через XmlReader.

...