문제

내 프로그램에서는 특수 문자가 포함된 수백만 개의 문자열을 처리합니다."|" 각 문자열 내에서 토큰을 분리합니다.n번째 토큰을 반환하는 함수가 있는데 다음과 같습니다.

function GetTok(const Line: string; const Delim: string; const TokenNum: Byte): string;
{ LK Feb 12, 2007 - This function has been optimized as best as possible }
var
 I, P, P2: integer;
begin
  P2 := Pos(Delim, Line);
  if TokenNum = 1 then begin
    if P2 = 0 then
      Result := Line
    else
      Result := copy(Line, 1, P2-1);
  end
  else begin
    P := 0; { To prevent warnings }
    for I := 2 to TokenNum do begin
      P := P2;
      if P = 0 then break;
      P2 := PosEx(Delim, Line, P+1);
    end;
    if P = 0 then
      Result := ''
    else if P2 = 0 then
      Result := copy(Line, P+1, MaxInt)
    else
      Result := copy(Line, P+1, P2-P-1);
  end;
end; { GetTok }

저는 Delphi 4를 사용할 때 이 기능을 개발했습니다.이는 원래 Fastcode에서 개발되었으며 현재 Delphi의 StrUtils 라이브러리에 포함된 매우 효율적인 PosEx 루틴을 호출합니다.

최근에 Delphi 2009로 업그레이드했는데 문자열이 모두 유니코드입니다.이 GetTok 기능은 여전히 ​​작동하고 잘 작동합니다.

저는 Delphi 2009의 새로운 라이브러리를 살펴보았는데 여기에는 많은 새로운 기능과 추가 사항이 있습니다.

하지만 새로운 Delphi 라이브러리, 다양한 Fastcode 프로젝트에서 필요한 GetToken 기능을 본 적이 없으며 Google 검색 이외의 다른 항목을 찾을 수 없습니다. 자르코 가이치:델파이 분할/토크나이저 기능, 이는 내가 이미 가지고 있는 것만큼 최적화되지 않았습니다.

내 프로그램에서는 10%라도 개선이 눈에 띌 것입니다.대안은 StringLists이고 항상 토큰을 별도로 유지하는 것이라는 것을 알고 있지만 이는 메모리 측면에서 큰 오버헤드를 가지며 변환하기 위해 모든 작업을 수행했는지 여부가 더 빠른지 확실하지 않습니다.

아휴.그래서 이 모든 장황한 이야기를 마친 후, 제 질문은 다음과 같습니다.

GetToken 루틴을 매우 빠르게 구현한 것을 알고 계십니까?어셈블러 최적화 버전이 이상적일까요?

그렇지 않은 경우 위의 코드에서 개선할 수 있는 최적화가 있습니까?


후속 조치:Barry Kelly는 내가 1년 전에 파일의 행 구문 분석 최적화에 대해 물었던 질문을 언급했습니다.그 당시 나는 읽기나 구문 분석에 사용되지 않은 GetTok 루틴을 생각조차 하지 못했습니다.이제서야 이 질문을 하게 된 GetTok 루틴의 오버헤드를 보았습니다.Carl Smotricz와 Barry의 답변이 나올 때까지 저는 이 둘을 연결할 생각을 전혀 해본 적이 없었습니다.너무나 당연하지만 등록되지 않았습니다.그 점을 지적해 주셔서 감사합니다.

