优化字符串拼接之二:非托管内存应用

前(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,367ms     2,708,098,820            358      0        0
======= 一千次 实体集合 ===
 运行时间    CPU时钟周期    垃圾回收( 1代      2代      3代 )
 1,256ms     2,479,181,117            129      0        0

//======= 优化前  ================================================================================
======= 一万次 单个实体 ===
 运行时间    CPU时钟周期    垃圾回收( 1代      2代      3代 )
 1,089ms     2,170,463,778            350      0        0
======= 一千次 实体集合 ===
 运行时间    CPU时钟周期    垃圾回收( 1代      2代      3代 )
 802ms       1,565,483,218            140      0        0

  其中 单个实体 是 一个拥有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 = new char[100];
fixed (char* p = buffer)
{
    p[10] = ‘a‘;
    //....
}   

  这样确实可以固定这个对象,保证不因为垃圾回收而改变内存位置,但是这是一个方法级的语块;这就意味着你无法固定一个类的字段,想想我们有那么多的Append方法,不可能每个方法都固定一次(fixed也是有性能损失的)

  另外一个方法就是申请非托管内存,所谓非托管,也就是说垃圾回收将不处理这块内存, 所以这也意味着,你可以不用担心GC来捣乱,但是需要自己去释放它,不过是小问题;

非托管内存

  申请非托管内存很简单,参考MSDN

//生成字符串缓冲区指针 ,一个char是2个字节,所以要乘以2
IntPtr _currIntPtr = System.Runtime.InteropServices.Marshal.AllocHGlobal(size * 2);
char* _current = (char*)_currIntPtr.ToPointer();

  当然用完要记得释放,这个就实现IDisposable

  还有一点需要注意的就是 这个方法是不能重复执行的,会报错,所以需要判断一下

private int _disposeMark = 0;

public void Dispose()
{
    var mark = System.Threading.Interlocked.Exchange(ref _disposeMark, 1);
    if (mark == 1)
    {
        return;
    }
    System.Runtime.InteropServices.Marshal.FreeHGlobal(_currIntPtr);
    GC.SuppressFinalize(this);
}

代码优化

  把以上2点结合起来,就有了以下代码

        /// <summary> 指针句柄
        /// </summary>
        private readonly IntPtr _currIntPtr;
        /// <summary> 一级缓冲指针
        /// </summary>
        private char* _current;
        /// <summary> 二级缓冲
        /// </summary>
        readonly string[] _buffer = new string[8];
        /// <summary> 备用二级缓冲索引
        /// </summary>
        int _bufferIndex;
        /// <summary> 总字符数
        /// </summary>
        int _length;
        /// <summary> 结束位,一级缓冲长度减一
        /// </summary>
        int _endPosition;
        /// <summary> 一级缓冲当前位置
        /// </summary>
        int _position;

        /// <summary> 初始化对象,并指定缓冲区大小
        /// </summary>
        /// <param name="size"></param>
        public QuickStringWriter(ushort size)
        {
            //确定最后一个字符的位置  长度-1
            _endPosition = size - 1;
            //生成字符串缓冲指针 ,一个char是2个字节,所以要乘以2
            _currIntPtr = System.Runtime.InteropServices.Marshal.AllocHGlobal(size * 2);
            _current = (char*)_currIntPtr.ToPointer();
        }

构造函数

        /// <summary> 获取当前实例中的字符串总长度
        /// </summary>
        public int Length
        {
            get
            {
                return _length + _position;
            }
        }

Length

        /// <summary> 尝试在一级缓冲区写入一个字符
        /// <para>如果一级缓冲区已满,将会自动调用Flush方法转移一级缓冲区中的内容</para>
        /// </summary>
        private void TryWrite()
        {
            if (_position > _endPosition)
            {
                Flush();
            }
            else if (_endPosition == int.MaxValue)
            {
                throw new Exception("指针尚未准备就绪!");
            }
        }
        /// <summary> 尝试在一级缓冲区写入指定数量的字符
        /// </summary>
        /// <para>如果尝试写入的字符数大于一级缓冲区的大小,返回false</para>
        /// <para>如果尝试写入的字符数超出一级缓冲区剩余容量,自动调用Flush方法</para>
        /// <param name="count">尝试写入的字符数</param>
        /// <returns></returns>
        private bool TryWrite(int count)
        {
            if (count >= _endPosition)
            {
                return false;
            }
            var pre = _position + count;
            if (pre >= _endPosition)
            {
                Flush();
            }
            else if (_endPosition == int.MaxValue)
            {
                throw new Exception("指针尚未准备就绪!");
            }
            return true;
        }

TryWrite

        /// <summary> 清理当前实例的一级缓冲区的内容,使所有缓冲数据写入二级缓冲区。
        /// </summary>
        public void Flush()
        {
            if (_position > 0)
            {
                _length += _position;
                if (_bufferIndex == 8)
                {
                    _buffer[0] = string.Concat(_buffer);
                    _buffer[1] = new string(_current, 0, _position);
                    _buffer[2] =
                    _buffer[3] =
                    _buffer[4] =
                    _buffer[5] =
                    _buffer[6] =
                    _buffer[7] = null;
                    _bufferIndex = 2;
                }
                else
                {
                    _buffer[_bufferIndex++] = new string(_current, 0, _position);
                }
                _position = 0;
            }
        }

Flush

        /// <summary> 返回当前实例中的字符串
        /// </summary>
        public override string ToString()
        {
            if (_bufferIndex == 0)
            {
                return new string(_current, 0, _position);
            }
            if (_bufferIndex <= 3)
            {
                return string.Concat(_buffer[0], _buffer[1], _buffer[2], new string(_current, 0, _position));
            }
            return string.Concat(_buffer[0], _buffer[1], _buffer[2], _buffer[3],
                                 _buffer[4], _buffer[5], _buffer[6], _buffer[7],
                                 new string(_current, 0, _position));
        }

ToString

其他一些优化

        private static char HexToChar(int a)
        {
            a &= 15;
            return a > 9 ? (char)(a - 10 + 0x61) : (char)(a + 0x30);
        }

        /// <summary> 将 Guid 对象转换为字符串追加到当前实例。
        /// </summary>
        public QuickStringWriter Append(Guid val, char format = ‘d‘)
        {
            int flag;
            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()));
                    return this;
            }
            var bs = 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;
            }
            return this;
        }

