c# - Fast serialization and deserialization using dynamically emitted POCOs -
i serializing sql table rows binary format efficient storage. serialize/deserialize binary data list<object>
per row. i'm trying upgrade use pocos, dynamically generated (emitted) 1 field per column.
i've been searching online hours , have stumbled upon orms/frameworks ef, t4, expandoobject, of these either use dynamic object (properties can added/removed on fly) or generate poco before compiling. cannot use templating because schema of tables unknown @ compile time, , using dynamic objects overkill (and slow) since know exact set of properties , types. need generate 1 poco per table, fields corresponding columns, , data types set accordingly (int -> int, text -> string).
after generating poco, i'll proceed get/set properties using emitted cil, petapoco statically compiled pocos. i'm hoping of rigmarole faster using untyped lists, , give me high-fidelity pocos strongly-typed , can accelerated clr. correct assume this? , can start me off on generating pocos @ runtime? , using pocos faster or more memory-efficient using list<object>
? basically, worth trouble? know how accelerate getting/setting fields using emitted cil.
from comments , chat, seems key part of still creating dynamic type; ok, here's full example shows serializable (by common serializer) type. of course add more type - maybe indexers properties number or name, inotifypropertychanged
, etc.
also - critical point: must cache , re-use generated type
instances. not keep regenerating stuff... hemorrhage memory.
using newtonsoft.json; using protobuf; using system; using system.io; using system.reflection; using system.reflection.emit; using system.runtime.serialization; using system.runtime.serialization.formatters.binary; using system.text; using system.xml.serialization; public interface ibasicrecord { object this[int field] { get; set; } } class program { static void main() { object o = 1; int foo = (int)o; string[] names = { "id", "name", "size", "when" }; type[] types = { typeof(int), typeof(string), typeof(float), typeof(datetime?) }; var asm = appdomain.currentdomain.definedynamicassembly( new assemblyname("dynamicstuff"), assemblybuilderaccess.run); var module = asm.definedynamicmodule("dynamicstuff"); var tb = module.definetype("mytype", typeattributes.public | typeattributes.serializable); tb.setcustomattribute(new customattributebuilder( typeof(datacontractattribute).getconstructor(type.emptytypes), new object[0])); tb.addinterfaceimplementation(typeof(ibasicrecord)); fieldbuilder[] fields = new fieldbuilder[names.length]; var datamemberctor = typeof(datamemberattribute).getconstructor(type.emptytypes); var datamemberprops = new[] { typeof(datamemberattribute).getproperty("order") }; (int = 0; < fields.length; i++) { var field = fields[i] = tb.definefield("_" + names[i], types[i], fieldattributes.private); var prop = tb.defineproperty(names[i], propertyattributes.none, types[i], type.emptytypes); var getter = tb.definemethod("get_" + names[i], methodattributes.public | methodattributes.hidebysig, types[i], type.emptytypes); prop.setgetmethod(getter); var il = getter.getilgenerator(); il.emit(opcodes.ldarg_0); // il.emit(opcodes.ldfld, field); // .foo il.emit(opcodes.ret); // return var setter = tb.definemethod("set_" + names[i], methodattributes.public | methodattributes.hidebysig, typeof(void), new type[] { types[i] }); prop.setsetmethod(setter); il = setter.getilgenerator(); il.emit(opcodes.ldarg_0); // il.emit(opcodes.ldarg_1); // value il.emit(opcodes.stfld, field); // .foo = il.emit(opcodes.ret); prop.setcustomattribute(new customattributebuilder( datamemberctor, new object[0], datamemberprops, new object[1] { + 1 })); } foreach (var prop in typeof(ibasicrecord).getproperties()) { var accessor = prop.getgetmethod(); if (accessor != null) { var args = accessor.getparameters(); var argtypes = array.convertall(args, => a.parametertype); var method = tb.definemethod(accessor.name, accessor.attributes & ~methodattributes.abstract, accessor.callingconvention, accessor.returntype, argtypes); tb.definemethodoverride(method, accessor); var il = method.getilgenerator(); if (args.length == 1 && argtypes[0] == typeof(int)) { var branches = new label[fields.length]; (int = 0; < fields.length; i++) { branches[i] = il.definelabel(); } il.emit(opcodes.ldarg_1); // key il.emit(opcodes.switch, branches); // switch // default: il.throwexception(typeof(argumentoutofrangeexception)); (int = 0; < fields.length; i++) { il.marklabel(branches[i]); il.emit(opcodes.ldarg_0); // il.emit(opcodes.ldfld, fields[i]); // .foo if (types[i].isvaluetype) { il.emit(opcodes.box, types[i]); // (object) } il.emit(opcodes.ret); // return } } else { il.throwexception(typeof(notimplementedexception)); } } accessor = prop.getsetmethod(); if (accessor != null) { var args = accessor.getparameters(); var argtypes = array.convertall(args, => a.parametertype); var method = tb.definemethod(accessor.name, accessor.attributes & ~methodattributes.abstract, accessor.callingconvention, accessor.returntype, argtypes); tb.definemethodoverride(method, accessor); var il = method.getilgenerator(); if (args.length == 2 && argtypes[0] == typeof(int) && argtypes[1] == typeof(object)) { var branches = new label[fields.length]; (int = 0; < fields.length; i++) { branches[i] = il.definelabel(); } il.emit(opcodes.ldarg_1); // key il.emit(opcodes.switch, branches); // switch // default: il.throwexception(typeof(argumentoutofrangeexception)); (int = 0; < fields.length; i++) { il.marklabel(branches[i]); il.emit(opcodes.ldarg_0); // il.emit(opcodes.ldarg_2); // value il.emit(types[i].isvaluetype ? opcodes.unbox_any : opcodes.castclass, types[i]); // (sometype) il.emit(opcodes.stfld, fields[i]); // .foo = il.emit(opcodes.ret); // return } } else { il.throwexception(typeof(notimplementedexception)); } } } var type = tb.createtype(); var obj = activator.createinstance(type); // we'll use index (via known interface) set values ibasicrecord rec = (ibasicrecord)obj; rec[0] = 123; rec[1] = "abc"; rec[2] = 12f; rec[3] = datetime.now; (int = 0; < 4; i++) { console.writeline("{0} = {1}", i, rec[i]); } using (var ms = new memorystream()) { var ser = new xmlserializer(type); ser.serialize(ms, obj); console.writeline("xmlserializer: {0} bytes", ms.length); } using (var ms = new memorystream()) { using (var writer = new streamwriter(ms, encoding.utf8, 1024, true)) { var ser = new jsonserializer(); ser.serialize(writer, obj); } console.writeline("json.net: {0} bytes", ms.length); } using (var ms = new memorystream()) { var ser = new datacontractserializer(type); ser.writeobject(ms, obj); console.writeline("datacontractserializer: {0} bytes", ms.length); } using (var ms = new memorystream()) { serializer.nongeneric.serialize(ms, obj); console.writeline("protobuf-net: {0} bytes", ms.length); } using (var ms = new memorystream()) { // note: never unless have custom binder; // assembly not deserialize in next appdomain (i.e. // next time load app, won't able load) // - shown illustration var bf = new binaryformatter(); bf.serialize(ms, obj); console.writeline("binaryformatter: {0} bytes", ms.length); } } }
output:
xmlserializer: 246 bytes json.net: 81 bytes datacontractserializer: 207 bytes protobuf-net: 25 bytes binaryformatter: 182 bytes
Comments
Post a Comment