4
public Int64 ReturnDifferenceA()
{
  User[] arrayList;
  Int64 firstTicks;
  IList<User> userList;
  Int64 secondTicks;
  System.Diagnostics.Stopwatch watch;

  userList = Enumerable
              .Range(0, 1000)
              .Select(currentItem => new User()).ToList();

  arrayList = userList.ToArray();

  watch = new Stopwatch();
  watch.Start();

  for (Int32 loopCounter = 0; loopCounter < arrayList.Count(); loopCounter++)
  {
     DoThings(arrayList[loopCounter]);
  }

  watch.Stop();
  firstTicks = watch.ElapsedTicks;

  watch.Reset();
  watch.Start();
  for (Int32 loopCounter = 0; loopCounter < arrayList.Count(); loopCounter++)
  {
     DoThings(arrayList[loopCounter]);
  }
  watch.Stop();
  secondTicks = watch.ElapsedTicks;

  return firstTicks - secondTicks;
}

ご覧のとおり、これは非常に簡単です。ユーザーのリストを作成し、配列に強制し、ウォッチを開始し、リストをループしてメソッドを呼び出し、ウォッチを停止します。繰り返す。1回目と2回目の差額を返して終了。

今、私はこれらで呼び出しています:

differenceList = Enumerable
                 .Range(0, 50)
                 .Select(currentItem => ReturnDifferenceA()).ToList();
average = differenceList.Average();

differenceListA = Enumerable
                  .Range(0, 50)
                  .Select(currentItem => ReturnDifferenceA()).ToList();
averageA = differenceListA.Average();

differenceListB = Enumerable
                  .Range(0, 50)
                  .Select(currentItem => ReturnDifferenceA()).ToList();
averageB = differenceListB.Average();

ここで面白いのは、すべての平均が 150k から 300k ティックの範囲で比較的大きくプラスになっていることです。

私が得られないのは、同じリストを同じ方法で同じ方法で調べているにもかかわらず、そのような違いがあるということです。ある種のキャッシングが行われていますか?

もう 1 つの興味深い点は、最初のストップ ウォッチ セクションの前にリストを反復すると、平均が約 5k 程度になることです。

4

7 に答える 7

5

多くのキャッシングとパフォーマンスの最適化を行うランタイム環境を備えた高水準言語で実行しています。これは一般的です。仮想マシンのウォームアップまたはサーバーのウォームアップ (本番アプリケーションの場合) と呼ばれることもあります。

何かを繰り返し実行する場合、最初に測定された実行時間が長くなり、残りはより少ない量で横ばいになることがよくあります。

これを MATLAB コードで実行すると、初めてベンチマーク ループを実行するときに 5 秒かかり、その後は 1/5 秒かかることがわかります。これは大きな違いです。何らかの形式のコンパイルが必要なインタープリター型言語であるためです。ただし、実際にはパフォーマンスに影響はありません。大部分は、どの製品アプリケーションでも「2 回目」になるからです。

于 2008-11-19T21:03:31.330 に答える
4

ちなみに、Array で IEnumerable.Count() を使用すると、Array.Length よりも何百倍も遅くなります...ただし、これは質問にはまったく答えません。

于 2008-11-19T21:07:18.157 に答える
3

DoThings() が最初に呼び出されるまでネイティブ コードに JIT コンパイルされない可能性は十分にあります。

于 2008-11-19T21:04:42.810 に答える
1

.NET は Java プラットフォームと同様に JIT 環境であるためです。高レベルの .NET コードはすべて、Microsoft の中間言語バイトコードにコンパイルされます。

プログラムを実行するには、このバイトコードをネイティブ マシン コードにコンパイル/変換する必要があります。ただし、コンパイルされた .NET プログラム ファイルは、ネイティブ マシン コードではなく、中間の仮想マシン バイトコードに格納されます。

最初の実行は JIT コンパイルされるため、余分な時間がかかりました。後続の実行は JIT コンパイルする必要がなくなりますが、ネイティブ コードは JIT キャッシュから取得されるため、高速になるはずです。

後続の実行で終了せずにアプリケーションを維持しましたか? 次に、2 つ目の理由も VM によるものです。(VM:1 = 仮想マシン、VM:2 = 仮想メモリ)。最新の一般化されたオペレーティング システムはすべて、実メモリのマップである仮想メモリ上でプロセスを実行し、オペレーティング システムがシステム リソースの使用を管理および最適化できるようにします。使用頻度の低いプロセスは頻繁にディスク キャッシュに移動され、他のプロセスがリソースを最適に使用できるようになります。

プロセスは最初は仮想メモリになかったので、メモリにスイープされるというオーバーヘッドが発生する必要があります。その後、あなたのプロセスは最近使用されたトップリスト (最も最近使用されていないリストの一番下) にあったため、まだディスクキャッシュに取り込まれませんでした。

また、リソースは必要に応じて OS によってプロセスに分配されます。そのため、最初のラウンドでは、プロセスは OS とのエンベロープ競合を押し進めてリソース境界を拡張するという苦労を経験しなければなりませんでした。

仮想マシンを使用すると、.NET と Java はほとんどのプログラミング機能をマシンに依存しないレイヤーに抽象化して分離し、マシンに依存するソフトウェア エンジニアが対処できる混乱を小さくすることができます。Microsoft Windows はかなり統合された x86 派生ハードウェアで実行されますが、.NET プログラマーとユーザーに一貫したビューを提供するために抽象化された仮想マシンを保証するために、さまざまな OS バージョンと CPU モデルに十分な違いがあります。

于 2009-08-06T22:30:32.120 に答える
0

あなたはそれを 3 回やってもわからないと言いますが、2 回目と 3 回目は比較的近いです。物事が遅いのは、ループを通過するのは初めてのようです。

于 2008-11-19T21:40:27.810 に答える
0

あなたが呼び出す関数は、最初の実行までジャストインタイムではないと思います。試すことができるのは、一度実行してから停止して、もう一度実行することです。コードを変更しなくても、前回の実行からの Just-In-Time コンパイルは問題なく実行され、表示される残りの最適化は実際のキャッシュ効果です。

于 2008-11-19T21:54:23.520 に答える