Результаты показались настолько интересными, что решил создать новую тему.
На всякий пожарный сделал проверку качества сериализации путем загрузки данных в другой DataSet и сравнения с исходным. Вариант Trantor-а не прошел проверку. Оказалось, что дата записанная в стринг по средствам ToString() и считанная обратно теряет точность. Так что вариант со строковой сериализацией нужно еще дописывать. Если вообще нужна строковая сериализация. Ведь разница в скорости почти 4 раза, и в размерах данных примерно в 1.7 раза.
За одно измерил время загрузки. Результаты оказались дико интересным! Гляньте на результаты BinaryFormatter-а! Загрузка почти в два раза медленнее чем у любого другого. А по сравнению сDetaSetCustomSerializer2 разница в скорости в 55 раз! И это МС продает как прорыв в работе с данными! По убывал бы. Причем вообще не ясно, что делает этот BinaryFormatter, так как известно, что DataSet всегда сериализуется в XML.
Скептики вроде DG должны заметить, что датасет не нуждается в универсальном сохранении графа объектов. Максимум что ему нужно сохранить свои таблицы... И это прекрасно делается с помощью ручной сериализации.
Попутно обнаружил серьезный недостаток. Дело в том, что Trantor не рассчитывал на то, что ячейки могут содержать DbNull. Мой вариант пока, что тоже не учитывать DbNull, но завтра допишу его шоб поддерживал. Запас по скорости таки есть.
Так же добавил в тесте сборку мусора (двойную, с ожиданием). Чтобы вообще не было намека на наложенку.
Традиционно вначале результаты (мой вариант называется DetaSetCustomSerializer2):
DataSetHandsSerializate
Serialization time elapsed: 381
Length is: 1 600 338
Loading time elapsed: 1 171
DetaSetCustomSerializer2
Serialization time elapsed: 100 !!!
Length is: 948 955 !!!
Loading time elapsed: 290 !!!
DataTable одинаковые!
BinaryFormatter
Serialization time elapsed: 3 715
Length is: 7 819 323 !!!! Это все потому, что реально в Датасете хранится XML!
Loading time elapsed: 16 103 !!!!
DataTable одинаковые!
XmlSerializer
Serialization time elapsed: 1 833
Length is: 10 059 102
Loading time elapsed: 7 371
DataTable одинаковые!
SoapFormatter
Serialization time elapsed: 2 854
Length is: 11 980 266
Loading time elapsed: 9 063
DataTable одинаковые!
using System;
using System.IO;
using System.Data;
namespace SerializeTest
{
class DetaSetCustomSerializer2
{
// Массив для перемапливания TypeCode на Type
// TypeCode это перечисление включающее константы для базовых типов.static Type[] _TypeMap;
// Статический констрктор. Нужен для инициализации _TypeMap.static DetaSetCustomSerializer2()
{
// TypeCode.DBNull и TypeCode.Empty пропущены, так как они
// по сути не являются типами.
_TypeMap = new Type[(int)TypeCode.String + 1];
_TypeMap[(int)TypeCode.Object] = typeof(Object);
_TypeMap[(int)TypeCode.Boolean] = typeof(Boolean);
_TypeMap[(int)TypeCode.Char] = typeof(Char);
_TypeMap[(int)TypeCode.SByte] = typeof(SByte);
_TypeMap[(int)TypeCode.Byte] = typeof(Byte);
_TypeMap[(int)TypeCode.Int16] = typeof(Int16);
_TypeMap[(int)TypeCode.UInt16] = typeof(UInt16);
_TypeMap[(int)TypeCode.Int32] = typeof(Int32);
_TypeMap[(int)TypeCode.UInt32] = typeof(UInt32);
_TypeMap[(int)TypeCode.Int64] = typeof(Int64);
_TypeMap[(int)TypeCode.UInt64] = typeof(UInt64);
_TypeMap[(int)TypeCode.Single] = typeof(Single);
_TypeMap[(int)TypeCode.Double] = typeof(Double);
_TypeMap[(int)TypeCode.Decimal] = typeof(Decimal);
_TypeMap[(int)TypeCode.DateTime] = typeof(DateTime);
_TypeMap[(int)TypeCode.String] = typeof(String);
}
public static void Serialize(Stream stream, DataSet ds)
{
BinaryWriter bw = new BinaryWriter(stream);
DataTableCollection tables = ds.Tables;
bw.Write(ds.DataSetName);
bw.Write(tables.Count);
foreach(DataTable dt in tables)
{ // Вообще-то foreach-и лучше на всякий пожанный избегать. Но мне было в лом.
DataColumnCollection columns = dt.Columns;
int iColCount = columns.Count;
TypeCode[] colTypeCodes = new TypeCode[iColCount];
//table name
bw.Write(dt.TableName);
//count columns
bw.Write(iColCount);
for(int i = 0; i < iColCount; i++)
{
DataColumn dc = columns[i];
// Получаем TypeCode для типа обрабатываемой колонки.
TypeCode tc = Type.GetTypeCode(dc.DataType);
// Запоминаем TypeCode колонки в соотвествующем массиве.
colTypeCodes[i] = tc;
bw.Write(dc.ColumnName);
// Записываем TypeCode как Int32. Можно было бы и сэкономить 3 байта. :)
bw.Write((Int32)tc);
// TODO: Нужно добавть проверку на DbNull !!!
}
///////////////////////////////////////////////////////////////
// add data
// count rows
bw.Write(dt.Rows.Count);
foreach(DataRow dr in dt.Rows)
{
for(int i = 0; i < iColCount; i++)
{
// Записываем данные ячейки.
// TODO: Нужно добавть проверку на DbNull !!!switch(colTypeCodes[i])
{ // Каждому типу соотвествует переопределенная фунция...case TypeCode.Boolean: bw.Write((Boolean)dr[i]); break;
case TypeCode.Char: bw.Write((Char)dr[i]); break;
case TypeCode.SByte: bw.Write((SByte)dr[i]); break;
case TypeCode.Byte: bw.Write((Byte)dr[i]); break;
case TypeCode.Int16: bw.Write((Int16)dr[i]); break;
case TypeCode.UInt16: bw.Write((UInt16)dr[i]); break;
case TypeCode.Int32: bw.Write((Int32)dr[i]); break;
case TypeCode.UInt32: bw.Write((UInt32)dr[i]); break;
case TypeCode.Int64: bw.Write((Int64)dr[i]); break;
case TypeCode.UInt64: bw.Write((UInt64)dr[i]); break;
case TypeCode.Single: bw.Write((Single)dr[i]); break;
case TypeCode.Double: bw.Write((Double)dr[i]); break;
case TypeCode.Decimal: bw.Write((Decimal)dr[i]); break;
case TypeCode.DateTime:
// Для DateTime приходится выпендриваться особым образом.
bw.Write(((DateTime)(dr[i])).ToFileTime());
break;
case TypeCode.String: bw.Write((String)dr[i]); break;
default:
// На всякий случай пробуем записать неопознанный тип
// виде строки.
bw.Write(dr[i].ToString());
break;
}
}
}
}
}
public static DataSet Deserialize( Stream stream )
{
BinaryReader br = new BinaryReader(stream);
// Считываем имя датасета и создаем его...
DataSet ds = new DataSet(br.ReadString());
int counTables = br.ReadInt32();
DataTable dt;
DataColumn dc;
DataRow dr;
for(int t = 0; t < counTables; t++)
{
// Имя дататэйбла тоже передается в качестве параметра конструктора.
dt = new DataTable(br.ReadString());
int iColCount = br.ReadInt32();
// В этом массив будут записаны TypeCode-ы для колонок DataTable-а.
TypeCode[] colTypeCodes = new TypeCode[iColCount];
// А в этот массив будут записаны типы (Type) колонок DataTable-а
// соотвествующие TypeCode-ам.
Type[] colTypes = new Type[iColCount];
for(int c = 0; c < iColCount; c++)
{
string colName = br.ReadString();
// Считываем TypeCode.
TypeCode tc = (TypeCode)br.ReadInt32();
// Помещаем TypeCode в массив для дальнейшего использования.
colTypeCodes[c] = tc;
// Получаем (через мап) тип соответсвующий TypeCode-у.
Type type = _TypeMap[(int)tc];
// Создаем колонку с полученым именем и типом.
dc = new DataColumn(colName, type);
dt.Columns.Add(dc);
}
int counRows = br.ReadInt32();
for(int r = 0; r < counRows; r++)
{
// Считываем данные.
dr = dt.NewRow();
for(int i = 0; i < iColCount; i++)
{
// TODO: Здесь нужно бы обрабатывать DbNull !!!switch(colTypeCodes[i])
{
case TypeCode.Boolean: dr[i] = br.ReadBoolean(); break;
case TypeCode.Char: dr[i] = br.ReadChar(); break;
case TypeCode.SByte: dr[i] = br.ReadSByte(); break;
case TypeCode.Byte: dr[i] = br.ReadByte(); break;
case TypeCode.Int16: dr[i] = br.ReadInt16(); break;
case TypeCode.UInt16: dr[i] = br.ReadUInt16(); break;
case TypeCode.Int32: dr[i] = br.ReadInt32(); break;
case TypeCode.UInt32: dr[i] = br.ReadUInt32(); break;
case TypeCode.Int64: dr[i] = br.ReadInt64(); break;
case TypeCode.UInt64: dr[i] = br.ReadUInt64(); break;
case TypeCode.Single: dr[i] = br.ReadSingle(); break;
case TypeCode.Double: dr[i] = br.ReadDouble(); break;
case TypeCode.Decimal: dr[i] = br.ReadDecimal(); break;
case TypeCode.DateTime:
dr[i] = DateTime.FromFileTime(br.ReadInt64());
break;
case TypeCode.String: dr[i] = br.ReadString(); break;
default:
dr[i] = Convert.ChangeType(br.ReadString(),
colTypeCodes[i]);
break;
}
}
dt.Rows.Add(dr);
}
ds.Tables.Add(dt);
}
return ds;
}
}
}
Тестовый код:
using System;
using System.Data;
using System.Xml;
using System.Xml.Serialization;
using System.IO;
using System.Runtime.Serialization.Formatters.Binary;
using System.Runtime.Serialization.Formatters.Soap;
namespace SerializeTest
{
class Class1
{
// Сравнивает два DataTablestatic bool CmpDataTables(DataTable dt1, DataTable dt2)
{
if(dt1.Rows.Count != dt1.Rows.Count)
return false;
if(dt1.Columns.Count != dt1.Columns.Count)
return false;
int iCols = dt1.Columns.Count;
DataRowCollection rows1 = dt1.Rows;
DataRowCollection rows2 = dt2.Rows;
int iRows = rows1.Count;
for(int r = 0; r < iRows; r++)
{
DataRow dr1 = rows1[r];
DataRow dr2 = rows2[r];
for(int i = 0; i < iCols; i++)
{
if(!dr1[i].Equals(dr2[i]))
return false;
}
}
Console.WriteLine("DataTable одинаковые!");
return true;
}
// Вызывает суровую уборку мусора. :)static void ClearMem()
{
GC.Collect();
GC.WaitForPendingFinalizers();
GC.Collect();
GC.WaitForPendingFinalizers();
}
[STAThread]
static void Main(string[] args)
{
int start; // Используется для вычисления времени выполнения
DataSet ds = new DataSet("myDataSet");
DataTable dt = new DataTable("Table1");
dt.Columns.Add(new DataColumn("col0", typeof(Int32)));
dt.Columns.Add(new DataColumn("col1", typeof(string)));
dt.Columns.Add(new DataColumn("col2", typeof(DateTime)));
dt.Columns.Add(new DataColumn("col3", typeof(byte)));
DataRow dr;
for (int i = 0; i < 40000; i++)
{
dr = dt.NewRow();
dr[0] = i;
dr[1] = "2test" + i.ToString();
dr[2] = DateTime.Now;
dr[3] = (byte)i % 255;
dt.Rows.Add(dr);
}
ds.Tables.Add(dt);
// Стрим для сериализации через BinaryFormatter
MemoryStream msBf = new MemoryStream(1024);
// Стрим для ручной сериализации через DataSetHandsSerializate.
MemoryStream msBm1 = new MemoryStream(1024);
// Стрим для ручной сериализации через DetaSetCustomSerializer2.
MemoryStream msBm2 = new MemoryStream(1024);
// Стрим для сериализации через SoapFormatter
MemoryStream msSf = new MemoryStream(1024);
// Стрим для сериализации через XmlSerializer
MemoryStream msXml = new MemoryStream(1024);
BinaryFormatter bf = new BinaryFormatter();
SoapFormatter sf = new SoapFormatter();
DataSet dsLoaded;
ClearMem(); // Вызов GC чтобы небыло налаженки...
// Сериализация DataSetHandsSerializate
start = Environment.TickCount;
DataSetHandsSerializate.Serialize(msBm1, ds);
Console.WriteLine(
"\n DataSetHandsSerializate\n"
+ "Serialization time elapsed: {0,10:### ### ###}\n"
+ "Length is: {1,10:### ### ###}",
Environment.TickCount - start, msBm1.Length);
// Загрузка DataSetHandsSerializate
msBm1.Seek(0, SeekOrigin.Begin);
start = Environment.TickCount;
dsLoaded = DataSetHandsSerializate.Deserialize(msBm1);
Console.WriteLine("Loading time elapsed: {0,10:### ### ###}",
Environment.TickCount - start);
CmpDataTables(dsLoaded.Tables[0], ds.Tables[0]);
ClearMem();
// Сериализация DetaSetCustomSerializer2
start = Environment.TickCount;
DetaSetCustomSerializer2.Serialize(msBm2, ds);
Console.WriteLine(
"\n DetaSetCustomSerializer2\n"
+ "Serialization time elapsed: {0,10:### ### ###}\n"
+ "Length is: {1,10:### ### ###}",
Environment.TickCount - start,msBm2.Length);
// Загрузка DetaSetCustomSerializer2
msBm2.Seek(0, SeekOrigin.Begin);
start = Environment.TickCount;
dsLoaded = DetaSetCustomSerializer2.Deserialize(msBm2);
Console.WriteLine("Loading time elapsed: {0,10:### ### ###}",
Environment.TickCount - start);
CmpDataTables(dsLoaded.Tables[0], ds.Tables[0]);
ClearMem();
// Сериализация BinaryFormatter
start = Environment.TickCount;
bf.Serialize(msBf, ds);
Console.WriteLine(
"\n BinaryFormatter\n"
+ "Serialization time elapsed: {0,10:### ### ###}\n"
+ "Length is: {1,10:### ### ###}",
Environment.TickCount - start,msBf.Length);
// Загрузка BinaryFormatter
msBf.Seek(0, SeekOrigin.Begin);
start = Environment.TickCount;
dsLoaded = (DataSet)bf.Deserialize(msBf);
Console.WriteLine("Loading time elapsed: {0,10:### ### ###}",
Environment.TickCount - start);
CmpDataTables(dsLoaded.Tables[0], ds.Tables[0]);
ClearMem();
// Сериализация XmlSerializer
start = Environment.TickCount;
XmlSerializer serializer =
new XmlSerializer(typeof(DataSet));
serializer.Serialize(msXml, ds);
Console.WriteLine(
"\n XmlSerializer\n"
+ "Serialization time elapsed: {0,10:### ### ###}\n"
+ "Length is: {1,10:### ### ###}",
Environment.TickCount - start, msXml.Length);
// Загрузка XmlSerializer
msXml.Seek(0, SeekOrigin.Begin);
start = Environment.TickCount;
dsLoaded = (DataSet)serializer.Deserialize(msXml);
Console.WriteLine("Loading time elapsed: {0,10:### ### ###}",
Environment.TickCount - start);
CmpDataTables(dsLoaded.Tables[0], ds.Tables[0]);
ClearMem();
// Сериализация SoapFormatter
start = Environment.TickCount;
sf.Serialize(msSf, ds);
Console.WriteLine(
"\n SoapFormatter\n"
+ "Serialization time elapsed: {0,10:### ### ###}\n"
+ "Length is: {1,10:### ### ###}",
Environment.TickCount - start, msSf.Length);
// Загрузка SoapFormatter
msSf.Seek(0, SeekOrigin.Begin);
start = Environment.TickCount;
dsLoaded = (DataSet)sf.Deserialize(msSf);
Console.WriteLine("Loading time elapsed: {0,10:### ### ###}",
Environment.TickCount - start);
CmpDataTables(dsLoaded.Tables[0], ds.Tables[0]);
Console.ReadLine();
}
}
}
... << RSDN@Home 1.0 beta 6a >>
Есть логика намерений и логика обстоятельств, последняя всегда сильнее.
Re: Сериализация в дотнете 2 - самопальный датасет
под бинарный вид... отладку и тесты... > > Результаты показались настолько интересными, что решил создать новую тему. >
Еще на 100 сообщений? WOW!
А уж рассказы про то, что BinaryFormatter медленнее, чем Soap это совсем смешно. Провести кучу тестов и не сказать почему он в этом тесте оказался медленне?
Кстати — в вашем тесте не видно, что DataSet могут быть не только текущие значения, но и оригинальные. Если это не нужно, то тогда зачем DataSet использовать?
И хорошо-бы каждый тест оформить в отдельном методе, вызывать его как минимум несколько раз, а потом интерполировать значения. При этом несколько первых результатов можно и отбросить.
Posted via RSDN NNTP Server 1.5 beta
Если у Вас нет паранойи, то это еще не значит, что они за Вами не следят.
Re: Сериализация в дотнете 2 - самопальный датасет
Здравствуйте, VladD2, Вы писали:
VD>На всякий пожарный сделал проверку качества сериализации путем загрузки данных в другой DataSet и сравнения с исходным. Вариант Trantor-а не прошел проверку. Оказалось, что дата записанная в стринг по средствам ToString() и считанная обратно теряет точность. Так что вариант со строковой сериализацией нужно еще дописывать. Если вообще нужна строковая сериализация. Ведь разница в скорости почти 4 раза, и в размерах данных примерно в 1.7 раза.
Согласен с тобой, отправляем мой датасет на свалку истории
VD>За одно измерил время загрузки. Результаты оказались дико интересным! Гляньте на результаты BinaryFormatter-а!
Вот тут мне все предлагали то Sleep поставить, то GC.Collect, а ты попробуй местами поменять форматеры, прогони тест 2-3 раза и посмотри на результаты, у меня почему-то всегда (и при Sleep и GC.Collect) первый форматер приблизительно 1 секунду дольше работал... Я уже списал это на мой тормознтый комп (P3-700-128Мб), но может и у других также...
VD>Попутно обнаружил серьезный недостаток. Дело в том, что Trantor не рассчитывал на то, что ячейки могут содержать DbNull. Мой вариант пока, что тоже не учитывать DbNull, но завтра допишу его шоб поддерживал. Запас по скорости таки есть.
Я об этом подумал, но не проверил Скорее всего есть еще не учтеные моменты....
... << RSDN@Home 1.0 beta 6a >>
В жизни мало быть умным, надо еще быть не дураком.
Re[2]: Сериализация в дотнете 2 - самопальный датасет
Здравствуйте, TK, Вы писали:
TK>Кстати — в вашем тесте не видно, что DataSet могут быть не только текущие значения, но и оригинальные. Если это не нужно, то тогда зачем DataSet использовать?
А что за такие оригинальные значения?
... << RSDN@Home 1.0 beta 6a >>
В жизни мало быть умным, надо еще быть не дураком.
Re[3]: Сериализация в дотнете 2 - самопальный датасет
Здравствуйте, Trantor, Вы писали:
TK>Кстати — в вашем тесте не видно, что DataSet могут быть не только текущие значения, но и оригинальные. Если это не нужно, то тогда зачем DataSet использовать?
T>А что за такие оригинальные значения?
см. DataRow.Item Property (DataColumn, DataRowVersion)
перед тем, как писать тесты хорошо-бы представлять себе назначение DataSet.
А то, что получается: Взяли автомобиль, заменили двигатель внутреннего сгорания на педальную тягу и после этого сравниваем его с велосипедом...
Если у Вас нет паранойи, то это еще не значит, что они за Вами не следят.
Re[2]: Сериализация в дотнете 2 - самопальный датасет
Здравствуйте, TK, Вы писали:
TK>Кстати — в вашем тесте не видно, что DataSet могут быть не только текущие значения, но и оригинальные. Если это не нужно, то тогда зачем DataSet использовать?
Здравствуйте, AndrewVK, Вы писали:
TK>Кстати — в вашем тесте не видно, что DataSet могут быть не только текущие значения, но и оригинальные. Если это не нужно, то тогда зачем DataSet использовать?
AVK>А еще там не сериализуется схема.
Кстати, а почему они стали делать сериализацию целиком, а написали XmlReader/XmlWriter по двоичным данным?
(а то обсуждение большое, перечитывать некогда...)
Если у Вас нет паранойи, то это еще не значит, что они за Вами не следят.
Re[4]: Сериализация в дотнете 2 - самопальный датасет
Здравствуйте, TK, Вы писали:
TK>см. DataRow.Item Property (DataColumn, DataRowVersion) TK>перед тем, как писать тесты хорошо-бы представлять себе назначение DataSet.
Написано перед тестом: T>Вообщем по совету Влада решил написать самопальный сериализатор датасета, функционал его выполнен не полностью (не хватает связей между таблицами,ключевых полей и может еще чего...),но для большинства случаев он сгодится.
По поводу Original значений, единственное что я нашел по этому поводу в MSDN вот это After calling the DataRow object's AcceptChanges method, the Original value becomes identical to the Current value. Т.е. получается что пока мы не вызовем AcceptChanges у таблицы или строчки, в датасете хранится как оригинальное значение, так и текущее, после вызова AcceptChanges оригинальные значения не остаются... Все попытки получить эти значения(оригинальные) при сериализации датасета в XML у меня провалились... Поэтому если несложно напиши пример или кинь ссылку на то как это все должно выглядить в сериализованом виде.
... << RSDN@Home 1.0 beta 6a >>
В жизни мало быть умным, надо еще быть не дураком.
Re[3]: Сериализация в дотнете 2 - самопальный датасет
Здравствуйте, TK, Вы писали:
TK>Кстати, а почему они стали делать сериализацию целиком, а написали XmlReader/XmlWriter по двоичным данным? TK>(а то обсуждение большое, перечитывать некогда...)
Здравствуйте, AndrewVK, Вы писали:
AVK>Что, и все референсы тоже?
Под референсами ты понимаешь System.Data/System.Data, Version 1.0.3300.0 ....?
Если это, то его и стандартный сериализатор датасета недобавляет, а добавляет форматер...
... << RSDN@Home 1.0 beta 6a >>
В жизни мало быть умным, надо еще быть не дураком.
Re[2]: Сериализация в дотнете 2 - самопальный датасет
TK>И хорошо-бы каждый тест оформить в отдельном методе, вызывать его как минимум несколько раз, а потом интерполировать значения. При этом несколько первых результатов можно и отбросить.
Обоими руками поддерживаю. Так, кстати, и возможные интерференции с GC нивелируются, и разброс/погрешность измерений оценить можно.
... << RSDN@Home 1.0 beta 6a >>
Re[5]: Сериализация в дотнете 2 - самопальный датасет
T> Поэтому если несложно напиши пример или кинь ссылку на то как это все должно выглядить в сериализованом виде.
WriteXml, с параметром DiffGram.
Наверное, и сериализация эти "оригинальные значения" должна записывать/считывать — по крайней мере я видел там именно DiffGram-формат.
Также в DataSet есть специальный метод для выделения только изменившихся данных — вроде Clone, но возвращает только изменения. Такое обычно используется для отправки изменений на сервер, или для приёма изменений с сервера.
... << RSDN@Home 1.0 beta 6a >>
Re[6]: Сериализация в дотнете 2 - самопальный датасет
Здравствуйте, TK, Вы писали:
TK>А уж рассказы про то, что BinaryFormatter медленнее, чем Soap это совсем смешно.
Можешь смеяться. Такие резулттаты и у АВК.
TK>Провести кучу тестов и не сказать почему он в этом тесте оказался медленне?
Чтобы сказать почему. Нужно иметь приличный профайлр для дотнета. У меня его нет. Я попытался скомпилировать бинари-форматер от CLI, но он настолько завязан в mscorlib, что выдрать его от туда почти невозможно.
TK>Кстати — в вашем тесте не видно, что DataSet могут быть не только текущие значения, но и оригинальные. Если это не нужно,
Во многих случаях ненужно. Но нет этго скорее потому, что 1) забыли, 2) и так роботенка нехилая.
TK>то тогда зачем DataSet использовать?
Дык потому-что это самый простой и удобный способ передать данные полученные из БД с сервера клиенту. И потому-что для датасета относительно легко написать рукопашную сериализацию. Со структурами все будет намного сложнее. Скорость же их сериализации дотнетными форматерами ненамного выше чем датасета.
TK>И хорошо-бы каждый тест оформить в отдельном методе, вызывать его как минимум несколько раз, а потом интерполировать значения.
Можно попробовать.
TK>При этом несколько первых результатов можно и отбросить.
А от чего же отбрасывать. Интересны и те, и те результаты.
... << RSDN@Home 1.0 beta 6a >>
Есть логика намерений и логика обстоятельств, последняя всегда сильнее.
Re[3]: Сериализация в дотнете 2 - самопальный датасет
Здравствуйте, TK, Вы писали:
TK>Кстати, а почему они стали делать сериализацию целиком, а написали XmlReader/XmlWriter по двоичным данным? TK>(а то обсуждение большое, перечитывать некогда...)
Они это мы или парни из МС? И что значит "написали XmlReader/XmlWriter по двоичным данным?"?
... << RSDN@Home 1.0 beta 6a >>
Есть логика намерений и логика обстоятельств, последняя всегда сильнее.
Re[4]: Сериализация в дотнете 2 - самопальный датасет
Здравствуйте, TK, Вы писали:
TK>см. DataRow.Item Property (DataColumn, DataRowVersion) TK>перед тем, как писать тесты хорошо-бы представлять себе назначение DataSet.
Ну, кое-кто все же знает как жта штука устроена. Ну, а на вопрос я отвтил выше. Времени небыло и нужды особой.
TK>А то, что получается: Взяли автомобиль, заменили двигатель внутреннего сгорания на педальную тягу и после этого сравниваем его с велосипедом...
Я бы это дело с конем педальным сравнил. Так как для передачи данных по сети он подходит так же как педальный конь для поездок в гости.
Будет время доведу серализацию до ума. Заметно медленнее или больше по объему она не станет. Не будет версий — не будет и особого увеличения объема данных и времени на их сериализацию.
В любом случае деже в таком виде датасет становится прекрасным средством передачи данных по сети. Во многих случаях этого достаточно.
... << RSDN@Home 1.0 beta 6a >>
Есть логика намерений и логика обстоятельств, последняя всегда сильнее.
Re[5]: Сериализация в дотнете 2 - самопальный датасет
Здравствуйте, Trantor, Вы писали:
T>попытки получить эти значения(оригинальные) при сериализации датасета в XML у меня провалились... Поэтому если несложно напиши пример или кинь ссылку на то как это все должно выглядить в сериализованом виде.
Надо заполнять датасет из БД, а потом изменять значения ячеек.
... << RSDN@Home 1.0 beta 6a >>
Есть логика намерений и логика обстоятельств, последняя всегда сильнее.
Re[3]: Сериализация в дотнете 2 - самопальный датасет
Здравствуйте, mihailik, Вы писали:
TK>>И хорошо-бы каждый тест оформить в отдельном методе, вызывать его как минимум несколько раз, а потом интерполировать значения. При этом несколько первых результатов можно и отбросить.
M>Обоими руками поддерживаю. Так, кстати, и возможные интерференции с GC нивелируются, и разброс/погрешность измерений оценить можно.
Попробуем.
... << RSDN@Home 1.0 beta 6a >>
Есть логика намерений и логика обстоятельств, последняя всегда сильнее.
Re[5]: Сериализация в дотнете 2 - самопальный датасет
Здравствуйте, VladD2, Вы писали:
TK>>Кстати, а почему они стали делать сериализацию целиком, а написали XmlReader/XmlWriter по двоичным данным? TK>>(а то обсуждение большое, перечитывать некогда...)
VD>Они это мы или парни из МС? И что значит "написали XmlReader/XmlWriter по двоичным данным?"?
Он имеет ввиду что DataSet умеет писать не только в готовый xml, но и в XmlWriter (это некое подобие SAX, только без событий). Таким образом xml-формат остается, но его можно сделать бинарным, дабы потом не тратить много времени на парсинг. Только думается мне что сильно это не поможет, будет немножко быстрее BinaryFormatter, хотя конечно померить стоило бы.
Здравствуйте, VladD2, Вы писали:
VD>Это копейки. Да и не нужна она за частую. Ты хоть раз ее использовал? В том же Хоуме, например?
А в хоуме и датасеты в общем то не нужны. Массив не хуже, просто лень было обертки писать.
VD>Зато деже в этом виде ее можно использовать в том же Хоуме.
Здравствуйте, VladD2, Вы писали:
TK>>А уж рассказы про то, что BinaryFormatter медленнее, чем Soap это совсем смешно.
VD>Можешь смеяться. Такие резулттаты и у АВК.
Здравствуйте, AndrewVK, Вы писали:
VD>Они это мы или парни из МС? И что значит "написали XmlReader/XmlWriter по двоичным данным?"?
AVK>Он имеет ввиду что DataSet умеет писать не только в готовый xml, но и в XmlWriter (это некое подобие SAX, только без событий). Таким образом xml-формат остается, но его можно сделать бинарным, дабы потом не тратить много времени на парсинг. Только думается мне что сильно это не поможет, будет немножко быстрее BinaryFormatter, хотя конечно померить стоило бы.
Могу сказать, что пустой XmlWriter без реализации работает на тестах Влада в 2 раза быстрее чем стандартный. так-что выйграть можно будет 20-30% максимум...
Если у Вас нет паранойи, то это еще не значит, что они за Вами не следят.
Re[7]: Сериализация в дотнете 2 - самопальный датасет
Здравствуйте, TK, Вы писали:
TK>Могу сказать, что пустой XmlWriter без реализации работает на тестах Влада в 2 раза быстрее чем стандартный. так-что выйграть можно будет 20-30% максимум...
Дык это при записи, там большого выигрыша и не должно быть, а вот при чтении может стать все интереснее.
Здравствуйте, AndrewVK, Вы писали:
AVK>Он имеет ввиду что DataSet умеет писать не только в готовый xml, но и в XmlWriter (это некое подобие SAX, только без событий). Таким образом xml-формат остается, но его можно сделать бинарным, дабы потом не тратить много времени на парсинг. Только думается мне что сильно это не поможет, будет немножко быстрее BinaryFormatter, хотя конечно померить стоило бы.
Слается мне, что если сделать ручную сериализацию на SAX-е, то скорость будет очень высокой, а от стандартных средств многого не добьешся.
... << RSDN@Home 1.0 beta 6a >>
Есть логика намерений и логика обстоятельств, последняя всегда сильнее.
Re[5]: Сериализация в дотнете 2 - самопальный датасет
Здравствуйте, AndrewVK, Вы писали:
AVK>Здравствуйте, VladD2, Вы писали:
VD>>Это копейки. Да и не нужна она за частую. Ты хоть раз ее использовал? В том же Хоуме, например?
AVK>А в хоуме и датасеты в общем то не нужны. Массив не хуже, просто лень было обертки писать.
Если бы ты читал вниательнее, то понял бы, ято массивы структур — это не меньшие тормоза чем датасет. Причем датасет можно лгко сериалзовать вручную (лбой датасет, с любым форматом), в то время как массмв структур сериалзовать автоматически сложнее. Да и бестолку это, так как из базы данных автоматически массив не вынеш.
VD>>Зато деже в этом виде ее можно использовать в том же Хоуме.
AVK>А стоит ли там вобще датасеты использовать?
См. выше. И вообще я тут уже про это сто раз говорил.
... << RSDN@Home 1.0 beta 6a >>
Есть логика намерений и логика обстоятельств, последняя всегда сильнее.
Re[9]: Сериализация в дотнете 2 - самопальный датасет
Здравствуйте, AndrewVK, Вы писали:
VD>>Трентор в своей реализации как раз указал, что связи не сериализуются. Но это копейки. И по памяти и по времени.
AVK>По памяти согласен, по времени не уверен, особенно когда датасеты будут небольшими.
Ну, твоя уверенность — это твои проблемы.
Чето там делать то? Пробежаться по списку и записать его содержимое. Если список пуст, то и писать нечего.
Намного больше времени отнимет сериализаыия DbNull, так как придется писать эти значения перед значениями ячеек строк.
... << RSDN@Home 1.0 beta 6a >>
Есть логика намерений и логика обстоятельств, последняя всегда сильнее.
Re[4]: Сериализация в дотнете 2 - самопальный датасет
Здравствуйте, AndrewVK, Вы писали:
VD>>Можешь смеяться. Такие резулттаты и у АВК.
AVK>Ну не правда, Soap я вобще не использовал.
Но бинари у тебя был медленнее чем хмл.
Кстати, при повторе или некоторой перестоновке тормоза загрузки бинарного сериализатора исчезают. Но один хрен он работает на уровне хмл-сериализатора. Что вполне обхяснимо. Основное время тратится на процедуру сериализации реализованную в самом датасете.
... << RSDN@Home 1.0 beta 6a >>
Есть логика намерений и логика обстоятельств, последняя всегда сильнее.
Re[5]: Сериализация в дотнете 2 - самопальный датасет
Здравствуйте, VladD2, Вы писали:
VD>>>Можешь смеяться. Такие резулттаты и у АВК.
AVK>>Ну не правда, Soap я вобще не использовал.
VD>Но бинари у тебя был медленнее чем хмл.
Чем XmlSerializer. Это вполне объяснимо — он не использует при сериализации рефлекшен и XML DOM
Здравствуйте, VladD2, Вы писали:
VD>Слается мне, что если сделать ручную сериализацию на SAX-е,
Блин, сколько тебе повторять — нет в дотнете SAX, есть XMLReader/XMLWriter. Столь же быстрые, но пользоваться удобнее.
VD>то скорость будет очень высокой, а от стандартных средств многого не добьешся.
XmlSerializer как раз подобный механизм и использует.
Здравствуйте, AndrewVK, Вы писали:
AVK>Блин, сколько тебе повторять — нет в дотнете SAX, есть XMLReader/XMLWriter. Столь же быстрые, но пользоваться удобнее.
Дык тот же ДатаСет при сериализациии как раз их и использует. Вот только толку от этого никакого. Так что или они не столь же быстрые, или все же нужно делать с ними, но вручную и не так как в датасете.
А то что в дотрнете нет чистого SAX-а... это все фигня. Можно или сделать обертку для native-ного или создать версию для дотнета.
VD>>то скорость будет очень высокой, а от стандартных средств многого не добьешся.
AVK>XmlSerializer как раз подобный механизм и использует.
И что толку? Результаыт ты вроде видел...
... << RSDN@Home 1.0 beta 6a >>
Есть логика намерений и логика обстоятельств, последняя всегда сильнее.
Re[7]: Сериализация в дотнете 2 - самопальный датасет
Здравствуйте, AndrewVK, Вы писали:
AVK>Здравствуйте, VladD2, Вы писали:
VD>>Если бы ты читал вниательнее, то понял бы, ято массивы структур — это не меньшие тормоза чем датасет.
AVK>Его можно тупо в ансейфе одним плевком конвертануть в byte[]. Будет ультрабыстро.
Тупо нельзя. Там строка. К тому это еще одно копирование.
Да и причем тут это? Главное, что для каждогй структуры придется писать самостоятельную сериализацию. Для датасета же можно написать один раз универсальную сериализацию. Она будет эффективная и универсальная.
... << RSDN@Home 1.0 beta 6a >>
Есть логика намерений и логика обстоятельств, последняя всегда сильнее.
Re[6]: Сериализация в дотнете 2 - самопальный датасет
Здравствуйте, AndrewVK, Вы писали:
AVK>Чем XmlSerializer. Это вполне объяснимо — он не использует при сериализации рефлекшен и XML DOM
А я и не спорю, что понятно. Вот только это чистой воды доказательство кривости идеологии принятов для форматеров.
Кстати, я последовал совету ТК и сделал несколько повторов. Бинари форматер разогнался (на втором повторе) и стал работать примерно со скоростью XmlSerializer-а. Такой же эффект происходит при некоторый перестановках тестов. Причем на другие способы перестановки влияют незначительно. Что-то там не так.
... << RSDN@Home 1.0 beta 6a >>
Есть логика намерений и логика обстоятельств, последняя всегда сильнее.
Re[7]: Сериализация в дотнете 2 - самопальный датасет
Здравствуйте, VladD2, Вы писали:
AVK>>Чем XmlSerializer. Это вполне объяснимо — он не использует при сериализации рефлекшен и XML DOM
VD>А я и не спорю, что понятно. Вот только это чистой воды доказательство кривости идеологии принятов для форматеров.
Влад, но ведь форматтеры выполняют куда больше работы и они куда универсальнее. Просто по моему вместо того чтобы сделать нормальные сериализаторы они предлагают пользоваться форматтерами.
VDТакой же эффект происходит при некоторый перестановках тестов. Причем на другие способы перестановки влияют незначительно. Что-то там не так.
Ну так исходники есть. IT вроде ротор собрал — можно попросить его прогнать форматтер профайлером и посмотреть где кака.
Здравствуйте, VladD2, Вы писали:
VD>Дык тот же ДатаСет при сериализациии как раз их и использует. Вот только толку от этого никакого. Так что или они не столь же быстрые,
Ну так XmlSerializer, пользуясь ими, работает очень быстро. Значит дело не в них, а в DataSet
VD>А то что в дотрнете нет чистого SAX-а... это все фигня. Можно или сделать обертку для native-ного или создать версию для дотнета.
Зачем обертывать чужой парсер, если есть родной?
AVK>>XmlSerializer как раз подобный механизм и использует.
VD>И что толку? Результаыт ты вроде видел...
Здравствуйте, AndrewVK, Вы писали:
AVK>Зачем обертывать чужой парсер, если есть родной?
Я же сказал. Если дело в XMLReader... Не факт что он действительно работает быстро. Это нужно проверять.
AVK>Результат очень хороший. Не забывай что это xml.
Блин, твой необоснованный оптимизм просто поражает. Посмотри на результат Трентора. Он между прочем просто текст писал. Если в его код вставить теги и именами олей, то будет тот же эффект, что и с XMLWriter. С загрузкой то же фигня.
... << RSDN@Home 1.0 beta 6a >>
Есть логика намерений и логика обстоятельств, последняя всегда сильнее.
Re[8]: Сериализация в дотнете 2 - самопальный датасет
Здравствуйте, AndrewVK, Вы писали:
AVK>Влад, но ведь форматтеры выполняют куда больше работы и они куда универсальнее. Просто по моему вместо того чтобы сделать нормальные сериализаторы они предлагают пользоваться форматтерами.
Понятно, что универсальнее. Вот только мне почему-то не хочется платить десятикратными тормозами за эту универсальность. Особенно если речь идет о передачи данных по сети. Где мне полностью плевать на версии, а нужна масимальная скорость.
Всю эту универсальность можно было бы реализовать и без таких страшных провалов производительности. Все что нужно было сделать — это генерировать код сериализации помпилятором, а возможно просто более качественно написать код сериализации. Дтя того же датасета ведь сереализация написана вручную. Но почему-то только в хмл, и почему то тормоза такие же как при использовании SOAP-форматера.
AVK>VDТакой же эффект происходит при некоторый перестановках тестов. Причем на другие способы перестановки влияют незначительно. Что-то там не так.
AVK>Ну так исходники есть.
И что толку с них?
AVK>IT вроде ротор собрал — можно попросить его прогнать форматтер профайлером и посмотреть где кака.
У меня нет нормального профайлера. Если есть что на примете, подкинь...
А вообще, сделать это точно надо. Мне уже просто интересно, что они там такое натварили.
... << RSDN@Home 1.0 beta 6a >>
Есть логика намерений и логика обстоятельств, последняя всегда сильнее.
Re: Сериализация в дотнете 2 - самопальный датасет
1. Поддерживаются DbNull-значения.
2. Поддерживаются версии строк.
3. Код разбит так чтобы можно было сериализовать отдельные DataTable-ы. Введены функции:
4. Модифицирован тест:
4.1. Тесты переставлены местами и повторяются по 3 раза.
4.2. В DataTable вносятся записи поля в которых периодически содержат DbNull.
4.3. Добавлены записи с разными версиями (удаленные, добавленные, измененные, немодифицированные).
Результат:
1. Время сериализации несколько увеличилось, но не существенно (один фиг быстрее чем у Трентора).
2. Вырос объем данных: По 2 байта на строку плюс если есть другая версия, то ее размер.
Что не поддерживается сериализатором?
1. Индексы и представления (view). Сделать можно, но это тоже время...
2. Вычисляемые поля.
3. Констрэйны.
Возможно я пропустил еще что-то...
Но это все ерунда, так как много времени сериализация остального незаймет, так как остались исключительно декларативные вещи.
Уже в этом виде датасет можно смело использовать в 90% приложений, так как для предачи данных извращения с многотабличностью ненужны. Индексы можно строить и на клиенте (должен же он хоть что-то делать).
using System;
using System.IO;
using System.Data;
using System.Collections;
namespace RSDN
{
class DetaSerializer
{
static readonly DataRowVersion[] _aryVer = new DataRowVersion[2]
{ DataRowVersion.Original, DataRowVersion.Current };
// Массив для перемапливания TypeCode на Type
// TypeCode это перечисление включающее константы для базовых типов.static readonly Type[] _TypeMap;
// Статический конструктор. Нужен для инициализации _TypeMap.static DetaSerializer()
{
// TypeCode.DBNull и TypeCode.Empty пропущены, так как они
// по сути, не являются типами.
_TypeMap = new Type[(int)TypeCode.String + 1];
_TypeMap[(int)TypeCode.Object] = typeof(Object);
_TypeMap[(int)TypeCode.Boolean] = typeof(Boolean);
_TypeMap[(int)TypeCode.Char] = typeof(Char);
_TypeMap[(int)TypeCode.SByte] = typeof(SByte);
_TypeMap[(int)TypeCode.Byte] = typeof(Byte);
_TypeMap[(int)TypeCode.Int16] = typeof(Int16);
_TypeMap[(int)TypeCode.UInt16] = typeof(UInt16);
_TypeMap[(int)TypeCode.Int32] = typeof(Int32);
_TypeMap[(int)TypeCode.UInt32] = typeof(UInt32);
_TypeMap[(int)TypeCode.Int64] = typeof(Int64);
_TypeMap[(int)TypeCode.UInt64] = typeof(UInt64);
_TypeMap[(int)TypeCode.Single] = typeof(Single);
_TypeMap[(int)TypeCode.Double] = typeof(Double);
_TypeMap[(int)TypeCode.Decimal] = typeof(Decimal);
_TypeMap[(int)TypeCode.DateTime] = typeof(DateTime);
_TypeMap[(int)TypeCode.String] = typeof(String);
}
///////////////////////////////////////////////////////////////////////
// Сериализация.public static void SerializeDataSet(Stream stream, DataSet ds)
{
BinaryWriter bw = new BinaryWriter(stream);
DataTableCollection tables = ds.Tables;
bw.Write(ds.DataSetName);
bw.Write(tables.Count);
foreach(DataTable dt in tables)
{ // Вообще-то foreach-и лучше на всякий пожарный избегать.
// Но мне было в лом.
SerializeDataTable(bw, dt);
}
}
public static void SerializeDataTable(BinaryWriter bw, DataTable dt)
{
DataColumnCollection columns = dt.Columns;
int iColCount = columns.Count;
TypeCode[] colTypeCodes = new TypeCode[iColCount];
bool[] aryIsNullabl = new bool[iColCount];
// Имя таблицы
bw.Write(dt.TableName);
bw.Write(iColCount);
// Получаем и записываем описание колонок.for(int i = 0; i < iColCount; i++)
{
DataColumn dc = columns[i];
// Получаем TypeCode для типа обрабатываемой колонки.
TypeCode tc = Type.GetTypeCode(dc.DataType);
// Запоминаем TypeCode колонки в соотвествующем массиве.
colTypeCodes[i] = tc;
bw.Write(dc.ColumnName);
// Записываем TypeCode как Int32. Можно было бы и
// сэкономить 3 байта. :)
bw.Write((Int32)tc);
// Создаем массив информации о поддержке колонками DBNull
aryIsNullabl[i] = dc.AllowDBNull;
}
// Записываем битовое поле описывающее колонки поддерживающие
// DBNull. Если бит поднят, значит, колонка поддерживает DBNull.
BitArray bitsNull = new BitArray(aryIsNullabl);
byte[] byteNull = new byte[(iColCount + 7) / 8];
bitsNull.CopyTo(byteNull, 0);
bw.Write(byteNull);
///////////////////////////////////////////////////////////////
// add data
// count rows
bw.Write(dt.Rows.Count);
// Записываем строкиforeach(DataRow dr in dt.Rows)
{
byte verFlags = 0;
int iVerStart;
int iVerEnd;
// Разбираемся, какие версии нужно писать.
// Всего есть два варианта: Original и Current
DataRowState state = dr.RowState;
switch(state)
{
// Original + Current и они равны!case DataRowState.Unchanged:
iVerStart = 0;
iVerEnd = 0;
verFlags = 0;
break;
case DataRowState.Deleted: // Только Original
iVerStart = 0;
iVerEnd = 0;
verFlags = 1;
break;
case DataRowState.Added: // Только Current
iVerStart = 1;
iVerEnd = 1;
verFlags = 2;
break;
// Original + Current и они НЕ равны!case DataRowState.Modified:
iVerStart = 0;
iVerEnd = 1;
verFlags = 3;
break;
default:
throw new ApplicationException(
"Недопустимое состояние строки: " + state.ToString());
}
// Пишем описание версий. Временно, так как на этом мы
// теряем байт на строку. Куда лучше писать дополнительные два
// бита в битовое поле DbNull (хотя это и не красиво).
bw.Write(verFlags);
// Записываем версии текущей строки. Всего их может быть две.
// в принципе можно было бы для случая DataRowState.Modified
// писать только дельту данных. Но это как-нибудь потом. :)for(int iVetIndex = iVerStart; iVetIndex <= iVerEnd; iVetIndex++)
{
DataRowVersion drv = _aryVer[iVetIndex];
// Создаем и заполняем битовое поле. Если бит поднят,
// значит, соответствующая колонка содержит DBNull.
bitsNull.SetAll(false);
for(int i = 0; i < iColCount; i++)
{
if(dr[i, drv] == DBNull.Value)
bitsNull.Set(i, true);
}
bitsNull.CopyTo(byteNull, 0);
// Записываем битовое поле в стрим.
bw.Write(byteNull);
// Перебираем колонки и пишем данные...for(int i = 0; i < iColCount; i++)
{
// Если колонка содержит DBNull, записывать ее значение
// ненужно.object data = dr[i, drv];
if(data == DBNull.Value) // Учитываем версию!continue;
// Записываем данные ячейки.switch(colTypeCodes[i])
{ // Каждому типу соответствует переопределенная функция...case TypeCode.Boolean: bw.Write((Boolean)data); break;
case TypeCode.Char: bw.Write((Char)data); break;
case TypeCode.SByte: bw.Write((SByte)data); break;
case TypeCode.Byte: bw.Write((Byte)data); break;
case TypeCode.Int16: bw.Write((Int16)data); break;
case TypeCode.UInt16: bw.Write((UInt16)data); break;
case TypeCode.Int32: bw.Write((Int32)data); break;
case TypeCode.UInt32: bw.Write((UInt32)data); break;
case TypeCode.Int64: bw.Write((Int64)data); break;
case TypeCode.UInt64: bw.Write((UInt64)data); break;
case TypeCode.Single: bw.Write((Single)data); break;
case TypeCode.Double: bw.Write((Double)data); break;
case TypeCode.Decimal: bw.Write((Decimal)data); break;
case TypeCode.DateTime:
// Для DateTime приходится выпендриваться особым образом.
bw.Write(((DateTime)(data)).ToFileTime());
break;
case TypeCode.String: bw.Write((String)data); break;
default:
// На всякий случай пробуем записать неопознанный тип
// виде строки.
bw.Write(data.ToString());
break;
}
}
}
}
}
public static void SerializeDataTable(Stream stream, DataTable dt)
{
BinaryWriter bw = new BinaryWriter(stream);
SerializeDataTable(bw, dt);
}
///////////////////////////////////////////////////////////////////////
// Десериализацияpublic static DataTable DeserializeTable(BinaryReader br)
{
DataColumn dc;
DataRow dr;
// Имя DataTable тоже передается в качестве параметра конструктора.
DataTable dt = new DataTable(br.ReadString());
dt.BeginLoadData();
int iColCount = br.ReadInt32();
// В этом массив будут записаны TypeCode-ы для колонок DataTable-а.
TypeCode[] colTypeCodes = new TypeCode[iColCount];
// А в этот массив будут записаны типы (Type) колонок DataTable-а
// соотвествующие TypeCode-ам.for(int c = 0; c < iColCount; c++)
{
string colName = br.ReadString();
// Считываем TypeCode.
TypeCode tc = (TypeCode)br.ReadInt32();
// Помещаем TypeCode в массив для дальнейшего использования.
colTypeCodes[c] = tc;
// Получаем (через мап) тип соответствующий TypeCode-у.
Type type = _TypeMap[(int)tc];
// Создаем колонку с полученным именем и типом.
dc = new DataColumn(colName, type);
dt.Columns.Add(dc);
}
// Считываем список nullabl-колонок.int iBitLenInBytes = (iColCount + 7) / 8;
byte[] byteNull = new byte[iBitLenInBytes];
br.Read(byteNull, 0, iBitLenInBytes);
BitArray bitsNull = new BitArray(byteNull);
bitsNull.Length = iColCount;
bool[] aryIsNullabl = new bool[iColCount];
bitsNull.CopyTo(aryIsNullabl, 0);
object[] ad = new object[iColCount];
int counRows = br.ReadInt32();
DataRowCollection rows = dt.Rows;
for(int r = 0; r < counRows; r++)
{
// Читаем описание версий. Временно, так как на этом мы
// теряем байт на строку.byte verFlags = br.ReadByte();
int iVerStart;
int iVerEnd;
DataRowState drs;
switch(verFlags)
{
// Original + Current и они равны!case 0: // DataRowState.Unchanged
iVerStart = 0;
iVerEnd = 0;
drs = DataRowState.Unchanged;
break;
case 1: // DataRowState.Deleted Только Original
iVerStart = 0;
iVerEnd = 0;
drs = DataRowState.Deleted;
break;
case 2: // DataRowState.Added Только Current
iVerStart = 1;
iVerEnd = 1;
drs = DataRowState.Added;
break;
// Original + Current и они НЕ равны!case 3: // DataRowState.Modified
iVerStart = 0;
iVerEnd = 1;
drs = DataRowState.Modified;
break;
default:
throw new ApplicationException(
"Недопустимое состояние строки. Сбой при загрузке.");
}
// Считываем данные.
dr = dt.NewRow();
rows.Add(dr);
dr.BeginEdit();
// Считываем версии текущей строки.for(int iVetIndex = iVerStart; iVetIndex <= iVerEnd; iVetIndex++)
{
br.Read(byteNull, 0, iBitLenInBytes);
bitsNull = new BitArray(byteNull);
for(int i = 0; i < iColCount; i++)
{
if(bitsNull.Get(i))
{
dr[i] = DBNull.Value;
continue;
}
switch(colTypeCodes[i])
{
case TypeCode.Boolean: dr[i] = br.ReadBoolean(); break;
case TypeCode.Char: dr[i] = br.ReadChar(); break;
case TypeCode.SByte: dr[i] = br.ReadSByte(); break;
case TypeCode.Byte: dr[i] = br.ReadByte(); break;
case TypeCode.Int16: dr[i] = br.ReadInt16(); break;
case TypeCode.UInt16: dr[i] = br.ReadUInt16(); break;
case TypeCode.Int32: dr[i] = br.ReadInt32(); break;
case TypeCode.UInt32: dr[i] = br.ReadUInt32(); break;
case TypeCode.Int64: dr[i] = br.ReadInt64(); break;
case TypeCode.UInt64: dr[i] = br.ReadUInt64(); break;
case TypeCode.Single: dr[i] = br.ReadSingle(); break;
case TypeCode.Double: dr[i] = br.ReadDouble(); break;
case TypeCode.Decimal: dr[i] = br.ReadDecimal(); break;
case TypeCode.DateTime:
dr[i] = DateTime.FromFileTime(br.ReadInt64());
break;
case TypeCode.String: dr[i] = br.ReadString(); break;
default:
dr[i] = Convert.ChangeType(br.ReadString(),
colTypeCodes[i]);
break;
}
}
if(iVetIndex == 0)
{
dr.AcceptChanges();
if(iVerEnd > 0)
dr.BeginEdit();
}
}
if(drs == DataRowState.Deleted)
dr.Delete();
dr.EndEdit();
}
dt.EndLoadData();
return dt;
}
public static DataTable DeserializeTable(Stream stream)
{
BinaryReader br = new BinaryReader(stream);
return DeserializeTable(br);
}
public static DataSet DeserializeDataSet(Stream stream)
{
BinaryReader br = new BinaryReader(stream);
// Считываем имя DataSet и создаем его...
DataSet ds = new DataSet(br.ReadString());
//ds.BeginInit();int counTables = br.ReadInt32();
DataTable dt;
for(int t = 0; t < counTables; t++)
{
dt = DeserializeTable(br);
ds.Tables.Add(dt);
}
//ds.AcceptChanges(); с версиями так нельзя... :)
//ds.EndInit();return ds;
}
}
}
Тест:
using System;
using System.Data;
using System.Xml;
using System.Xml.Serialization;
using System.IO;
using System.Runtime.Serialization.Formatters.Binary;
using System.Runtime.Serialization.Formatters.Soap;
using RSDN;
namespace SerializeTest
{
class Class1
{
static bool IsOriginalVer(DataRow dr)
{
DataRowState state = dr.RowState;
switch(state)
{
// Original + Current и они равны!case DataRowState.Unchanged: return true;
case DataRowState.Deleted: return true;
case DataRowState.Added: return false;
case DataRowState.Modified: return true;
default:
throw new ApplicationException(
"Недопустимое состояние строки: " + state.ToString());
}
}
static bool IsCurrentVer(DataRow dr)
{
DataRowState state = dr.RowState;
switch(state)
{
// Original + Current и они равны!case DataRowState.Unchanged: return true;
case DataRowState.Deleted: return false;
case DataRowState.Added: return true;
case DataRowState.Modified: return true;
default:
throw new ApplicationException(
"Недопустимое состояние строки: " + state.ToString());
}
}
static bool CmpDataSets(DataSet ds1, DataSet ds2)
{
if(!CmpDataTables(ds1.Tables[0], ds2.Tables[0]))
{
Console.WriteLine("DataTable НЕ одинаковые!!!");
return false;
}
return true;
}
// Сравнивает два DataTablestatic bool CmpDataTables(DataTable dt1, DataTable dt2)
{
if(dt1.Rows.Count != dt1.Rows.Count)
return false;
if(dt1.Columns.Count != dt1.Columns.Count)
return false;
int iCols = dt1.Columns.Count;
DataRowCollection rows1 = dt1.Rows;
DataRowCollection rows2 = dt2.Rows;
int iRows = rows1.Count;
for(int r = 0; r < iRows; r++)
{
DataRow dr1 = rows1[r];
DataRow dr2 = rows2[r];
if(IsCurrentVer(dr1))
{
if(!IsCurrentVer(dr2))
return false;
for(int i = 0; i < iCols; i++)
{
if(!dr1[i, DataRowVersion.Current].Equals(
dr2[i, DataRowVersion.Current]))
return false;
}
}
else
if(IsCurrentVer(dr2))
return false;
if(IsOriginalVer(dr1))
{
if(!IsOriginalVer(dr2))
return false;
for(int i = 0; i < iCols; i++)
{
if(!dr1[i, DataRowVersion.Original].Equals(
dr2[i, DataRowVersion.Original]))
return false;
}
}
else
if(IsOriginalVer(dr2))
return false;
}
return true;
}
// Вызывает суровую уборку мусора. :)static void ClearMem()
{
GC.Collect();
GC.WaitForPendingFinalizers();
GC.Collect();
GC.WaitForPendingFinalizers();
}
[STAThread]
static void Main(string[] args)
{
DataSet ds = new DataSet("myDataSet");
DataTable dt = new DataTable("Table1");
dt.Columns.Add(new DataColumn("col0", typeof(Int32)));
dt.Columns[0].AllowDBNull = false;
dt.Columns.Add(new DataColumn("col1", typeof(string)));
dt.Columns.Add(new DataColumn("col2", typeof(DateTime)));
dt.Columns.Add(new DataColumn("col3", typeof(byte)));
dt.Columns[3].AllowDBNull = false;
DataRow dr;
dt.BeginLoadData();
for(int i = 0; i < 40000; i++)
{
dr = dt.NewRow();
dr.BeginEdit();
dr[0] = i;
// В каждой 100-й строке колонка 1 заполняется DBNull-ом.
dr[1] = i % 100 == 0 ? DBNull.Value : (object)("2test" + i.ToString());
// В каждой 40-й строке колонка 2 заполняется DBNull-ом.
dr[2] = i % 40 == 0 ? DBNull.Value : (object)DateTime.Now;
dr[3] = (byte)i % 255;
dt.Rows.Add(dr);
if(i % 20 == 0)
{
// Каждая 20 строка помечается как отредактированная
dr.AcceptChanges();
dr[0] = i;
}
else if(i % 21 == 0)
{
// Каждая 21 строка помечается как удаленная
dr.AcceptChanges();
dr.Delete();
}
else if(i % 22 != 0)
// Все остальные строки кроме каждой 22 помечаются как
// оригинальные (не модифицированные). Таким обрезом каждая 22
// строка остается помеченной как добавленная.
dr.AcceptChanges();
dr.EndEdit();
}
dt.EndLoadData();
ds.Tables.Add(dt);
//ds.AcceptChanges();
//ds.Tables[0].Rows[0].HasVersion();
// Console.WriteLine(ds.Tables[0].Rows[0].RowState);
// ds.AcceptChanges();
// Console.WriteLine(ds.Tables[0].Rows[0].RowState);
RsdnDetaSerializer(ds);
RsdnDetaSerializer(ds);
RsdnDetaSerializer(ds);
TestBinaryFormatter(ds);
TestBinaryFormatter(ds);
TestBinaryFormatter(ds);
TestXmlSerializer(ds);
TestXmlSerializer(ds);
TestXmlSerializer(ds);
TestSoapFormatter(ds);
TestSoapFormatter(ds);
TestSoapFormatter(ds);
frmDataset frm = new frmDataset();
frm.dataGrid1.DataSource = ds.Tables[0];
frm.ShowDialog();
Console.WriteLine("\nDone!");
Console.ReadLine();
}
static void TestDataSetHandsSerializate(DataSet ds)
{
int start; // Используется для вычисления времени выполнения
ClearMem(); // Вызов GC чтобы небыло налаженки...
// Стрим для ручной сериализации через DetaSetCustomSerializer2.
MemoryStream ms = new MemoryStream(1024);
// Сериализация DataSetHandsSerializate
start = Environment.TickCount;
DataSetHandsSerializate.Serialize(ms, ds);
int timeSerialize = Environment.TickCount - start;
// Загрузка DataSetHandsSerializate
ms.Seek(0, SeekOrigin.Begin);
start = Environment.TickCount;
DataSet dsLoaded = DataSetHandsSerializate.Deserialize(ms);
Console.WriteLine(
"\n DataSetHandsSerializate\n"
+ "Serialization: {0,10:### ### ###} ms "
+ "Loading: {1,10:### ### ###}\n"
+ "Length is: {2,10:### ### ###}",
timeSerialize, Environment.TickCount - start, ms.Length);
CmpDataSets(dsLoaded, ds);
}
static void RsdnDetaSerializer(DataSet ds)
{
int start; // Используется для вычисления времени выполнения
ClearMem();
// Стрим для ручной сериализации через DataSetHandsSerializate.
MemoryStream ms = new MemoryStream(1024);
// Сериализация DetaSetCustomSerializer2
start = Environment.TickCount;
DetaSerializer.SerializeDataSet(ms, ds);
int timeSerialize = Environment.TickCount - start;
// Загрузка DetaSetCustomSerializer2
ms.Seek(0, SeekOrigin.Begin);
start = Environment.TickCount;
DataSet dsLoaded = DetaSerializer.DeserializeDataSet(ms);
Console.WriteLine(
"\n RsdnDetaSerializer\n"
+ "Serialization: {0,10:### ### ###} ms "
+ "Loading: {1,10:### ### ###} "
+ "Length: {2,10:### ### ###} bytes",
timeSerialize, Environment.TickCount - start, ms.Length);
// dsLoaded.Tables[0].Rows[2][0] = 123; // проверка CmpDataSets
CmpDataSets(dsLoaded, ds);
// тест.
//frmDataset frm = new frmDataset();
//frm.dataGrid1.DataSource = ds.Tables[0];
//frm.ShowDialog();
}
static void TestBinaryFormatter(DataSet ds)
{
int start; // Используется для вычисления времени выполнения
ClearMem();
// Стрим для сериализации через BinaryFormatter
MemoryStream ms = new MemoryStream(1024);
BinaryFormatter bf = new BinaryFormatter();
// Сериализация BinaryFormatter
start = Environment.TickCount;
bf.Serialize(ms, ds);
int timeSerialize = Environment.TickCount - start;
// Загрузка BinaryFormatter
ms.Seek(0, SeekOrigin.Begin);
start = Environment.TickCount;
DataSet dsLoaded = (DataSet)bf.Deserialize(ms);
Console.WriteLine(
"\n BinaryFormatter\n"
+ "Serialization: {0,10:### ### ###} ms "
+ "Loading: {1,10:### ### ###} "
+ "Length: {2,10:### ### ###} bytes",
timeSerialize, Environment.TickCount - start, ms.Length);
CmpDataSets(dsLoaded, ds);
}
static void TestXmlSerializer(DataSet ds)
{
int start; // Используется для вычисления времени выполнения
ClearMem();
// Стрим для сериализации через XmlSerializer
MemoryStream ms = new MemoryStream(1024);
// Сериализация XmlSerializer
start = Environment.TickCount;
XmlSerializer serializer = new XmlSerializer(typeof(DataSet));
serializer.Serialize(ms, ds);
int timeSerialize = Environment.TickCount - start;
// Загрузка XmlSerializer
ms.Seek(0, SeekOrigin.Begin);
start = Environment.TickCount;
DataSet dsLoaded = (DataSet)serializer.Deserialize(ms);
Console.WriteLine(
"\n XmlSerializer\n"
+ "Serialization: {0,10:### ### ###} ms "
+ "Loading: {1,10:### ### ###} "
+ "Length: {2,10:### ### ###} bytes",
timeSerialize, Environment.TickCount - start, ms.Length);
CmpDataSets(dsLoaded, ds);
}
static void TestSoapFormatter(DataSet ds)
{
int start; // Используется для вычисления времени выполнения
ClearMem();
// Стрим для сериализации через SoapFormatter
MemoryStream ms = new MemoryStream(1024);
SoapFormatter sf = new SoapFormatter();
// Сериализация SoapFormatter
start = Environment.TickCount;
sf.Serialize(ms, ds);
int timeSerialize = Environment.TickCount - start;
// Загрузка SoapFormatter
ms.Seek(0, SeekOrigin.Begin);
start = Environment.TickCount;
DataSet dsLoaded = (DataSet)sf.Deserialize(ms);
Console.WriteLine(
"\n SoapFormatter\n"
+ "Serialization: {0,10:### ### ###} ms "
+ "Loading: {1,10:### ### ###} "
+ "Length: {2,10:### ### ###} bytes",
timeSerialize, Environment.TickCount - start, ms.Length);
CmpDataSets(dsLoaded, ds);
}
}
}
... << RSDN@Home 1.0 beta 6a >>
Есть логика намерений и логика обстоятельств, последняя всегда сильнее.
Re: Сериализация в дотнете 2 - самопальный датасет
Здравствуйте, VladD2, Вы писали:
VD>Всю эту универсальность можно было бы реализовать и без таких страшных провалов производительности. Все что нужно было сделать — это генерировать код сериализации помпилятором,
Это возможно если заранее известно какие конкретно объекты будут сериализоваться. А это приведет к XmlSerializer, для которого сериализуемые данные нужно заранее размечать.
Здравствуйте, VladD2, Вы писали:
VD>Блин, твой необоснованный оптимизм просто поражает. Посмотри на результат Трентора. Он между прочем просто текст писал. Если в его код вставить теги и именами олей, то будет тот же эффект, что и с XMLWriter. С загрузкой то же фигня.
Здравствуйте, AndrewVK, Вы писали:
AVK>Это возможно если заранее известно какие конкретно объекты будут сериализоваться. А это приведет к XmlSerializer, для которого сериализуемые данные нужно заранее размечать.
Вовсе нет. Просто код должен будет генерироваться для каждого типа плюс для массива содержащего этот тип. Если будет какой-нить object, нужно будет всего-лишь прочитать его тип и найти соотвествующий сериализатор. В общем хеш-таблицы нам помогут.
Интересно насколько тормозит сериализация в Яве?
... << RSDN@Home 1.0 beta 6a >>
Есть логика намерений и логика обстоятельств, последняя всегда сильнее.
Re[2]: Сериализация в дотнете 2 - самопальный датасет
Здравствуйте, VladD2, Вы писали:
VD>Вовсе нет. Просто код должен будет генерироваться для каждого типа плюс для массива содержащего этот тип. Если будет какой-нить object, нужно будет всего-лишь прочитать его тип и найти соотвествующий сериализатор. В общем хеш-таблицы нам помогут.
То есть ты предлагаешь генерить сериализатор при первом обращении к конкретному классу?
VD>Интересно насколько тормозит сериализация в Яве?
VD>>Всю эту универсальность можно было бы реализовать и без таких страшных провалов производительности. Все что нужно было сделать — это генерировать код сериализации помпилятором,
AVK>Это возможно если заранее известно какие конкретно объекты будут сериализоваться. А это приведет к XmlSerializer, для которого сериализуемые данные нужно заранее размечать.
Вообще-то, можно было реализовывать ISerializable. Прямо там, где генерируется код для typed dataset, в Visual Studio. Можно было, но они на этом решили сэкономить. Ну и хрен с ним.
... << RSDN@Home 1.0 beta 6a >>
Re[12]: Сериализация в дотнете 2 - самопальный датасет
Здравствуйте, AndrewVK, Вы писали:
AVK>То есть ты предлагаешь генерить сериализатор при первом обращении к конкретному классу?
Оптимально было бы это делать еже при компиляции типа. Встретили атрибут Serialazable... и сгенерили код сериализации. Оптимальный и шустрый. При этом ожно было бы создавать два варианта сериализации толернтный к версии и скоростной. Первый использовать при сериализации в файл, а второй в ремоутинге и других местах где скорость самый важный показатель.
VD>>Интересно насколько тормозит сериализация в Яве?
AVK>Вот как то не интересовался.
А было бы интересно сравнить. За одно сравнить с реализацией на плюсах. В дотнете даже BinaryReaded/BinaryWriter написаны далеко не оптимально (вместо адресной арифметики используются операторы сдвига).
... << RSDN@Home 1.0 beta 6a >>
Есть логика намерений и логика обстоятельств, последняя всегда сильнее.
Re[13]: Сериализация в дотнете 2 - самопальный датасет
Здравствуйте, mihailik, Вы писали:
VD>> В дотнете даже BinaryReaded/BinaryWriter написаны далеко не оптимально (вместо адресной арифметики используются операторы сдвига).
M>Ух ты, интерестно! А как это?
У тебя что Анакрины нет? Или исходников Ротора?
Вот так:
public virtual void Write(int value)
{
this._buffer[0] = (byte) value;
this._buffer[1] = (byte) value >> 8;
this._buffer[2] = (byte) value >> 16;
this._buffer[3] = (byte) value >> 24;
this.OutStream.Write(this._buffer, 0, 4);
}
Вместо банального С++-ного:
public virtual void Write(int value)
{
memcpy(_buffer, &value, sizeof(value));
}
... << RSDN@Home 1.0 beta 6a >>
Есть логика намерений и логика обстоятельств, последняя всегда сильнее.