Append(Guid val)

        /// <summary> 将 Int64 对象转换为字符串追加到当前实例。
        /// </summary>
        public QuickStringWriter Append(Int64 val)
        {
            if (val == 0)
            {
                TryWrite();
                _current[_position++] = ‘0‘;
                return this;
            }

            var zero = (long)‘0‘;

            var pos = 19;
            var f = 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] = ‘-‘;
            }
            var length = 20 - pos;
            Append(_number, pos, length);
            return this;
        }

Append(Number val)

/// <summary> 将内存中的字符串追加到当前实例。
        /// </summary>
        /// <param name="point">内存指针</param>
        /// <param name="offset">指针偏移量</param>
        /// <param name="length">字符长度</param>
        /// <returns></returns>
        public QuickStringWriter Append(char* point, int offset, int length)
        {
            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++] = new string(point, offset, length);
                    _length += length;
                }
            }

            return this;
        }

Append(char* point, int offset, int length)

优化后的性能对比

  依然使用 ObjectToJsonString 来做对比

//======= StringBuilder  =========================================================================
======= 一万次 单个实体 ===
 运行时间    CPU时钟周期    垃圾回收( 1代      2代      3代 )
 1,367ms     2,708,098,820            358      0        0
======= 一千次 实体集合 ===
 运行时间    CPU时钟周期    垃圾回收( 1代      2代      3代 )
 1,256ms     2,479,181,117            129      0        0

//======= 优化前  ================================================================================
======= 一万次 单个实体 ===
 运行时间    CPU时钟周期    垃圾回收( 1代      2代      3代 )
 1,089ms     2,170,463,778            350      0        0
======= 一千次 实体集合 ===
 运行时间    CPU时钟周期    垃圾回收( 1代      2代      3代 )
 802ms       1,565,483,218            140      0        0

//======= 优化后  ================================================================================
======= 一万次 单个实体 ===
 运行时间    CPU时钟周期    垃圾回收( 1代      2代      3代 )
 688ms       1,353,917,147            52       0        0
======= 一千次 实体集合 ===
 运行时间    CPU时钟周期    垃圾回收( 1代      2代      3代 )
 663ms       1,322,653,932            78       0        0

  这样对我来说就很满意了,至少比StringBuilder快了50%!

代码托管平台

https://code.csdn.net/snippets/436915

优化字符串拼接之二:非托管内存应用

时间: 2024-10-13 03:33:17

优化字符串拼接之二:非托管内存应用的相关文章

如何让IntPtr指向一块内存,以及托管内存与非托管内存的相互转化

IntPtr idp= IntPtr.Zero; StringBuilder idata = new StringBuilder("000000"); string idata ="000000"; 我这样建立的2个idata字符串,如何让idp指向他 我指向他的目的是为了传递给dll的某个函数,他需要传指针 还有我定义了一个结构如何向dll的函数传递其指针? 所有分了,解决一起结了 用GCHandle.Alloc(object obj)方法来给string分配一个

