先看下Reflector.exe反匯編.net framework 2.0中Mscorlid.dll,得到String.ToCharArray ()源碼:
//.net framework 2.0
public unsafe char[] ToCharArray()
{
int num1 = this.Length;
char[] chArray1 = new char[num1];
if (num1 > 0)
{
fixed (char* chRef1 = &this.m_firstChar)
{
//使用fixed,防止gc執行垃圾回收后移動堆上的對象而造成無效指針
fixed (char* chRef2 = chArray1)
{
string.wstrcpyPtrAligned(chRef2, chRef1, num1);
chRef1 = null;
}
}
}
return chArray1;
}
private static unsafe void wstrcpyPtrAligned(char* dmem, char* smem, int charCount)
{
while (charCount >= 8)//四重展開
{
*((int*) dmem) = *((uint*) smem); //拷貝32bit
*((int*) (dmem + 2)) = *((uint*) (smem + 2));
*((int*) (dmem + 4)) = *((uint*) (smem + 4));
*((int*) (dmem + 6)) = *((uint*) (smem + 6));
dmem += 8;
smem += 8;
charCount -= 8;
}
if ((charCount & 4) != 0)
{
*((int*) dmem) = *((uint*) smem);
*((int*) (dmem + 2)) = *((uint*) (smem + 2));
dmem += 4;
smem += 4;
}
if ((charCount & 2) != 0)
{
*((int*) dmem) = *((uint*) smem);
dmem += 2;
smem += 2;
}
if ((charCount & 1) != 0)
{
dmem[0] = smem[0];
}
} ToCharArray中使用了fixed來防止gc執行垃圾回收后移動托管堆上的對象。托管堆的工作方式類似于棧,在某種程度上,連續的對象會在內存緊挨的位置放置,這樣很容易使用指向下一個空閑存儲單元的堆指針,來確定下一個對象的位置。在gc回收不再引用的對象所占用的內存后,現存對象在內存的位置可能不再連續,此時gc會把所有對象移動到托管堆的端部,在次形成一個連續的塊。當然,在移動對象時,這些對象的所有引用都需要用正確的新地址來更新。gc的這個壓縮操作時托管的堆與非托管堆的區別所在。使用托管堆,就只需要讀取堆指針的值即可,而不是搜索鏈接地址列表,來查找一個地方放置新對象。因此在.net下實例化對象要快得多。
使用了fixed后,在fixed塊語句內指針所指向的對象將不會在托管堆上移動,從而保證wstrcpyPtrAligned方法中的指正都指向正確的內存地址。
wstrcpyPtrAligned方法中也用幾點內存優化技巧(《代碼優化規則》摘錄了更多的技巧):
1. *((int*) targetAddress) = *((uint*) originalAddress);
將執行char的指針轉換成指向int/uint的指針,從而實現每次讀取32bit的數據。
2. while (charCount >= 8){}
按四重展開循環。流水線型CPU對分支語句表現出過度敏感,從而降低了分支語句的執行速度(循環語句也是一種分支語句)。可以把CPU比作賽跑選手,把代碼比作跑道,選手會在每個彎道(分支語句)處減速。循環展開有助于減少分支,從而減少運行時間。一般按4/8/16重展開,再繼續展開的話,優化效果可以忽略,而書寫的代碼量大,CPU的Cache也容不下這個龐然大物。--《代碼優化:有效使用內存》
3. *((int*) dmem) = *((uint*) smem);
*((int*) (dmem + 2)) = *((uint*) (smem + 2));
*((int*) (dmem + 4)) = *((uint*) (smem + 4));
*((int*) (dmem + 6)) = *((uint*) (smem + 6));
dmem += 8;
smem += 8;
前面四條語句都是根據dmen/smen+X來定位地址,消除了語句之間的數據相關性。如果寫成下面這樣:
*((int*) dmem) = *((uint*) smem);
dmem += 2; smem += 2;
*((int*) dmem) = *((uint*) smem);
dmem += 2; smem += 2;
*((int*) dmem) = *((uint*) smem);
dmem += 2; smem += 2;
*((int*) dmem) = *((uint*) smem);
dmem += 2; smem += 2;
這種寫法不但增加了代碼量,而且還降低了指令執行的并發度。
另外,.net framework 1.1中的ToCharArray()的實現與2.0版本中的有所不同,下面是ToCharArray方法的源碼:
//.net framework 1.1
public char[] ToCharArray()
{
return this.ToCharArray(0, this.Length);
}
public char[] ToCharArray(int startIndex, int length)
{
if (((startIndex < 0) || (startIndex > this.Length)) || (startIndex > (this.Length - length)))
{
throw new ArgumentOutOfRangeException("startIndex", Environment.GetResourceString("ArgumentOutOfRange_Index"));
}
if (length < 0)
{
throw new ArgumentOutOfRangeException("length", Environment.GetResourceString("ArgumentOutOfRange_Index"));
}
char[] chArray1 = new char[length];
this.InternalCopyTo(startIndex, chArray1, 0, length);
return chArray1;
}
[MethodImpl(MethodImplOptions.InternalCall)]
internal extern void InternalCopyTo(int sourceIndex, char[] destination, int destinationIndex, int count);

浙公網安備 33010602011771號