ともだち
最初のプログラマーは、「0」や「1」などの概念を操作しましたが、これは長くてわかりにくいものでした。 ゼロと1の助けを借りて、怠inessは、MOV、ADD、JMPをプロセッサが理解できる新しいゼロと1に変換するプログラムを書くことを提案しました。
将来、怠inessはうとうとせず、6〜10行のアセンブラではなく1つの高レベルコマンドを呼び出すために呼び出されました。 その後、さらに怠zyなものが来ました。 OOP、データベースなど。 プログラムはより理解しやすくなり、さまざまなフレームワークのおかげでエラーの検索が時々減り、開発速度が向上しました。 高レベルの抽象化が惑星全体を行進しました。 みんな幸せでしたか?
敵
しかし、敵は居眠りしませんでした! ある時点で、プログラマーはこのレベルの抽象化では解決できない問題に直面しますが、この問題から彼の足がどこで成長するかさえ理解します。 何が起こっているのかを理解するには、ゼロと1のジャングルに登らなければなりません。 例? はい、お願いします(すべての例はC#で提供されています)。
例1
static void Main( string [] args)
{
double tenth = 0.1;
double first = 0;
for ( int i = 0; i < 10; i++)
{
first += tenth;
}
double eighth = 0.125;
double second = 0;
for ( int i = 0; i < 8; i++)
{
second += eighth;
}
Console .WriteLine(first == second);
Console .WriteLine( "0.1 * 10 = {0}\n0.125 * 8 = {1}" , first, second);
Console .ReadKey();
}
画面に表示される内容を推測してみてください。
試しましたか? そして、あなたは何をしましたか? だから、提案した人、まあ、あまりに怠け者でこの場所を読んだ人のために、私は知っています、私たちは見るでしょう-偽。
値が一致することに注意してください? 原則として、10を100に、8を80に置き換えると、値が異なることがわかりますが、ここではO_o
ここでは、ゼロと1のレベルです。 原則として、浮動小数点数がどのように表現されるかを理解すると、1/8は2から-3度に過ぎず、エラーなしで加算されますが、0.1は現代のdoubleの容量の2の整数累乗の合計として表すことはできません。 ここでエラーが発生します。
続行しますか?
例2
static void Main( string [] args)
{
int n = 10000;
int [,] array1 = new int [n, n];
int [,] array2 = new int [n, n];
for ( int i = 0; i < n; i++)
{
for ( int j = 0; j < n; j++)
{
array1[i, j] = 0;
array2[i, j] = 0;
}
}
DateTime begin = DateTime.Now;
for ( int i = 0; i < n; i++)
{
for ( int j = 0; j < n; j++)
{
array1[j, i]++;
}
}
Console .WriteLine( " [j, i] = {0}" , DateTime.Now.Subtract(begin).TotalSeconds);
begin = DateTime.Now;
for ( int i = 0; i < n; i++)
{
for ( int j = 0; j < n; j++)
{
array1[i, j]++;
}
}
Console .WriteLine( " [i, j] = {0}" , DateTime.Now.Subtract(begin).TotalSeconds);
Console .ReadKey();
}
最も驚くべきことは、配列の要素にアクセスするときにインデックスの順序を変更すると、動作速度がほぼ2倍変化することです(私のコンピューターでは、最初のサイクルは2.16秒で、2番目は1.13秒で動作しました)。 nが11000に増加すると、差はすでに5倍になります。
違いはどこですか? 再びゼロと1のレベルから。 この場合、キャッシュの操作が明示され、メモリ内の2次元配列が実際には1次元であるという事実が明らかになります。
例3
ジェネリック型の出現により、この例は関係ないかもしれませんが、それでも抵抗することはできません。
class MyPack
{
public int Value { get ; set ; }
}
static void Main( string [] args)
{
int n = 100000;
object [] array1 = new object [n];
object [] array2 = new object [n];
for ( int i = 0; i < n; i++)
{
array1[i] = 0;
array2[i] = new MyPack () { Value = 0 };
}
DateTime begin = DateTime .Now;
for ( int i = 0; i < n; i++)
{
array1[i] = ( int )array1[i] + 1;
}
Console .WriteLine( " c int = {0}" , DateTime .Now.Subtract(begin).TotalSeconds);
begin = DateTime .Now;
for ( int i = 0; i < n; i++)
{
(( MyPack )array2[i]).Value = (( MyPack )array2[i]).Value + 1;
}
Console .WriteLine( " MyPack = {0}" , DateTime .Now.Subtract(begin).TotalSeconds);
Console .ReadKey();
}
最初のケースでは、実行時間は0.017秒で、2番目のケースでは0.005秒でした。 ここでは、それほど低レベルなものは有効になりませんが、単純型から型オブジェクトへの変換の特性(興味のある人は誰でもパッケージとアンパックについて読むことができます)。
結論
そもそも、高レベルの抽象化は悪とは考えていません。 記事のタイトルは、お気づきのように、高レベルの抽象化は私たちの友人であり、それが敵にならないように、新しいIntelプロセッサアーキテクチャについて説明します。
PS執筆の時点で、私は廊下に出て、そこで同僚に出会いました。 そのため、彼は、Windows Mobile用のアプリケーションを開発するときに、PDAがスリープ状態になるまでアプリケーションが適切に動作するという問題を思い付きました。 アプリケーションを通常モードにすると、メモリもエラーメッセージもなくなります。 あなたはどう思いますか? 高レベルの抽象化に穴があるためだと思います。