お気に入りのプログラミング言語で1〜2,000,000のすべての数値を合計しようとしたことがありますか?結果は手動で簡単に計算できます。2,000,001,000,000は、符号なし32ビット整数の最大値の約900倍です。
C#が出力される-1453759936
-負の値!そして、Javaも同じことをしていると思います。
つまり、デフォルトでは算術オーバーフローを無視する一般的なプログラミング言語がいくつかあります(C#には、それを変更するための非表示オプションがあります)。それは私には非常に危険に見える動作であり、Ariane 5のクラッシュはそのようなオーバーフローによって引き起こされたのではありませんか?
では、このような危険な動作の背後にある設計上の決定は何ですか?
編集:
この質問に対する最初の回答は、チェックの過剰なコストを表しています。短いC#プログラムを実行して、この仮定をテストしましょう。
Stopwatch watch = Stopwatch.StartNew();
checked
{
for (int i = 0; i < 200000; i++)
{
int sum = 0;
for (int j = 1; j < 50000; j++)
{
sum += j;
}
}
}
watch.Stop();
Console.WriteLine(watch.Elapsed.TotalMilliseconds);
私のマシンでは、チェックされていないバージョンは4125msかかりますが、チェックされたバージョンは11015msかかります。つまり、チェック手順は、数値を追加するのにほぼ2倍の時間がかかります(元の時間の合計3倍)。しかし、10,000,000,000回の繰り返しで、チェックにかかる時間はまだ1ナノ秒未満です。それが重要な状況があるかもしれませんが、ほとんどのアプリケーションでは、それは重要ではありません。
編集2:
サーバーアプリケーション(いくつかのセンサーから受信したデータを分析するWindowsサービス)を/p:CheckForOverflowUnderflow="false"
パラメーターで再コンパイルし(通常、オーバーフローチェックをオンにします)、デバイスに展開しました。Nagiosモニタリングは、平均CPU負荷が17%に留まったことを示しています。
これは、上記の構成例で見つかったパフォーマンスヒットは、アプリケーションにとってまったく無関係であることを意味します。
(1..2_000_000).sum #=> 2000001000000
。私のお気に入りの言語のもう1つ:sum [1 .. 2000000] --=> 2000001000000
。私のお気に入りではありませんArray.from({length: 2000001}, (v, k) => k).reduce((acc, el) => acc + el) //=> 2000001000000
。(公平を期すために、最後のものは不正行為です。)
Integer
Haskellの@BernhardHiller は任意精度であり、割り当て可能なRAMが不足しない限り、任意の数値を保持します。
But with the 10,000,000,000 repetitions, the time taken by a check is still less than 1 nanosecond.
これは、ループが最適化されていないことを示しています。また、その文は、私にとって非常に有効な以前の数字と矛盾しています。
checked { }
セクションを使用して、算術オーバーフローチェックを実行する必要があるコードの部分をマークできます。これはパフォーマンスによるものです