VB.NET 内存指针和非托管内存的应用

介绍 Visual Basic 从来不像在C或C++里一样灵活的操纵指针和原始内存.然而利用.NET框架中的structures 和 classes,可以做许多类似的事情.它们包括 IntPtr,   Marshal 以及 GCHandle. 这些structures(结构) 和classes(类) 允许你在托管和非托管环境中进行交互.本文中将向您展示如何使用这些structures 和 classes 去完成指针和内存的操作. 关于 IntPtr 结构 IntPtr  结构的行为像一个整型指针

C# 托管内存与非托管内存之间的转换(结合Unity3d的实际开发)

1.c#的托管代码和非托管代码 c#有自己的内存回收机制,所以在c#中我们可以只new,不用关心怎样delete,c#使用gc来清理内存,这部分内存就是managed memory,大部分时候我们工作于c#环境中,都是在使用托管内存,然而c#毕竟运行在c++之上,有的时候,(比如可能我们需要引入一些第三方的c++或native代码的库,在Unity3d开发中很常见)我们需要直接在c#中操纵非托管的代码,这些non-managed memory我们就需要自己去处理他们的申请和释放了, c# 中提供

利用C#Marshal类实现托管和非托管的相互转换

Marshal 类 命名空间:System.Runtime.InteropServices 提供了一个方法集,这些方法用于分配非托管内存.复制非托管内存块.将托管类型转换为非托管类型,此外还提供了在与非托管代码交互时使用的其他杂项方法. Marshal 类中定义的 static 方法对于处理非托管代码至关重要.此类中定义的大多数方法通常由需要在托管和非托管编程模型之间提供桥梁的开发人员使用.例如,StringToHGlobalAnsi 方法将 ANSI 字符从指定的字符串(在托管堆中)复制到非托

在VS2010上使用C#调用非托管C++生成的DLL文件

背景 在项目过程中,有时候你需要调用非C#编写的DLL文件,尤其在使用一些第三方通讯组件的时候,通过C#来开发应用软件时,就需要利用DllImport特性进行方法调用.本篇文章将引导你快速理解这个调用的过程. 步骤 1. 创建一个CSharpInvokeCPP的解决方案: 2. 创建一个C++的动态库项目: 3. 在应用程序设置中,选择“DLL”,其他按照默认选项: 最后点击完成,得到如图所示项目: 我们可以看到这里有一些文件,其中dllmain.cpp作为定义DLL应用程序的入口点,它的作用跟

CSharpGL(36)通用的非托管数组排序方法

如果OpenGL要渲染半透明物体,一个方法是根据顶点到窗口的距离排序,按照从远到近的顺序依次渲染.所以本篇介绍对 UnmanagedArray<T> 进行排序的几种方法. +BIT祝威+悄悄在此留下版了个权的信息说: UnmanagedArray<T> 首先重新介绍一下非托管数组这个东西.一个 UnmanagedArray<float> 与一个 float[] 是一样的用处,只不过 UnmanagedArray<float> 是用 Marshal.Alloc

C#的三大难点之二:托管与非托管

相关文章: C#的三大难点之前传:什么时候应该使用C#??C#的三大难点之一:byte与char,string与StringBuilderC#的三大难点之二:托管与非托管C#的三大难点之三:消息与事件 托管代码与非托管代码 众所周知,我们正常编程所用的高级语言,是无法被计算机识别的.需要先将高级语言翻译为机器语言,才能被机器理解和运行.在标准C/C++中,编译过程是这样的:源代码首先经过预处理器,对头文件以及宏进行解析,然后经过编译器,生成汇编代码,接着,经过汇编,生成机器指令,最后将所有文件连

字符串拼接性能优化

var htmlString = '<div class="container">' + '<ul id="news-list">', for (var i = 0; i < NEWS.length; i++) { htmlString += '<li><a href="'+NEWS[i].LINK +'">' + NEWS[i].TITLE + '</a></li>'

.NET的堆和栈04,对托管和非托管资源的垃圾回收以及内存分配

在" .NET的堆和栈01,基本概念.值类型内存分配"中,了解了"堆"和"栈"的基本概念,以及值类型的内存分配.我们知道:当执行一个方法的时候,值类型实例会在"栈"上分配内存,而引用类型实例会在"堆"上分配内存,当方法执行完毕,"栈"上的实例由操作系统自动释放,"堆"上的实例由.NET Framework的GC进行回收. 在" .NET的堆和栈02,值类型和