寫程式是一種態度(二)四倍速memmove (轉)

worldblog發表於2007-12-13
寫程式是一種態度(二)四倍速memmove (轉)[@more@]

最近在網上發現一個有趣的現象,一些公司的技術面試總是光顧一些常用的ANSI C,我去年也有幸碰到了實現一個簡單scanf的命題。這類題,表面上看實際上都不難,但是想把它考慮全面,寫的精煉且高效卻很難,沒有一定內功的人是達不到這個水準的,這也是頻頻被用來做測試人才尺子的原因。

:namespace prefix = o ns = "urn:schemas--com::office" />

其實很早就知道有些高手為了練內功在讀C run-time程式碼,於是也深入了一段時間,同時我將視野放的更廣一些,涉及到了更多的經典程式碼,對端正我的寫態度起了很大的作用。趁這段時間辟穀,希望用這個系列把一些點滴記錄下來,藉著同廣大網友交流的絕好時機,再重新審視一番我的思路。

先要感謝第一位給我的“寫程式是一種態度(一)strcmp”回覆的網友darkay,將我的視線移入了更有興趣的話題。我提到MS run-time用C來實現函式如strcmp只是演算法的表徵,相對應的都有asm如strcmp.asm才是對其演算法的具體的針對指令集的高效實現。如此可以做一個也許不恰當的類比,strcmp.c是一個偽碼描述,而strcmp.asm才是具體實現;因為用某種c編譯後的strcmp.c很可能沒有直接的strcmp.asm更高效,儘管演算法的思路沒有變化。藉著這個話題我們在看一看經典的memcpy和memmove:

void * __cdecl memcpy (

  void * dst,

  const void * src,

  size_t count

  )

{

  void * ret = dst;

  /*

  * copy from lower addresses to higher addresses

  */

  while (count--) {

  *(char *)dst = *(char *)src;

  dst = (char *)dst + 1;

  src = (char *)src + 1;

  }

  return(ret);

}

void * __cdecl memmove (

  void * dst,

  const void * src,

  size_t count

  )

{

  void * ret = dst;

  if (dst <= src || (char *)dst >= ((char *)src + count)) {

  /*

   * Non-Overlap Buffers

  * copy from lower addresses to higher addresses

  */

  while (count--) {

  *(char *)dst = *(char *)src;

  dst = (char *)dst + 1;

  src = (char *)src + 1;

  }

  }

  else {

  /*

  * Overlapping Buffers

  * copy from higher addresses to lower addresses

  */

  dst = (char *)dst + count - 1;

  src = (char *)src + count - 1;

  while (count--) {

  *(char *)dst = *(char *)src;

  dst = (char *)dst - 1;

  src = (char *)src - 1;

   }

  }

  return(ret);

}

這裡我省掉了

#if defined (_M_MRX000) || defined (_M_ALPHA) || defined (_M_PPC) || defined (_M_IA64)

編譯開關裡的CODE,針對這些目標機有另外的處理,我們現在只定位於Intel 32-bit上。

1.文件中說的很清楚memcpy不考慮重疊,而memmove會考慮,實際上程式碼中很明顯,memcpy只是memmove的一個子集,所以建議總是用memmove這樣可以不考慮記憶體重疊問題。

2.考慮能否用C++去描述更精煉?如從低地址到高地址的賦值可以簡單寫成:

  while (count--) {

  *dst ++ = *src ++;

  }

3.c編譯器對以上程式碼編譯後所產生的指令是否是一個位元組一個位元組複製?

4.問題3將帶來我們對副標題的討論--四倍速

Intel 80386以上支援的指令集中MOVSD指令和REP指令配合將D(32bit)在記憶體間移動,即在一個時鐘週期copy四個位元組,整整比MOVSB(8bit)指令快了四倍。但是使用MOVSD移動到的目的記憶體地址必須是32bit對齊的(DWORD-aligned)。簡單說明從低位到高位的記憶體copy如下。

設L為要複製的總位元組數,Dest為目的起始地址,X為從Dest開始沒有DWORD-aligned的位元組數,Y為要複製的DWORD個數,Z為剩餘的沒有DWORD-aligned位元組數。那麼有公式如下:

X = (4 – Dest & 3 ) & 3 (bytes)  //低兩位為0的地址是DWORD-aligned

Y = (L – X) >> 2 (DWORDs)  //整除以4是DWORD個數

Z = (L – X – Y * 4) (bytes)

  再做相應的處理。 

總結一下,對大段的記憶體移動,用memmove將是非常的,相反若用c寫的code會降低四倍,這就是為什麼要用ASM直接實現的原因。其實讀程式也是一種態度,不知道我是否鑽了牛角尖了,但願不是。


來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/10752043/viewspace-992700/,如需轉載,請註明出處,否則將追究法律責任。

相關文章