汇编级超快字符串替换函数

 用这个函数进行字符串替换操作,比Delphi自带的ReplaceString要快N倍,效率一流,非常快,不愧为汇编级函数操作,哈哈.

下载函数单元文件:freplace.rar

delphi代码

  1. unit FReplace;   
  2.   
  3. interface  
  4.   
  5. Type   
  6. TFastPosProc = function(   
  7. const aSourceString, aFindString : String;   
  8. const aSourceLen, aFindLen, StartPos : integer  
  9. ) : integer;   
  10.   
  11. function FastReplace(   
  12. var aSourceString : String;   
  13. const aFindString, aReplaceString : String;   
  14. CaseSensitive : Boolean = False) : String;   
  15.   
  16. function FastPos(   
  17. const aSourceString, aFindString : String;   
  18. const aSourceLen, aFindLen, StartPos : integer  
  19. ) : integer;   
  20.   
  21. function FastPosNoCase(   
  22. const aSourceString, aFindString : String;   
  23. const aSourceLen, aFindLen, StartPos : integer  
  24. ) : integer;   
  25.   
  26. implementation  
  27.   
  28. // This TYPE declaration will become apparent later.  
  29. //The first thing to note here is that I’m passing the SourceLength and FindL  
  30. //ength. As neither Source nor Find will alter at any point during FastReplace  
  31. //, there’s no need to call the LENGTH subroutine each time!  
  32. function FastPos(   
  33. const aSourceString, aFindString : String;   
  34. const aSourceLen, aFindLen, StartPos : integer  
  35. ) : integer;   
  36. var  
  37. SourceLen : integer;   
  38. begin  
  39.   // Next, we determine how many bytes we need to  
  40.   // scan to find the "start" of aFindString.  
  41.   SourceLen := aSourceLen;   
  42.   SourceLen := SourceLen - aFindLen;   
  43.   if (StartPos-1) > SourceLen then begin  
  44.     Result := 0;   
  45.     Exit;   
  46.   end;   
  47.   SourceLen := SourceLen - StartPos;   
  48.   SourceLen := SourceLen +2;   
  49.   // The ASM starts here.  
  50.   asm  
  51.     // Delphi uses ESI, EDI, and EBX a lot,  
  52.     // so we must preserve them.  
  53.     push ESI   
  54.     push EDI   
  55.     push EBX   
  56.     // Get the address of sourceString[1]  
  57.     // and Add (StartPos-1).  
  58.     // We do this for the purpose of finding  
  59.     // the NEXT occurrence, rather than  
  60.     // always the first!  
  61.     mov EDI, aSourceString   
  62.     add EDI, StartPos   
  63.     Dec EDI   
  64.     // Get the address of aFindString.  
  65.     mov ESI, aFindString   
  66.     // Note how many bytes we need to  
  67.     // look through in aSourceString  
  68.     // to find aFindString.  
  69.     mov ECX, SourceLen   
  70.     // Get the first char of aFindString;  
  71.     // note how it is done outside of the  
  72.     // main loop, as it never changes!  
  73.     Mov  Al, [ESI]   
  74.     // Now the FindFirstCharacter loop!  
  75.     @ScaSB:   
  76.     // Get the value of the current  
  77.     // character in aSourceString.  
  78.     // This is equal to ah := EDI^, that  
  79.     // is what the [] are around [EDI].  
  80.     Mov  Ah, [EDI]   
  81.     // Compare this character with aDestString[1].  
  82.     cmp  Ah,Al   
  83.     // If they're not equal we don't  
  84.     // compare the strings.  
  85.     jne  @NextChar   
  86.     // If they're equal, obviously we do!  
  87.     @CompareStrings:   
  88.     // Put the length of aFindLen in EBX.  
  89.     mov  EBX, aFindLen   
  90.     // We DEC EBX to point to the end of  
  91.     // the string; that is, we don't want to  
  92.     // add 1 if aFindString is 1 in length!  
  93.     dec  EBX   
  94.        
  95.     // add by ShengQuanhu  
  96.     // If EBX is zero, then we've successfully  
  97.     // compared each character; i.e. it's A MATCH!  
  98.     // It will be happened when aFindLen=1  
  99.     Jz @EndOfMatch   
  100.     //add end  
  101.   
  102.     //Here’s another optimization tip. People at this point usually PUSH ESI and  
  103. //so on and then POP ESI and so forth at the end–instead, I opted not to chan  
  104. //ge ESI and so on at all. This saves lots of pushing and popping!  
  105.     @CompareNext:   
  106.     // Get aFindString character +  
  107.     // aFindStringLength (the last char).  
  108.     mov  Al, [ESI+EBX]   
  109.     // Get aSourceString character (current  
  110.     // position + aFindStringLength).  
  111.     mov  Ah, [EDI+EBX]   
  112.     // Compare them.  
  113.     cmp  Al, Ah   
  114.     Jz   @Matches   
  115.     // If they don't match, we put the first char  
  116.     // of aFindString into Al again to continue  
  117.     // looking for the first character.  
  118.     Mov  Al, [ESI]   
  119.     Jmp  @NextChar   
  120.     @Matches:   
  121.     // If they match, we DEC EBX (point to  
  122.     // previous character to compare).  
  123.     Dec  EBX   
  124.     // If EBX <> 0 ("J"ump "N"ot "Z"ero), we  
  125.     // continue comparing strings.  
  126.     Jnz  @CompareNext   
  127.        
  128.     //add by Shengquanhu  
  129.     @EndOfMatch:   
  130.     //add end  
  131.   
  132.     // If EBX is zero, then we've successfully  
  133.     // compared each character; i.e. it's A MATCH!  
  134.     // Move the address of the *current*  
  135.     // character in EDI.  
  136.     // Note, we haven't altered EDI since  
  137.     // the first char was found.  
  138.     mov  EAX, EDI   
  139.     // This is an address, so subtract the  
  140.     // address of aSourceString[1] to get  
  141.     // an actual character position.  
  142.     sub  EAX, aSourceString   
  143.     // Inc EAX to make it 1-based,  
  144.     // rather than 0-based.  
  145.     inc  EAX   
  146.     // Put it into result.  
  147.     mov  Result, EAX   
  148.     // Finish this routine!  
  149.     jmp  @TheEnd   
  150.     @NextChar:   
  151.     //This is where I jump to when I want to continue searching for the first char  
  152. //acter of aFindString in aSearchString:  
  153.     // Point EDI (aFindString[X]) to  
  154.     // the next character.  
  155.     Inc  EDI   
  156.     // Dec ECX tells us that we've checked  
  157.     // another character, and that we're  
  158.     // fast running out of string to check!  
  159.     dec  ECX   
  160.     // If EBX <> 0, then continue scanning  
  161.     // for the first character.  
  162.   
  163.     //add by shengquanhu  
  164.     //if ah is chinese char,jump again  
  165.     jz   @Result0   
  166.     cmp  ah, $80  
  167.     jb   @ScaSB   
  168.     Inc  EDI   
  169.     Dec  ECX   
  170.     //add by shengquanhu end  
  171.   
  172.     jnz  @ScaSB   
  173.        
  174.     //add by shengquanhu  
  175.     @Result0:   
  176.     //add by shengquanhu end  
  177.   
  178.     // If EBX = 0, then move 0 into RESULT.  
  179.     mov  Result,0  
  180.     // Restore EBX, EDI, ESI for Delphi  
  181.     // to work correctly.  
  182.     // Note that they're POPped in the  
  183.     // opposite order they were PUSHed.  
  184.     @TheEnd:   
  185.     pop  EBX   
  186.     pop  EDI   
  187.     pop  ESI   
  188.   end;   
  189. end;   
  190.   
  191. //This routine is an identical copy of FastPOS except where commented! The ide  
  192. //a is that when grabbing bytes, it ANDs them with $df, effectively making the  
  193. //m lowercase before comparing. Maybe this would be quicker if aFindString was  
  194. // made lowercase in one fell swoop at the beginning of the function, saving a  
  195. //n AND instruction each time.  
  196. function FastPosNoCase(   
  197. const aSourceString, aFindString : String;   
  198. const aSourceLen, aFindLen, StartPos : integer  
  199. ) : integer;   
  200. var  
  201. SourceLen : integer;   
  202. begin  
  203.   SourceLen := aSourceLen;   
  204.   SourceLen := SourceLen - aFindLen;   
  205.   if (StartPos-1) > SourceLen then begin  
  206.     Result := 0;   
  207.     Exit;   
  208.   end;   
  209.   SourceLen := SourceLen - StartPos;   
  210.   SourceLen := SourceLen +2;   
  211.   asm  
  212.     push ESI   
  213.     push EDI   
  214.     push EBX   
  215.        
  216.     mov EDI, aSourceString   
  217.     add EDI, StartPos   
  218.     Dec EDI   
  219.     mov ESI, aFindString   
  220.     mov ECX, SourceLen   
  221.     Mov  Al, [ESI]   
  222.        
  223.     //add by shengquanhu:just modified the lowercase 'a'..'z'  
  224.     cmp Al, $7A  
  225.     ja @ScaSB   
  226.        
  227.     cmp Al, $61  
  228.     jb @ScaSB   
  229.     //end------------------------------------------  
  230.   
  231.     // Make Al uppercase.  
  232.     and  Al, $df  
  233.        
  234.     @ScaSB:   
  235.     Mov  Ah, [EDI]   
  236.        
  237.     //add by shengquanhu:just modified the lowercase 'a'..'z'  
  238.     cmp Ah, $7A  
  239.     ja @CompareChar   
  240.        
  241.     cmp Ah, $61  
  242.     jb @CompareChar   
  243.     //end------------------------------------------  
  244.   
  245.     // Make Ah uppercase.  
  246.     and  Ah, $df  
  247.        
  248.     @CompareChar:   
  249.     cmp  Ah,Al   
  250.     jne  @NextChar   
  251.     @CompareStrings:   
  252.     mov  EBX, aFindLen   
  253.     dec  EBX   
  254.        
  255.     //add by ShengQuanhu  
  256.     Jz   @EndOfMatch   
  257.     //add end  
  258.   
  259.     @CompareNext:   
  260.     mov  Al, [ESI+EBX]   
  261.     mov  Ah, [EDI+EBX]   
  262.        
  263.     //add by shengquanhu:just modified the lowercase 'a'..'z'  
  264.     cmp Ah, $7A  
  265.     ja @LowerAh   
  266.        
  267.     cmp Al, $61  
  268.     jb @LowerAh   
  269.     //end------------------------------------------  
  270.   
  271.     // Make Al and Ah uppercase.  
  272.     and  Al, $df  
  273.        
  274.     //add by shengquanhu:just modified the lowercase 'a'..'z'  
  275.     @LowerAh:   
  276.     cmp Ah, $7A  
  277.     ja @CompareChar2   
  278.        
  279.     cmp Ah, $61  
  280.     jb @CompareChar2   
  281.     //end------------------------------------------  
  282.   
  283.     and  Ah, $df  
  284.        
  285.     @CompareChar2:   
  286.     cmp  Al, Ah   
  287.     Jz   @Matches   
  288.     Mov  Al, [ESI]   
  289.        
  290.     //add by shengquanhu:just modified the lowercase 'a'..'z'  
  291.     cmp Al, $7A  
  292.     ja @NextChar   
  293.        
  294.     cmp Al, $61  
  295.     jb @NextChar   
  296.     //end------------------------------------------  
  297.   
  298.     // Make Al uppercase.  
  299.     and  Al, $df  
  300.     Jmp  @NextChar   
  301.     @Matches:   
  302.     Dec  EBX   
  303.     Jnz  @CompareNext   
  304.        
  305.     //add by Shengquanhu  
  306.     @EndOfMatch:   
  307.     //add end  
  308.   
  309.     mov  EAX, EDI   
  310.     sub  EAX, aSourceString   
  311.     inc  EAX   
  312.     mov  Result, EAX   
  313.     jmp  @TheEnd   
  314.     @NextChar:   
  315.     Inc  EDI   
  316.     dec  ECX   
  317.     //add by shengquanhu  
  318.     //if ah is chinese char,jump again  
  319.     jz   @Result0   
  320.     cmp  ah, $80  
  321.     jb   @ScaSB   
  322.     Inc  EDI   
  323.     Dec  ECX   
  324.     //add by shengquanhu end  
  325.     jnz  @ScaSB   
  326.     @Result0:   
  327.     mov  Result,0  
  328.     @TheEnd:   
  329.     pop  EBX   
  330.     pop  EDI   
  331.     pop  ESI   
  332.   end;   
  333. end;   
  334.   
  335. //My move isn’t as fast as MOVE when source and destination are both DWord al  
  336. //igned, but it’s certainly faster when they’re not. As we’re moving charac  
  337. //ters in a string, it isn’t very likely at all that both source and destinat  
  338. //ion are DWord aligned, so moving bytes avoids the cycle penalty of reading/w  
  339. //riting DWords across physical boundaries.  
  340. procedure MyMove(   
  341. const Source; var Dest; Count : Integer);   
  342. asm  
  343.   // Note: When this function is called,  
  344. // Delphi passes the parameters as follows:  
  345. // ECX = Count  
  346. // EAX = Const Source  
  347. // EDX = Var Dest  
  348.   // If there are no bytes to copy, just quit  
  349.   // altogether; there's no point pushing registers.  
  350.   cmp   ECX,0  
  351.   Je    @JustQuit   
  352.   // Preserve the critical Delphi registers.  
  353.   push  ESI   
  354.   push  EDI   
  355.   // Move Source into ESI (generally the  
  356.   // SOURCE register).  
  357.   // Move Dest into EDI (generally the DEST  
  358.   // register for string commands).  
  359.   // This might not actually be necessary,  
  360.   // as I'm not using MOVsb etc.  
  361.   // I might be able to just use EAX and EDX;  
  362.   // there could be a penalty for not using  
  363.   // ESI, EDI, but I doubt it.  
  364.   // This is another thing worth trying!  
  365.   mov   ESI, EAX   
  366.   mov   EDI, EDX   
  367.   // The following loop is the same as repNZ  
  368.   // MovSB, but oddly quicker!  
  369.     @Loop:   
  370.   // Get the source byte.  
  371.   Mov   AL, [ESI]   
  372.   // Point to next byte.  
  373.   Inc   ESI   
  374.   // Put it into the Dest.  
  375.   mov   [EDI], AL   
  376.   // Point dest to next position.  
  377.   Inc   EDI   
  378.   // Dec ECX to note how many we have left to copy.  
  379.   Dec   ECX   
  380.   // If ECX <> 0, then loop.  
  381.   Jnz   @Loop   
  382.   // Another optimization note.  
  383.   // Many people like to do this.  
  384.   // Mov AL, [ESI]  
  385.   // Mov [EDI], Al  
  386.   // Inc ESI  
  387.   // Inc ESI  
  388. //There’s a hidden problem here. I won’t go into too much detail, but the Pe  
  389. //ntium can continue processing instructions while it’s still working out the  
  390. // result of INC ESI or INC EDI. If, however, you use them while they’re stil  
  391. //l being calculated, the processor will stop until they’re calculated (a pen  
  392. //alty). Therefore, I alter ESI and EDI as far in advance as possible of using  
  393. // them.  
  394.   // Pop the critical Delphi registers  
  395.   // that we've altered.  
  396.   pop   EDI   
  397.   pop   ESI   
  398.   @JustQuit:   
  399. end;   
  400.   
  401. //Point 1: I pass VAR aSourceString rather than just aSourceString. This is be  
  402. //cause I’ll just be passed a pointer to the data rather than a 10M copy of t  
  403. //he data itself, which is much quicker!  
  404. function FastReplace(   
  405. var aSourceString : String;   
  406. const aFindString, aReplaceString : String;   
  407. CaseSensitive : Boolean = False) : String;   
  408. var  
  409. // Size already passed to SetLength,  
  410.   // the REAL size of RESULT.  
  411.   ActualResultLen,   
  412. // Position of aFindString is aSourceString.  
  413.   CurrentPos,   
  414. // Last position the aFindString was found at.  
  415.   LastPos,   
  416. // Bytes to copy (that is, lastpos to this pos).  
  417.   BytesToCopy,   
  418. // The "running" result length, not the actual one.  
  419.   ResultLen,   
  420. // Length of aFindString, to save  
  421.   // calling LENGTH repetitively.  
  422.   FindLen,   
  423. // Length of aReplaceString, for the same reason.  
  424.   ReplaceLen,   
  425. SourceLen         : Integer;   
  426. // This is where I explain the  
  427.   // TYPE TFastPosProc from earlier!  
  428.   FastPosProc       : TFastPosProc;   
  429. begin  
  430.   //As this function has the option of being case-insensitive, I’d need to call  
  431. // either FastPOS or FastPOSNoCase. The problem is that you’d have to do this  
  432. // within a loop. This is a bad idea, since the result never changes throughou  
  433. //t the whole operation–in which case we can determine it in advance, like so  
  434. //:  
  435.   if CaseSensitive then  
  436.   FastPosProc := FastPOS   
  437.   else  
  438.   FastPOSProc := FastPOSNoCase;   
  439.   // I don't think I actually need  
  440.   // this, but I don't really mind!  
  441.   Result := '';   
  442.   // Get the lengths of the strings.  
  443.   FindLen := Length(aFindString);   
  444.   ReplaceLen := Length(aReplaceString);   
  445.   SourceLen := Length(aSourceString);   
  446.   // If we already have room for the replacements,  
  447.   // then set the length of the result to  
  448.   // the length of the SourceString.  
  449.   if ReplaceLen <= FindLen then  
  450.   ActualResultLen := SourceLen   
  451.   else  
  452.   // If not, we need to calculate the  
  453.     // worst-case scenario.  
  454.     // That is, the Source consists ONLY of  
  455.     // aFindString, and we're going to replace  
  456.     // every one of them!  
  457.     ActualResultLen :=   
  458.   SourceLen +   
  459.   (SourceLen * ReplaceLen div FindLen) +   
  460.   ReplaceLen;   
  461.   // Set the length of Result; this  
  462.   // will assign the memory, etc.  
  463.   SetLength(Result,ActualResultLen);   
  464.   CurrentPos := 1;   
  465.   ResultLen := 0;   
  466.   LastPos := 1;   
  467.   //Again, I’m eliminating an IF statement in a loop by repeating code–this ap  
  468. //proach results in very slightly larger code, but if ever you can trade some  
  469. //memory in exchange for speed, go for it!  
  470.   if ReplaceLen > 0 then begin  
  471.     repeat  
  472.     // Get the position of the first (or next)  
  473.       // aFindString in aSourceString.  
  474.       // Note that there's no If CaseSensitive,  
  475.       // I just call FastPOSProc, which is pointing  
  476.       // to the correct pre-determined routine.  
  477.       CurrentPos :=   
  478.     FastPosProc(aSourceString, aFindString,   
  479.     SourceLen, FindLen, CurrentPos);   
  480.     // If 0, then we're finished.  
  481.       if CurrentPos = 0 then break;   
  482.     // Number of bytes to copy from the  
  483.       // source string is CurrentPos - lastPos,  
  484.       // i.e. " cat " in "the cat the".  
  485.       BytesToCopy := CurrentPos-LastPos;   
  486.     // Copy chars from aSourceString  
  487.       // to the end of Result.  
  488.       MyMove(aSourceString[LastPos],   
  489.     Result[ResultLen+1], BytesToCopy);   
  490.     // Copy chars 

Posted in 软件开发 by 傻猫 at October 23, 2007.
Tags: 字符, 函数, 汇编, 替换

添加新评论