18

困惑しています。今日のCodeRageで、Marco Cantuは、CharInSetが遅いので、代わりにCaseステートメントを試す必要があると述べました。パーサーでこれを行ってから、AQTimeでスピードアップを確認しました。Caseステートメントの方がはるかに遅いことがわかりました。

4,894,539の実行:

CharInSet(P ^、[''、#10、#13、#0])ではありませんがinc(P);を実行します。

0.25秒で計時されました。

しかし、同じ数の実行:

Trueは、     ''、#10、#13、#0の
  ケースP ^を実行します:ブレーク;     else inc(P);   終わり;


「whileTrue」の場合は.16秒、最初の場合は.80秒、その他の場合は.13秒かかり、合計で1.09秒、つまり4倍以上の長さになります。

CharInSetステートメントのアセンブラーコードは次のとおりです。

add edi、$ 02
mov edx、$ 0064b290
movzx eax、[edi]
call CharInSet
test a1、a1
jz $ 00649f18(addステートメントに戻る)

一方、ケースロジックは単純に次のとおりです。

movzx eax、[edi]
sub ax、$ 01
jb $ 00649ef0
sub ax、$ 09
jz $ 00649ef0
sub ax、$ 03
jz $ 00649ef0
add edi、$ 02
jmp $ 00649ed6(movzxステートメントに戻る)

ケースロジックは非常に効率的なアセンブラを使用しているように見えますが、CharInSetステートメントは実際にはSysUtilsにあり、次のような単純なCharInSet関数を呼び出す必要があります。

関数CharInSet(C:AnsiChar; const CharSet:TSysCharSet):ブール値;
結果の開始
:=CharSetのC;
終わり;

これが行われる唯一の理由は、[''、#10、#13、#0]のP ^がDelphi2009で許可されなくなったため、呼び出しが型の変換を行って許可するためだと思います。

それにもかかわらず、私はこれに非常に驚いており、それでも私の結果を信頼していません。

AQTimeは何か間違ったものを測定していますか、この比較で何かが欠けていますか、それともCharInSetは本当に使用する価値のある効率的な関数ですか?


結論:

バリー、わかったと思う。時間を割いて詳細な例を示していただきありがとうございます。私は自分のマシンでコードをテストし、.171、.066、および.052秒を取得しました(私のデスクトップはラップトップよりも少し速いと思います)。

そのコードをAQTimeでテストすると、3つのテストで0.79、1.57、1.46秒になります。そこでは、計装からの大きなオーバーヘッドを見ることができます。しかし、私が本当に驚いたのは、このオーバーヘッドによって、見かけの「最良の」結果が、実際には最悪のCharInSet関数に変わることです。

したがって、Marcuは正しく、CharInSetは低速です。しかし、あなたはうっかりして(または意図的に)、CharInSetがSetメソッドのAnsiChar(P ^)で何をしているのかを引き出すことによって私にもっと良い方法を与えてくれました。ケース方式に比べて速度がわずかに優れていることを除けば、ケースを使用するよりもコードが少なく、理解しやすいです。

また、AQTime(および他のインストルメンテーションプロファイラー)を使用した誤った最適化の可能性についても認識しました。これを知っていると、Delphi用のプロファイラーとメモリ分析ツールに関する私の決定に役立ちます。また、 AQTimeはどのようにそれを行うのかという私の質問に対する別の答えでもあります。。もちろん、AQTimeはインストルメント時にコードを変更しないため、他の魔法を使用して変更する必要があります。

したがって、答えは、AQTimeが誤った結論につながる結果を示しているということです。


フォローアップ:AQTimeの結果が誤解を招く可能性があるという「告発」をこの質問に残しました。しかし、公平を期すために、この質問を読むように指示する必要があります。Delphiの高速GetTokenルーチンはありますか?これは、AQTimeが誤解を招く結果をもたらすと考え始めたものであり、そうではないと結論付けています。

4

5 に答える 5

26

AQTimeはインストルメンテーションプロファイラーです。インストルメンテーションプロファイラーは、特にあなたのようなマイクロベンチマークでは、コード時間を測定するのに適していないことがよくあります。これは、インストルメンテーションのコストが測定対象のコストを上回ることが多いためです。一方、インストルメンテーションプロファイラーは、メモリやその他のリソース使用量のプロファイリングに優れています。

通常、CPUの位置を定期的にチェックするサンプリングプロファイラーは、コード時間を測定するのに適しています。

いずれにせよ、ここに別のマイクロベンチマークがあります。これは、caseステートメントが。よりも高速であることを実際に示していCharInSetます。ただし、型キャストでセットチェックを使用して、切り捨ての警告を排除できることに注意してください(実際には、これがCharInSetが存在する唯一の理由です)。

{$apptype console}

uses Windows, SysUtils;

const
  SampleString = 'foo bar baz blah de;blah de blah.';

procedure P1;
var
  cp: PChar;
begin
  cp := PChar(SampleString);
  while not CharInSet(cp^, [#0, ';', '.']) do
    Inc(cp);
end;

procedure P2;
var
  cp: PChar;
begin
  cp := PChar(SampleString);
  while True do
    case cp^ of
      '.', #0, ';':
        Break;
    else
      Inc(cp);
    end;
end;

procedure P3;
var
  cp: PChar;
begin
  cp := PChar(SampleString);
  while not (AnsiChar(cp^) in [#0, ';', '.']) do
    Inc(cp);
end;

procedure Time(const Title: string; Proc: TProc);
var
  i: Integer;
  start, finish, freq: Int64;
begin
  QueryPerformanceCounter(start);
  for i := 1 to 1000000 do
    Proc;
  QueryPerformanceCounter(finish);
  QueryPerformanceFrequency(freq);
  Writeln(Format('%20s: %.3f seconds', [Title, (finish - start) / freq]));
end;

begin
  Time('CharInSet', P1);
  Time('case stmt', P2);
  Time('set test', P3);
end.

ここでの私のラップトップでの出力は次のとおりです。

CharInSet: 0.261 seconds
case stmt: 0.077 seconds
 set test: 0.060 seconds
于 2008-12-02T03:42:35.237 に答える
8

バリー、実装の構造が異なるため、ベンチマークはさまざまなメソッドの実際のパフォーマンスを反映していないことを指摘したいと思います。代わりに、すべてのメソッドで「while True do」構造を使用して、char-in-set チェックを実行するさまざまな方法の影響をより適切に反映する必要があります。

ここで、テストメソッドの置き換え (P2 は変更されず、P1 と P3 は "while True do" 構造を使用するようになりました):

procedure P1;
var
  cp: PChar;
begin
  cp := PChar(SampleString);
  while True do
    if CharInSet(cp^, [#0, ';', '.']) then
      Break
    else
      Inc(cp);
end;

procedure P2;
var
  cp: PChar;
begin
  cp := PChar(SampleString);
  while True do
    case cp^ of
      '.', #0, ';':
        Break;
    else
      Inc(cp);
    end;
end;

procedure P3;
var
  cp: PChar;
begin
  cp := PChar(SampleString);
  while True do
    if AnsiChar(cp^) in [#0, ';', '.'] then
      Break
    else
      Inc(cp);
end;

私のワークステーションは次のようになります:

CharInSet: 0.099 seconds
case stmt: 0.043 seconds
 set test: 0.043 seconds

どちらが期待される結果と一致しますか。私には、「ケースイン」構造を使用してもあまり役に立たないようです。ごめんねマルコ!

于 2008-12-02T09:55:53.877 に答える
3

Delphi 用の無料のサンプリング プロファイラは、次の場所にあります。

https://forums.codegear.com/thread.jspa?messageID=18506

計装プロファイラーの時間測定が正しくないという問題とは別に、どちらがより速いかは、「ケース」分岐の予測可能性にも依存することに注意してください。「ケース」のテストがすべて同様の確率で発生する場合、「ケース」のパフォーマンスは CharInSet のパフォーマンスよりも低くなる可能性があります。

于 2008-12-11T13:54:05.387 に答える
2

関数「CharInSet」のコードは「case」よりも高速で、「call」に時間がかかります。使用しない場合は(cp ^ in [..])

これが断食されていることがわかります。

于 2009-01-05T11:50:45.077 に答える
1

私が知っているように、両方が短いポインタを使用している場合、呼び出しはジャンプと同じ量のプロセッサ操作を必要とします。長いポインタでは異なる場合があります。アセンブラの呼び出しは、デフォルトではスタックを使用しません。十分な空きレジスタがある場合は、レジスタを使用します。したがって、スタック操作にも時間がかかりません。非常に高速なのは単なるレジスタです。

対照的に、私が見るように、バリアントは、非常に遅く、おそらくほとんどの余分な時間を追加する追加およびサブ操作を使用します。

于 2008-12-02T03:46:43.107 に答える