国产探花免费观看_亚洲丰满少妇自慰呻吟_97日韩有码在线_资源在线日韩欧美_一区二区精品毛片,辰东完美世界有声小说,欢乐颂第一季,yy玄幻小说排行榜完本

首頁 > 學院 > 開發設計 > 正文

優化字符串拼接之二:非托管內存應用

2019-11-14 16:32:31
字體:
來源:轉載
供稿:網友

前(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個字節,所以要乘以2IntPtr _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


發表評論 共有條評論
用戶名: 密碼:
驗證碼: 匿名發表
主站蜘蛛池模板: 彭州市| 驻马店市| 新宁县| 绥芬河市| 都兰县| 枝江市| 阳山县| 遵义县| 岱山县| 丰原市| 富源县| 洪雅县| 黄浦区| 澄迈县| 九寨沟县| 遵化市| 兴业县| 洛川县| 文山县| 田阳县| 乐陵市| 铜川市| 益阳市| 十堰市| 奉节县| 垣曲县| 扎鲁特旗| 赣州市| 庄浪县| 文化| 澎湖县| 溧阳市| 北碚区| 鹤山市| 那曲县| 台南市| 大渡口区| 松阳县| 台北市| 林口县| 神池县|