FluorineFx的AMFWriter实现的也太粗心了...
前两天打算给组件做个AMF3适配器用于组件和Flash进行通讯交互,经了解后发现FluorineFx在.net下对AMF3支持比较完善的一个项目,于就下载下来做一下集成.出于好奇于是看了一下相关代码,由于只需要用到序列化问题所以只关注了一下AMFWriter;从实现代码来看AMFWriter基本没有考虑高并发下的GC压力,数据写入过程基本都是通过new byte[]复制的方式.为了进行步了解于是对FluorineFx序列化对象做了个内存分析.
出来的结果让我摸不着头脑.
损耗排在前面的竟然一些意想不到的对象...于是详细跟踪进行发现这两个对象的开销都来源于AMFWriter.GetMember方法;细看这个方法我当场晕倒:
internal object GetMember(object instance, ClassMember member) { if (instance is ASObject) { ASObject aso = instance as ASObject; if (aso.ContainsKey(member.Name)) return aso[member.Name]; } Type type = instance.GetType(); if (member.MemberType == MemberTypes.Property) { PropertyInfo property= type.GetProperty(member.Name, member.BindingFlags); return property.GetValue(instance, null); } if (member.MemberType == MemberTypes.Field) { FieldInfo field = type.GetField(member.Name, member.BindingFlags); return field.GetValue(instance); } string msg = __Res.GetString(__Res.Reflection_MemberNotFound, string.Format("{0}.{1}", type.FullName, member.Name)); throw new FluorineException(msg); }
获取成员值的方法竟然每次都会GetProperty或GetField,所以在测试内存结果为什么PropertyInfo[]占第一位置的原因.其实PropertyInfo和FieldInfo都是线程安全根本没有必要做.于是做了简单的调整
internal object GetMember(object instance, ClassMember member) { if (instance is ASObject) { ASObject aso = instance as ASObject; if (aso.ContainsKey(member.Name)) return aso[member.Name]; } Type type = instance.GetType(); if (member.MemberType == MemberTypes.Property) { if(member.Property ==null) member.Property= type.GetProperty(member.Name, member.BindingFlags); return member.Property.GetValue(instance, null); } if (member.MemberType == MemberTypes.Field) { if(member.Field ==null) member.Field = type.GetField(member.Name, member.BindingFlags); return member.Field.GetValue(instance); } string msg = __Res.GetString(__Res.Reflection_MemberNotFound, string.Format("{0}.{1}", type.FullName, member.Name)); throw new FluorineException(msg); }
同样在查看代码中发现WriteAMF3UTF也很有问题,于是也调整了一下
static void _WriteAMF3UTF(string value, AMFWriter writer) { lock (_LockWriterString) { if (value == string.Empty) { writer.WriteAMF3IntegerData(1); } else { if (!writer._stringReferences.ContainsKey(value)) { writer._stringReferences.Add(value, writer._stringReferences.Count); int byteCount = Encoding.UTF8.GetBytes(value, 0, value.Length, encodingdata, 0); int handle = byteCount; handle = handle << 1; handle = handle | 1; writer.WriteAMF3IntegerData(handle); if (byteCount > 0) writer.Write(encodingdata,0,byteCount); } else { int handle = (int)writer._stringReferences[value]; handle = handle << 1; writer.WriteAMF3IntegerData(handle); } } } } /// <summary> /// Writes a UTF-8 string to the current position in the AMF stream. /// </summary> /// <param name="value">The UTF-8 string.</param> /// <remarks>Standard or long string header is not written.</remarks> public void WriteAMF3UTF(string value) { _WriteAMF3UTF(value, this); //if( value == string.Empty ) //{ // WriteAMF3IntegerData(1); //} //else //{ // if (!_stringReferences.ContainsKey(value)) // { // _stringReferences.Add(value, _stringReferences.Count); // UTF8Encoding utf8Encoding = new UTF8Encoding(); // int byteCount = utf8Encoding.GetByteCount(value); // int handle = byteCount; // handle = handle << 1; // handle = handle | 1; // WriteAMF3IntegerData(handle); // byte[] buffer = utf8Encoding.GetBytes(value); // if (buffer.Length > 0) // Write(buffer); // } // else // { // int handle = (int)_stringReferences[value]; // handle = handle << 1; // WriteAMF3IntegerData(handle); // } //} }
从整个AMFWriter的实现代码来说,发现作者内存开销方面没有任何意识,那也难怪MS在推出.NET的时候就告诉我们不需要管内存,我们会把它搞定...
调整前后序列化10000个order的效率对比.
总的来说AMFWriter的每个写入数据方法都有可优化的余地,看FluorineFx持官网似乎有商业支持,但代码修改日期已经是很久以前,竟然没意识到这一点感觉有点惊讶.
访问Beetlex的Github