前(tu)言(cao)之前一篇虽然也强调了,可是回复中还是有人来挑刺,并且还有人支持?!
#5楼2013-08-26 21:39
楼主看下StringBuilder的makeroom方法吧。微软官方的,有些东西不是人家做不到,而是人家考虑的更多。
所以我不得不再次强调一下,系统是考虑的很多,但是我不需要这么多功能可以吗?我只希望他快一点,(对我来说)更好用一点.
就好比,如果你只想拧螺丝,你会选择瑞士军刀,还是选择螺丝刀?! 你见过多少维修师傅带着一把瑞士军刀去你家修东西的?
当维修师傅拿出一把螺丝刀,并且觉得非常好用的时候,难道你对他说,瑞士军刀也能拧螺丝,为什么你带螺丝刀,你真是弱爆了!!
我只是想我的字符串拼接快一点,至于其他功能,暂时我不需要!谢谢
对上一篇的简述上一篇中的主要思路就是,参照StringBuilder中Append的重载,自己写一个新的对象,保证每一个Append方法都比StringBuilder更快就好了(实际上有部分是达不到这个要求的,但是能保证大部分快,一部分相同,1,2慢一些,整体上来说就能达到我的要求了)
并且在上一篇中有一个缓冲区的概念,是一个Char数组,当字符串超过缓冲区大小之后,将重新申请新的char数组,比原先的大一倍,并将原先的字符串赋值到新数组(这里成了新的一个瓶颈点)
上一篇(精简版StringBuilder,提速字符串拼接)链接在此,没看过的可以先移步去看一下
应用很多人说,节省这么点时间有什么用,我觉得这个观点本身就是错误的,古语有云:不积跬步无以至千里,不积小流无以成江海;
好吧又有很多人会说在放屁了,下面说个实例;
在我的Json组件中,下面有一组ObjectToJsonString的时间测试,(这次我没有拿千万次,百万次来测试)
//======= StringBuilder ================================================================================ 一万次 单个实体 ===运行时间 CPU时钟周期 垃圾回收( 1代 2代 3代 )1,367ms2,708,098,82035800======= 一千次 实体集合 ===运行时间 CPU时钟周期 垃圾回收( 1代 2代 3代 )1,256ms2,479,181,11712900//======= 优化前 ======================================================================================= 一万次 单个实体 ===运行时间 CPU时钟周期 垃圾回收( 1代 2代 3代 )1,089ms2,170,463,77835000======= 一千次 实体集合 ===运行时间 CPU时钟周期 垃圾回收( 1代 2代 3代 )
802ms1,565,483,21814000
其中 单个实体 是 一个拥有15个左右属性的实体 . 实体集合是 200个实体对象 每个实体 5个属性,从上面可以看出一些性能的差异,至于到底值不值得,就仁者见仁了
所以这个类设计之后被我应用到了很多地方,几乎所有的StringBuilder都可以用这个代替(我是说几乎,不是全部)
优化扩容操作刚才中有提到缓冲区在扩容的时候是一个性能瓶颈,其中有2个操作,1申请新的数组,2将原有数据拷贝到新数组中,这个拷贝的操作将是十分耗时的;所以我第一想到的就是不拷贝,仅申请一个新数组,不够的话再申请一个,直到最后的ToString 再把所有char数组合并 String.Concat
最终定下一个方案,仅在初始化时开辟一个8长度的String数组buffer作为二级缓冲,当一级缓冲char数组满了之后,把二级缓冲 string.Concat(buffer) 组成一个新的字符串,放入buffer[0],其他清空,这样就可以规避一些拷贝数据带来的性能损失
内存指针操作数组字符串操作,其实就是在操作Char数组, 说到数组的操作很多人会想到指针,没错数组操作,使用指针定位会比索引取值要快很多,但是.NET中指针操作被成为是"不安全代码",因为微软告诉我们
指向可移动托管变量的指针的作用很小,因为垃圾回收可能不可预知地重定位变量。
这就意味着一旦发生垃圾回收,你的指针指向的对象就有可能已经不是原来的对象了
比如:当我正在操作char数组的时候 我的指针指向 char数组的第10位 char* p = (char*)char[]; p[10]但是由于垃圾回收,当我得到p的时候 char[]被重新安排在内存的另外一个地方了,但是p并没有改变,那么p[10]所获取或设置的值,就已经不是原来char数组的值了
当然微软也有办法解决,其中fixed是一种方式:
char[] buffer =newchar[100];fixed(char* p =buffer)
{
p[10] ='a';//....}
这样确实可以固定这个对象,保证不因为垃圾回收而改变内存位置,但是这是一个方法级的语块;这就意味着你无法固定一个类的字段,想想我们有那么多的Append方法,不可能每个方法都固定一次(fixed也是有性能损失的)
另外一个方法就是申请非托管内存,所谓非托管,也就是说垃圾回收将不处理这块内存, 所以这也意味着,你可以不用担心GC来捣乱,但是需要自己去释放它,不过是小问题;
非托管内存申请非托管内存很简单,参考MSDN
//生成字符串缓冲区指针 ,一个char是2个字节,所以要乘以2IntPtr_currIntPtr = System.Runtime.InteropServices.Marshal.AllocHGlobal(size *2);char* _current = (char*)_currIntPtr.ToPointer();
当然用完要记得释放,这个就实现IDisposable
还有一点需要注意的就是 这个方法是不能重复执行的,会报错,所以需要判断一下
PRivateint_disposeMark =0;publicvoidDispose()
{varmark = System.Threading.Interlocked.Exchange(ref_disposeMark,1);if(mark ==1)
{return;
}
System.Runtime.InteropServices.Marshal.FreeHGlobal(_currIntPtr);GC.SuppressFinalize(this);
}
代码优化把以上2点结合起来,就有了以下代码
///<summary>指针句柄///</summary>privatereadonlyIntPtr _currIntPtr;///<summary>一级缓冲指针///</summary>private char*_current;///<summary>二级缓冲///</summary>readonlystring[] _buffer =newstring[8];///<summary>备用二级缓冲索引///</summary>int_bufferIndex;///<summary>总字符数///</summary>int_length;///<summary>结束位,一级缓冲长度减一///</summary>int_endPosition;///<summary>一级缓冲当前位置///</summary>int_position;
///<summary>初始化对象,并指定缓冲区大小///</summary>///<param name="size"></param>publicQuickStringWriter(ushortsize)
{//确定最后一个字符的位置 长度-1_endPosition = size -1;//生成字符串缓冲指针 ,一个char是2个字节,所以要乘以2_currIntPtr = System.Runtime.InteropServices.Marshal.AllocHGlobal(size *2);
_current= (char*)_currIntPtr.ToPointer();
}
构造函数
///<summary>获取当前实例中的字符串总长度///</summary>publicintLength
{get{return_length +_position;
}
}
Length
///<summary>尝试在一级缓冲区写入一个字符///<para>如果一级缓冲区已满,将会自动调用Flush方法转移一级缓冲区中的内容</para>///</summary>privatevoidTryWrite()
{if(_position >_endPosition)
{
Flush();
}elseif(_endPosition ==int.MaxValue)
{thrownewException("指针尚未准备就绪!");
}
}///<summary>尝试在一级缓冲区写入指定数量的字符///</summary>///<para>如果尝试写入的字符数大于一级缓冲区的大小,返回false</para>///<para>如果尝试写入的字符数超出一级缓冲区剩余容量,自动调用Flush方法</para>///<param name="count">尝试写入的字符数</param>///<returns></returns>privateboolTryWrite(intcount)
{if(count >=_endPosition)
{returnfalse;
}varpre = _position +count;if(pre >=_endPosition)
{
Flush();
}elseif(_endPosition ==int.MaxValue)
{thrownewException("指针尚未准备就绪!");
}returntrue;
}
TryWrite
///<summary>清理当前实例的一级缓冲区的内容,使所有缓冲数据写入二级缓冲区。///</summary>publicvoidFlush()
{if(_position >0)
{
_length+=_position;if(_bufferIndex ==8)
{
_buffer[0] =string.Concat(_buffer);
_buffer[1] =newstring(_current,0, _position);
_buffer[2] =_buffer[3] =_buffer[4] =_buffer[5] =_buffer[6] =_buffer[7] =null;
_bufferIndex=2;
}else{
_buffer[_bufferIndex++] =newstring(_current,0, _position);
}
_position=0;
}
}
Flush
///<summary>返回当前实例中的字符串///</summary>publicoverridestringToString()
{if(_bufferIndex ==0)
{returnnewstring(_current,0, _position);
}if(_bufferIndex <=3)
{returnstring.Concat(_buffer[0], _buffer[1], _buffer[2],newstring(_current,0, _position));
}returnstring.Concat(_buffer[0], _buffer[1], _buffer[2], _buffer[3],
_buffer[4], _buffer[5], _buffer[6], _buffer[7],newstring(_current,0, _position));
}
ToString
其他一些优化privatestaticcharHexToChar(inta)
{
a&=15;returna >9? (char)(a -10+0x61) : (char)(a +0x30);
}///<summary>将 Guid 对象转换为字符串追加到当前实例。///</summary>publicQuickStringWriter Append(Guid val,charformat ='d')
{intflag;switch(format)
{case'd':case'D':
flag=1;
TryWrite(36);
break;case'N':case'n':
flag=0;
TryWrite(32);
break;case'P':case'p':
TryWrite(38);
_current[_position++] ='(';
flag=')';
break;case'B':case'b':
TryWrite(38);
_current[_position++] ='{';
flag='}';
break;default:
Append(val.ToString(format.ToString()));returnthis;
}varbs =val.ToByteArray();
_current[_position++] = HexToChar(bs[3] >>4);
_current[_position++] = HexToChar(bs[3]);
_current[_position++] = HexToChar(bs[2] >>4);
_current[_position++] = HexToChar(bs[2]);
_current[_position++] = HexToChar(bs[1] >>4);
_current[_position++] = HexToChar(bs[1]);
_current[_position++] = HexToChar(bs[0] >>4);
_current[_position++] = HexToChar(bs[0]);if(flag >0)
{
_current[_position++] ='-';
}
_current[_position++] = HexToChar(bs[5] >>4);
_current[_position++] = HexToChar(bs[5]);
_current[_position++] = HexToChar(bs[4] >>4);
_current[_position++] = HexToChar(bs[4]);if(flag >0)
{
_current[_position++] ='-';
}
_current[_position++] = HexToChar(bs[7] >>4);
_current[_position++] = HexToChar(bs[7]);
_current[_position++] = HexToChar(bs[6] >>4);
_current[_position++] = HexToChar(bs[6]);if(flag >0)
{
_current[_position++] ='-';
}
_current[_position++] = HexToChar(bs[8] >>4);
_current[_position++] = HexToChar(bs[8]);
_current[_position++] = HexToChar(bs[9] >>4);
_current[_position++] = HexToChar(bs[9]);if(flag >0)
{
_current[_position++] ='-';
}
_current[_position++] = HexToChar(bs[10] >>4);
_current[_position++] = HexToChar(bs[10]);
_current[_position++] = HexToChar(bs[11] >>4);
_current[_position++] = HexToChar(bs[11]);
_current[_position++] = HexToChar(bs[12] >>4);
_current[_position++] = HexToChar(bs[12]);
_current[_position++] = HexToChar(bs[13] >>4);
_current[_position++] = HexToChar(bs[13]);
_current[_position++] = HexToChar(bs[14] >>4);
_current[_position++] = HexToChar(bs[14]);
_current[_position++] = HexToChar(bs[15] >>4);
_current[_position++] = HexToChar(bs[15]);if(flag >1)
{
_current[_position++] = (char)flag;
}returnthis;
}
Append(Guid val)
///<summary>将 Int64 对象转换为字符串追加到当前实例。///</summary>publicQuickStringWriter Append(Int64 val)
{if(val ==0)
{
TryWrite();
_current[_position++] ='0';returnthis;
}varzero = (long)'0';varpos =19;varf = val <0;if(f)
{
_number[pos]= (char)(~(val %10L) + (long)'1');if(val < -10)
{
val= val / -10;
_number[--pos] = (char)(val %10L+zero);
}
}else{
_number[pos]= (char)(val %10L+zero);
}while((val = val /10L) !=0L)
{
_number[--pos] = (char)(val %10L+zero);
}if(f)
{
_number[--pos] ='-';
}varlength =20-pos;
Append(_number, pos, length);returnthis;
}
Append(Number val)
///<summary>将内存中的字符串追加到当前实例。///</summary>///<param name="point">内存指针</param>///<param name="offset">指针偏移量</param>///<param name="length">字符长度</param>///<returns></returns>publicQuickStringWriter Append(char* point,intoffset,intlength)
{if(length >0)
{if(TryWrite(length))
{char* c = point +offset;if((length &1) !=0)
{
_current[_position++] = c[0];
c++;
length--;
}int* p1 = (int*)&_current[_position];int* p2 = ((int*)c);
_position+=length;while(length >=8)
{
(*p1++) = *(p2++);
(*p1++) = *(p2++);
(*p1++) = *(p2++);
(*p1++) = *(p2++);
length-=8;
}if((length &4) !=0)
{
(*p1++) = *(p2++);
(*p1++) = *(p2++);
}if((length &2) !=0)
{
(*p1) = *(p2);
}
}else{
Flush();
_buffer[_bufferIndex++] =newstring(point, offset, length);
_length+=length;
}
}returnthis;
}
Append(char* point, int offset, int length)
优化后的性能对比依然使用 ObjectToJsonString 来做对比
//======= StringBuilder ================================================================================ 一万次 单个实体 ===运行时间 CPU时钟周期 垃圾回收( 1代 2代 3代 )1,367ms2,708,098,82035800======= 一千次 实体集合 ===运行时间 CPU时钟周期 垃圾回收( 1代 2代 3代 )1,256ms2,479,181,11712900//======= 优化前 ======================================================================================= 一万次 单个实体 ===运行时间 CPU时钟周期 垃圾回收( 1代 2代 3代 )1,089ms2,170,463,77835000======= 一千次 实体集合 ===运行时间 CPU时钟周期 垃圾回收( 1代 2代 3代 )
802ms1,565,483,21814000//======= 优化后 ======================================================================================= 一万次 单个实体 ===运行时间 CPU时钟周期 垃圾回收( 1代 2代 3代 )
688ms1,353,917,1475200======= 一千次 实体集合 ===运行时间 CPU时钟周期 垃圾回收( 1代 2代 3代 )
663ms1,322,653,9327800
这样对我来说就很满意了,至少比StringBuilder快了50%!
代码托管平台https://code.csdn.net/snippets/436915