예, 제 Delim은 단일 캐릭터이므로 분명히 제가 할 수 있는 주요 최적화가 있습니다.GetTok 루틴(위)에서 Pos 및 PosEx를 사용하면서 다음과 같은 코드를 사용하여 문자별 검색을 사용하면 더 빠르게 검색할 수 있다는 생각을 하지 못하게 되었습니다.

      while (cp^ > #0) and (cp^ <= Delim) do    
        Inc(cp);

나는 모든 사람의 답변을 검토하고 다양한 제안을 시도하고 비교할 것입니다.그럼 결과를 포스팅하겠습니다.


착란:좋아요, 이제 정말 당황스럽습니다.

저는 Carl과 Barry의 추천에 따라 PChars를 사용했으며, 여기에 제가 구현한 내용이 있습니다.

function GetTok(const Line: string; const Delim: string; const TokenNum: Byte): string;
{ LK Feb 12, 2007 - This function has been optimized as best as possible }
{ LK Nov 7, 2009 - Reoptimized using PChars instead of calls to Pos and PosEx }
{ See; https://stackoverflow.com/questions/1694001/is-there-a-fast-gettoken-routine-for-delphi }
var
 I: integer;
 PLine, PStart: PChar;
begin
  PLine := PChar(Line);
  PStart := PLine;
  inc(PLine);
  for I := 1 to TokenNum do begin
    while (PLine^ <> #0) and (PLine^ <> Delim) do
      inc(PLine);
    if I = TokenNum then begin
      SetString(Result, PStart, PLine - PStart);
      break;
    end;
    if PLine^ = #0 then begin
      Result := '';
      break;
    end;
    inc(PLine);
    PStart := PLine;
  end;
end; { GetTok }

서류상으로는 이보다 더 잘할 수는 없을 것 같습니다.

그래서 저는 두 루틴을 모두 작업에 넣고 AQTime을 사용하여 무슨 일이 일어나고 있는지 확인했습니다.제가 수행한 실행에는 GetTok에 대한 1,108,514건의 호출이 포함되었습니다.

AQTime은 원래 루틴의 시간을 0.40초로 측정했습니다.Pos로의 100만 호출에는 0.10초가 걸렸습니다.50만 개의 TokenNum = 1 복사본을 만드는 데 0.10초가 걸렸습니다.60만 건의 PosEx 호출에는 0.03초밖에 걸리지 않았습니다.

그런 다음 동일한 실행과 정확히 동일한 호출에 대해 AQTime을 사용하여 새 루틴의 시간을 측정했습니다.AQTime은 나의 새로운 "빠른" 루틴이 3.65초가 걸렸다고 보고했는데, 이는 9배나 긴 시간입니다.AQTime에 따르면 범인은 첫 번째 루프였습니다.

     while (PLine^ <> #0) and (PLine^ <> Delim) do
       inc(PLine);

1,800만 번 실행된 while 라인은 2.66초로 보고됐다.1600만 번 실행되는 inc 라인은 0.47초가 걸린다고 한다.

이제 나는 여기서 무슨 일이 일어나고 있는지 알고 있다고 생각했습니다.작년에 제기한 질문에서 AQTime과 비슷한 문제가 있었습니다. CharInSet이 Case 문보다 빠른 이유는 무엇입니까?

이번에도 나에게 힌트를 준 사람은 배리 켈리였습니다.기본적으로 AQTime과 같은 계측 프로파일러가 반드시 미세 최적화 작업을 수행하는 것은 아닙니다.이는 각 라인에 오버헤드를 추가하여 이 숫자에 명확하게 표시된 결과를 압도할 수 있습니다.내 새로운 "최적화된 코드"에서 실행된 3,400만 줄은 Pos 및 PosEx 루틴의 오버헤드가 거의 또는 전혀 없이 원래 코드의 수백만 줄을 압도합니다.

Barry는 자신이 정확한지 확인하기 위해 QueryPerformanceCounter를 사용하는 코드 샘플을 나에게 줬고, 그 경우에는 맞았습니다.

자, 이제 QueryPerformanceCounter를 사용하여 동일한 작업을 수행하여 내 새 루틴이 AQTime에서 말하는 것보다 9배 느리지 않고 더 빠르다는 것을 증명해 보겠습니다.그래서 나는 간다:

function TimeIt(const Title: string): double;
var  i: Integer;
  start, finish, freq: Int64;
  Seconds: double;
begin
  QueryPerformanceCounter(start);
  for i := 1 to 250000 do
    GetTokOld('This is a string|that needs|parsing', '|', 1);
  for i := 1 to 250000 do
    GetTokOld('This is a string|that needs|parsing', '|', 2);
  for i := 1 to 250000 do
    GetTokOld('This is a string|that needs|parsing', '|', 3);
  for i := 1 to 250000 do
    GetTokOld('This is a string|that needs|parsing', '|', 4);
  QueryPerformanceCounter(finish);
  QueryPerformanceFrequency(freq);
  Seconds := (finish - start) / freq;
  Result := Seconds;
end;

따라서 GetTok에 대한 1,000,000개의 호출을 테스트합니다.

Pos 및 PosEx 호출에 대한 이전 절차는 0.29초가 걸렸습니다.PChars를 사용한 새로운 작업은 2.07초가 걸렸습니다.

이제 나는 완전히 어리둥절해졌습니다!PChar 절차가 더 느릴 뿐만 아니라 8~9배 더 느린 이유를 말해 줄 수 있는 사람이 있습니까?


미스터리가 풀렸습니다!Andreas는 그의 대답에서 Delim 매개변수를 문자열에서 Char로 변경하라고 말했습니다.나는 항상 Char만을 사용할 것이기 때문에 적어도 내 구현에서는 이것이 매우 가능합니다.나는 무슨 일이 일어났는지 보고 놀랐다.

100만 호출 시간이 1.88초에서 0.22초로 단축되었습니다.

그리고 놀랍게도 Delim 매개변수를 Char로 변경했을 때 원래 Pos/PosEx 루틴의 시간이 .29초에서 .44초로 늘어났습니다.

솔직히 저는 델파이의 옵티마이저에 실망했습니다.Delim은 상수 매개변수입니다.최적화 프로그램은 동일한 변환이 루프 내에서 발생하고 있음을 알아차리고 한 번만 수행되도록 이동했어야 합니다.

코드 생성 매개변수를 다시 확인합니다. 예, Optimization True 및 String 형식 확인이 꺼져 있습니다.

요점은 Andrea가 수정한 새로운 PChar 루틴이 내 원본보다 약 25% 더 빠르다는 것입니다(.22 대 .29).

나는 여전히 여기의 다른 의견에 대해 후속 조치를 취하고 테스트하고 싶습니다.


최적화를 끄고 문자열 형식 검사를 켜면 시간이 .22에서 .30으로 늘어납니다.원본과 거의 동일하게 추가됩니다.

어셈블러 코드를 사용하거나 Pos 또는 PosEx와 같은 어셈블러로 작성된 루틴을 호출할 때의 장점은 사용자가 설정한 코드 생성 옵션의 영향을 받지 않는다는 것입니다.그들은 항상 같은 방식, 즉 사전에 최적화되고 부풀려지지 않은 방식으로 실행됩니다.

나는 지난 며칠 동안 미세 최적화를 위해 코드를 비교하는 가장 좋은 방법은 CPU 창에서 어셈블러 코드를 보고 비교하는 것임을 재확인했습니다.Embarcadero가 해당 창을 좀 더 편리하게 만들고 일부를 클립보드에 복사하거나 일부를 인쇄할 수 있게 해준다면 좋을 것입니다.

또한 저는 이 게시물의 앞부분에서 AQTime을 부당하게 비난했습니다. 새로운 루틴에 추가 시간이 추가된 것은 순전히 추가된 계측 때문이라고 생각했기 때문입니다.이제 다시 돌아가서 String 대신 Char 매개 변수를 사용하여 확인한 결과 while 루프는 2.66에서 .30초로 줄었고 inc 라인은 .47에서 .14초로 줄었습니다.inc 라인도 내려가는 것이 이상합니다.하지만 나는 이미 이 모든 테스트에 지쳐가고 있습니다.


나는 문자별로 반복한다는 Carl의 아이디어를 받아들여 그 아이디어로 해당 코드를 다시 작성했습니다..22초에서 .19초로 또 다른 개선이 이루어졌습니다.이제 지금까지 최고는 다음과 같습니다.

function GetTok(const Line: string; const Delim: Char; const TokenNum: Byte): string;
{ LK Nov 8, 2009 - Reoptimized using PChars instead of calls to Pos and PosEx }
{ See; https://stackoverflow.com/questions/1694001/is-there-a-fast-gettoken-routine-for-delphi }
var
  I, CurToken: Integer;
  PLine, PStart: PChar;
begin
  CurToken := 1;
  PLine := PChar(Line);
  PStart := PLine;
  for I := 1 to length(Line) do begin
    if PLine^ = Delim then begin
      if CurToken = TokenNum then
        break
      else begin
        CurToken := CurToken + 1;
        inc(PLine);
        PStart := PLine;
      end;
    end
    else
      inc(PLine);
  end;
  if CurToken = TokenNum then
    SetString(Result, PStart, PLine - PStart)
  else
    Result := '';
end;

CurToken = Tokennum 비교와 같은 몇 가지 사소한 최적화가 여전히 있을 수 있습니다. 이는 동일한 유형, Integer 또는 Byte 중 더 빠른 유형이어야 합니다.

하지만 지금은 행복하다고 가정 해 봅시다.

StackOverflow Delphi 커뮤니티에 다시 한 번 감사드립니다.

도움이 되었습니까?

해결책

새 함수(PChar가 있는 함수)는 "Delim"을 다음과 같이 선언해야 합니다. 그리고 그렇지 않다 .현재 구현에서 컴파일러는 "Delim"과 비교하기 위해 PLine^ 문자를 문자열로 변환해야 합니다.그리고 이는 긴밀한 루프에서 발생하여 엄청난 성능 저하를 초래합니다.

function GetTok(const Line: string; const Delim: Char{<<==}; const TokenNum: Byte): string;
{ LK Feb 12, 2007 - This function has been optimized as best as possible }
{ LK Nov 7, 2009 - Reoptimized using PChars instead of calls to Pos and PosEx }
{ See; http://stackoverflow.com/questions/1694001/is-there-a-fast-gettoken-routine-for-delphi }
var
 I: integer;
 PLine, PStart: PChar;
begin
  PLine := PChar(Line);
  PStart := PLine;
  inc(PLine);
  for I := 1 to TokenNum do begin
    while (PLine^ <> #0) and (PLine^ <> Delim) do
      inc(PLine);
    if I = TokenNum then begin
      SetString(Result, PStart, PLine - PStart);
      break;
    end;
    if PLine^ = #0 then begin
      Result := '';
      break;
    end;
    inc(PLine);
    PStart := PLine;
  end;
end; { GetTok }

다른 팁

'데림'이 어떤 모습일지 기대가 큰 차이를 만든다.단일 문자일 것으로 예상되는 경우 문자열을 문자별로, 이상적으로는 PChar를 통해 단계별로 실행하고 구체적으로 테스트하는 것이 훨씬 좋습니다.

긴 문자열인 경우 Boyer-Moore 및 유사한 검색에는 건너뛰기 테이블에 대한 설정 단계가 있으며 가장 좋은 방법은 테이블을 한 번 작성하고 후속 검색마다 재사용하는 것입니다.이는 호출 사이에 상태가 필요하다는 것을 의미하며, 이 함수는 대신 객체에 대한 메서드로 사용하는 것이 더 나을 것입니다.

당신은 관심이있을 수 있습니다 이 대답은 제가 얼마 전에 Delphi에서 한 줄을 구문 분석하는 가장 빠른 방법에 대한 질문에 제공한 것입니다. (그런데 질문한 사람이 바로 당신인 걸 보니!그럼에도 불구하고 문제를 해결하면서 구문 분석을 설명하는 방법을 설명하겠습니다. ~ 아니다 Delim의 일반적인 모습에 따라 사용하는 것처럼 PosEx를 사용합니다.)


업데이트:네, 이걸 보는데 40분 정도 걸렸어요.구분 기호가 문자가 될 것이라는 것을 안다면 거의 항상 두 번째 버전을 사용하는 것이 더 나을 것입니다(예:PChar 스캔)을 통과해야 하지만 Delim 캐릭터로.글을 쓰는 시점에 PLine^ 표현식 - Char 유형 - Delim과 비교하기 위한 문자열입니다.그것은 매우 느릴 것입니다.심지어 문자열에 대한 인덱싱도 Delim[1] 또한 다소 느릴 것입니다.

그러나 줄의 크기와 꺼내려는 구분된 부분의 수에 따라 토큰화 루틴 내에서 원치 않는 구분된 부분을 건너뛰는 것보다 재개 가능한 접근 방식을 사용하는 것이 더 나을 수 있습니다.현재 미니 벤치마크에서 수행하는 것처럼 연속적으로 증가하는 인덱스로 GetTok을 호출하면 O(n*n) 성능을 얻게 됩니다. 여기서 n은 구분된 섹션 수입니다.스캔 상태를 저장하고 다음 반복을 위해 복원하거나 추출된 모든 항목을 배열로 압축하면 O(n)으로 바뀔 수 있습니다.

다음은 모든 토큰화를 한 번 수행하고 배열을 반환하는 버전입니다.그러나 배열을 얼마나 크게 만들 것인지 알기 위해서는 두 번 토큰화해야 합니다.반면에 두 번째 토큰화에서만 문자열을 추출해야 합니다.

// Do all tokenization up front.
function GetTok4(const Line: string; const Delim: Char): TArray<string>;
var
  cp, start: PChar;
  count: Integer;
begin
  // Count sections
  count := 1;
  cp := PChar(Line);
  start := cp;
  while True do
  begin
    if cp^ <> #0 then
    begin
      if cp^ <> Delim then
        Inc(cp)
      else
      begin
        Inc(cp);
        Inc(count);
      end;
    end
    else
    begin
      Inc(count);
      Break;
    end;
  end;

  SetLength(Result, count);
  cp := start;
  count := 0;

  while True do
  begin
    if cp^ <> #0 then
    begin
      if cp^ <> Delim then
        Inc(cp)
      else
      begin
        SetString(Result[count], start, cp - start);
        Inc(cp);
        Inc(count);
      end;
    end
    else
    begin
      SetString(Result[count], start, cp - start);
      Break;
    end;
  end;
end;

재개 가능한 접근 방식은 다음과 같습니다.하지만 현재 위치와 구분 기호 문자를 로드하고 저장하는 데는 비용이 듭니다.

type
  TTokenizer = record
  private
    FSource: string;
    FCurrPos: PChar;
    FDelim: Char;
  public
    procedure Reset(const ASource: string; ADelim: Char); inline;
    function GetToken(out AResult: string): Boolean; inline;
  end;

procedure TTokenizer.Reset(const ASource: string; ADelim: Char);
begin
  FSource := ASource; // keep reference alive
  FCurrPos := PChar(FSource);
  FDelim := ADelim;
end;

function TTokenizer.GetToken(out AResult: string): Boolean;
var
  cp, start: PChar;
  delim: Char;
begin
  // copy members to locals for better optimization
  cp := FCurrPos;
  delim := FDelim;

  if cp^ = #0 then
  begin
    AResult := '';
    Exit(False);
  end;

  start := cp;
  while (cp^ <> #0) and (cp^ <> Delim) do
    Inc(cp);

  SetString(AResult, start, cp - start);
  if cp^ = Delim then
    Inc(cp);
  FCurrPos := cp;
  Result := True;
end;

벤치마킹에 사용한 전체 프로그램은 다음과 같습니다.

결과는 다음과 같습니다.

*** count=3, Length(src)=200
GetTok1: 595 ms
GetTok2: 547 ms
GetTok3: 2366 ms
GetTok4: 407 ms
GetTokBK: 226 ms
*** count=6, Length(src)=350
GetTok1: 1587 ms
GetTok2: 1502 ms
GetTok3: 6890 ms
GetTok4: 679 ms
GetTokBK: 334 ms
*** count=9, Length(src)=500
GetTok1: 3055 ms
GetTok2: 2912 ms
GetTok3: 13766 ms
GetTok4: 947 ms
GetTokBK: 446 ms
*** count=12, Length(src)=650
GetTok1: 4997 ms
GetTok2: 4803 ms
GetTok3: 23021 ms
GetTok4: 1213 ms
GetTokBK: 543 ms
*** count=15, Length(src)=800
GetTok1: 7417 ms
GetTok2: 7173 ms
GetTok3: 34644 ms
GetTok4: 1480 ms
GetTokBK: 653 ms

데이터의 특성, 구분 기호가 문자일 가능성이 있는지 여부, 사용 방법에 따라 다양한 접근 방식이 더 빨라질 수 있습니다.

(이전 프로그램에서 실수를 저질렀습니다. 각 루틴 스타일에 대해 동일한 작업을 측정하지 않았습니다.페이스트빈 링크와 벤치마크 결과를 업데이트했습니다.)

Delphi는 매우 효율적인 코드로 컴파일됩니다.내 경험상 어셈블러를 더 잘하는 것은 매우 어려웠습니다.

내 생각에는 PChar를 지정해야 한다고 생각합니다. (아직도 존재하지 않습니까?문자열 시작 부분에서 Delphi 4.0)과 헤어지고 n-1개를 찾을 때까지 "|"를 세면서 문자열을 증가시킵니다.PosEx를 반복적으로 호출하는 것보다 더 빠를 것이라고 생각합니다.

해당 위치를 기록한 후 다음 파이프에 도달할 때까지 포인터를 좀 더 증가시킵니다.하위 문자열을 꺼내세요.완료.

나는 추측일 뿐이지만 이것이 이 문제를 해결할 수 있는 가장 빠른 방법에 가깝다면 놀라지 않을 것입니다.

편집하다: 내가 염두에 둔 것은 다음과 같습니다.아쉽게도 이 코드는 컴파일되지도, 테스트되지도 않았지만, 내가 의미하는 바를 보여줘야 합니다.

특히 Delim은 단일 문자로 취급되므로 요구 사항을 충족한다면 엄청난 차이가 있을 것이라고 생각하며 PLine의 문자는 한 번만 테스트됩니다.마지막으로 TokenNum과 더 이상 비교할 수 없습니다.구분 기호를 계산하려면 카운터를 0으로 줄이는 것이 더 빠르다고 생각합니다.

function GetTok(const Line: string; const Delim: string; const TokenNum: Byte): string;
var 
  Del: Char;
  PLine, PStart: PChar;
  Nth, I, P0, P9: Integer;
begin
  Del := Delim[1];
  Nth := TokenNum + 1;
  P0 := 1;
  P9 := Line.length + 1;
  PLine := PChar(line);
  for I := 1 to P9 do begin
    if PLine^ = Del then begin
      if Nth = 0 then begin
        P9 := I;
        break;
      end;
      Dec(Nth);
      if Nth = 0 then P0 := I + 1
    end;
    Inc(PLine);
  end;
  if (Nth <= 1) or (TokenNum = 1) then
    Result := Copy(Line, P0, P9 - P0);
  else
    Result := '' 
end;

어셈블러를 사용하면 미세하게 최적화될 수 있습니다.알고리즘을 최적화하면 훨씬 더 큰 이득을 얻을 수 있습니다.일을 하지 않는 것은 언제나 가능한 가장 빠른 방법으로 일하는 것보다 낫습니다.

한 가지 예는 프로그램에 동일한 줄의 여러 토큰이 필요한 위치가 있는 경우입니다.인덱싱할 수 있는 토큰 배열을 반환하는 또 다른 프로시저는 함수를 두 번 이상 호출하는 것보다 더 빠릅니다. 특히 프로시저가 모든 토큰을 반환하지 않고 필요한 만큼만 반환하도록 하는 경우 더욱 그렇습니다.

그러나 일반적으로 나는 Carl의 답변 (+1)에 동의합니다. PChar 아마도 현재 코드보다 더 빠를 것입니다.

이것은 제가 꽤 오랫동안 개인 라이브러리에 가지고 있었고 광범위하게 사용하고 있는 기능입니다.나는 이것이 가장 최신 버전이라고 생각합니다.과거에는 다양한 이유로 최적화된 여러 버전이 있었습니다.이것은 인용된 문자열을 고려하려고 시도하지만 해당 코드가 제거되면 함수가 약간 더 빨라집니다.

실제로는 CountSections 및 ParseSectionPOS와 같은 다른 루틴도 많이 있습니다.

불행하게도 이 루틴은 ansi/pchar 기반입니다.유니코드로 옮기는 것은 어렵지 않을 것 같지만요.어쩌면 이미 그렇게 했을 수도 있겠네요... 확인해 봐야겠습니다.

메모:이 루틴은 ParseNum 인덱싱을 기반으로 하는 1입니다.

function ParseSection(ParseLine: string; ParseNum: Integer; ParseSep: Char; QuotedStrChar:char = #0) : string;
var
   wStart, wEnd : integer;
   wIndex : integer;
   wLen : integer;
   wQuotedString : boolean;
begin
   result := '';
   wQuotedString := false;
   if not (ParseLine = '') then
   begin
      wIndex := 1;
      wStart := 1;
      wEnd := 1;
      wLen := Length(ParseLine);
      while wEnd <= wLen do
      begin
         if (QuotedStrChar <> #0) and (ParseLine[wEnd] = QuotedStrChar) then
            wQuotedString := not wQuotedString;

         if not wQuotedString and (ParseLine[wEnd] = ParseSep) then
         begin
            if wIndex=ParseNum then
               break
            else
            begin
               inc(wIndex);
               wStart := wEnd+1;
            end;
         end;
         inc(wEnd);
      end;

      result := copy(ParseLine, wStart, wEnd-wStart);
      if (length(result) > 0) and (QuotedStrChar <> #0) and (result[1] = QuotedStrChar) then
         result := AnsiDequotedStr(result, QuotedStrChar);
   end;
end; { ParseSection }

귀하의 코드에서는 이것이 최적화될 수 있는 유일한 줄이라고 생각합니다.

Result := copy(Line, P+1, MaxInt)

거기에서 새로운 길이를 계산하면 조금 더 빨라질 수 있지만 원하는 10%는 아닙니다.

토큰화 알고리즘은 꽤 괜찮은 것 같습니다.최적화를 위해 프로파일러를 통해 실행하겠습니다(예: AQ시간 AutomatedQA에서)를 생산 데이터의 대표적인 하위 집합으로 포함합니다.그것은 당신에게 가장 약한 부분을 가리킬 것입니다.

가장 근접한 유일한 RTL 함수는 Classes 유닛에 있는 함수입니다:

procedure TStrings.SetDelimitedText(const Value: string);

토큰화하지만 둘 다 사용합니다. 견적문자 그리고 구분 기호, 그러나 구분 기호만 사용합니다.

그것은 문자열 설정 PChar/PAnsiChar/PUUnicodeChar 및 길이를 기반으로 문자열의 내용을 설정하는 매우 빠른 방법인 시스템 유닛의 함수입니다.

그러면 약간의 개선도 얻을 수 있습니다.반면에, 복사 정말 빠르기도 해요.

나는 항상 알고리즘을 비난하는 사람은 아니지만 첫 번째 소스를 보면 문제는 문자열 n의 경우 문자열 1..N-1에 대해 POS/POSEXES를 다시 수행한다는 것입니다.

이는 N개 항목에 대해 합계 (n, n-1,n-2...1) POSes (=+/- 0.5*N^2) 를 수행하지만 N개만 필요함을 의미합니다.

마지막으로 발견된 결과의 위치를 ​​단순히 캐시하는 경우(예:VAR 매개변수로 전달된 기록에서는 많은 이득을 얻을 수 있습니다.

유형
tlastPosition = Record ElementNr :정수;// 마지막 토큰 숫자 요소 :정수;// 마지막 경기의 문자 인덱스 끝;

그리고 뭔가

iF 토큰넘버=(lastposition.elementnr 1) THEN 시작 newpos:=posex(delim,line,lastposition.elementpos);끝;

안타깝게도 지금은 글을 쓸 시간이 없지만, 여러분이 아이디어를 얻으셨기를 바랍니다.

라이센스 : CC-BY-SA ~와 함께 속성
제휴하지 않습니다 StackOverflow
scroll top