web-dev-qa-db-ja.com

Javaに異なるサイズの数値のプリミティブがあるのはなぜですか?

Javaには、byteshortintlongのプリミティブ型があり、floatdouble。プリミティブ値に使用するバイト数を人に設定する必要があるのはなぜですか?渡された数値の大きさによっては、サイズを動的に決定できなかっただけです?

私が考えることができる2つの理由があります:

  1. データのサイズを動的に設定するには、動的に変更できる必要があることを意味します。これはパフォーマンスの問題を引き起こす可能性がありますか?
  2. おそらく、プログラマーは誰かが特定のサイズよりも大きい数を使用できるようにしたくないので、これによりユーザーはそれを制限できます。

単一のintfloat型を使用するだけで多くのことが得られたと思いますが、特定の理由があった場合Javaこのルートに行きますか?

20
yitzih

言語設計の多くの側面と同様に、エレガンスとパフォーマンスのトレードオフになります(以前の言語からの歴史的な影響は言うまでもありません)。

代替案

単一のタイプの自然数natを持つプログラミング言語を作成することは確かに可能です(そして非常に単純です)。学術研究に使用されているほとんどすべてのプログラミング言語(PCF、システムFなど)はこの単一の数値型を持っています。これは、ご想像のとおり、よりエレガントなソリューションです。しかし、実際の言語設計は優雅さだけではありません。パフォーマンスも考慮する必要があります(パフォーマンスがどの程度考慮されるかは、言語の使用目的によって異なります)。パフォーマンスには、時間とスペースの両方の制約が含まれます。

スペースの制約

プログラマが事前にバイト数を選択できるようにすると、メモリに制約のあるプログラムのスペースを節約できます。すべての数値が256未満になる場合は、bytesの8倍のlongsを使用するか、保存されたストレージをより複雑なオブジェクトに使用できます。標準のJavaアプリケーション開発者は、これらの制約について心配する必要はありませんが、実際に発生します。

効率

スペースを無視しても、CPUの制約を受けます。CPUには、固定バイト数(64ビットアーキテクチャでは8バイト)で動作する命令しかありません。つまり、単一の8バイトのlong型を指定しても、算術演算をsingleに直接マッピングできるため、無制限の自然数型よりも言語の実装が大幅に簡素化されます。 =基になるCPU命令。プログラマが任意の数を使用できるようにする場合は、単一の算術演算をsequenceの複雑な機械語命令にマッピングする必要があり、プログラムの速度が低下します。これがあなたが育てたポイント(1)です。

浮動小数点型

これまでの議論は整数にのみ関係してきました。浮動小数点型は、非常に微妙なセマンティクスとエッジケースを持つ複雑な獣です。したがって、intlongshort、およびbyteを単一のnatタイプに簡単に置き換えることができたとしても、 isであっても、浮動小数点数の型を明確にします。実数はプログラミング言語には存在できないため、これらは実数ではありません。これらもまた、かなり有理数ではありません(必要に応じて有理型を作成するのは簡単ですが)。基本的に、IEEEは、実数をある程度近似する方法を決定しました。それ以来、すべての言語(およびプログラマー)は、それらに悩まされてきました。

最後に:

おそらく、プログラマーは誰かが特定のサイズよりも大きい数を使用できるようにしたくないので、これによりユーザーはそれを制限できます。

これは正当な理由ではありません。まず、型が数値の境界を自然にエンコードできる状況は考えられません。言うまでもなく、プログラマーが強制したい境界がプリミティブ型のサイズに正確に対応する可能性は天文学的に低いです。

16
gardenhead

その理由は非常に簡単です:効率。複数の方法で。

  1. ネイティブデータタイプ:言語のデータタイプがハードウェアの基本的なデータタイプと一致するほど、言語の効率は高くなります。 (プログラムが必ずしも効率的であるという意味ではありませんが、実際に何をしているのかがわかっていれば、ハードウェアが実行できるのと同じくらい効率的に実行されるコードを書くことができるという意味です。)提供されるデータ型Javaは、世の中で最も人気のあるハードウェアのバイト、ワード、ダブルワード、クワッドワードに対応しています。これが最も効率的な方法です。

  2. 2ビットシステムでの不当なオーバーヘッド:すべてを64ビット長の固定サイズにマップすることを決定した場合、これはかなり多くを必要とする32ビットアーキテクチャに大きなペナルティを課すことになります。 32ビット動作よりも64ビット動作を実行するためのクロックサイクル。

  3. メモリの浪費:そこには、メモリの調整にあまり注意を払っていないハードウェアがたくさんあります(Intel x86およびx64アーキテクチャがその例です)。そのため、そのハードウェア上の100バイトの配列は、 100バイトのメモリのみを使用します。ただし、バイトがなく、代わりにlongを使用する必要がある場合は、同じ配列が1桁多いメモリを占有します。そして、バイト配列は非常に一般的です。

  4. 数値のサイズの計算:渡された数値の大きさに応じて整数のサイズを動的に決定するというあなたの考えは単純すぎます。数字を「渡す」という単一のポイントはありません。実行時に、より大きなサイズの結果が必要になる可能性があるすべての操作で、数値の大きさの計算を実行する必要があります。数値をインクリメントするたびに、2つの数値を追加するたびに、2を乗算するたびに番号など.

  5. 異なるサイズの数の操作:その後、潜在的に異なるサイズの数がメモリ内に浮かぶと、複雑になりますall演算:2つの数値を単純に比較する場合でも、ランタイムは最初に、比較する両方の数値が同じサイズかどうかを確認し、そうでない場合は、大きい方のサイズに合わせて小さい方のサイズを変更する必要があります。

  6. 特定のオペランドサイズを必要とする演算:特定のビット単位演算は、特定のサイズの整数に依存しています。事前に決められた特定のサイズがないため、これらの操作をエミュレートする必要があります。

  7. ポリモーフィズムのオーバーヘッド:実行時に数値のサイズを変更するということは、本質的にポリモーフィックでなければならないことを意味します。これは、スタックに割り当てられた固定サイズのプリミティブになることはできず、ヒープに割り当てられたオブジェクトでなければならないことを意味します。それはひどく非効率的です。 (上記の#1をもう一度読んでください。)

9
Mike Nakis

他の回答で説明されている点が繰り返されないように、代わりに複数の視点の概要を説明します。

言語設計の観点から

  • マシンの幅に収まらない整数演算の結果に自動的に対応するプログラミング言語とその実行環境を設計して実装することは確かに可能です。
  • このような動的な幅の整数をこの言語のデフォルトの整数型にするかどうかは、言語設計者の選択です。
  • ただし、言語設計者は次の欠点を考慮する必要があります。
    • CPUはより多くのコードを実行する必要があり、より多くの時間がかかります。ただし、整数が1台のマシンのWordに収まる最も頻繁なケースを最適化することは可能です。 タグ付きポインタ表現 を参照してください。
    • その整数のサイズは動的になります。
    • メモリから動的な幅の整数を読み取るには、複数のトリップが必要になる場合があります。
    • フィールド/要素内に動的な幅の整数を含む構造体(オブジェクト)と配列も、動的な合計(占有)サイズになります。

歴史的な理由

これは、Javaの歴史に関するWikipediaの記事ですでに説明されており、 Marco13の回答 でも簡単に説明されています。

私はそれを指摘します:

  • 言語デザイナーは、美的と実用的な考え方を両立させなければなりません。美的思考は、整数オーバーフローなどのよく知られた問題を起こしにくい言語を設計することを望んでいます。実用的な考え方は、プログラミング言語は、有用なソフトウェアアプリケーションを実装し、異なる言語で実装されている他のソフトウェアパーツと相互運用するのに十分である必要があることを設計者に思い出させます。
  • 古いプログラミング言語から市場シェアを獲得しようとするプログラミング言語は、実用的である傾向があります。考えられる結果の1つは、それらの古い言語から既存のプログラミング構成とスタイルを取り入れたり借用したりすることをいとわないことです。

効率の理由

効率が問題になるのはいつですか?

  • プログラミング言語を大規模アプリケーションの開発に適していると宣伝する場合。
  • 何百万、何十億もの小さなアイテムで作業する必要があるとき、あらゆる効率が追加されます。
  • 他のプログラミング言語と競合する必要がある場合、その言語はきちんと実行する必要があります。それは最高である必要はありませんが、最高のパフォーマンスに近い状態を維持するのに役立ちます。

ストレージの効率(メモリ内またはディスク上)

  • コンピュータのメモリはかつては乏しい資源でした。昔は、コンピュータで処理できるアプリケーションデータのサイズは、コンピュータのメモリ量によって制限されていましたが、賢いプログラミングを使用して回避することは間違いありません(実装にコストがかかります)。

実行効率(CPU内、またはCPUとメモリ間)

  • すでに 庭師の答え で議論されています。
  • 連続して格納された小さな数値の非常に大きな配列をプログラムで処理する必要がある場合、大量のデータがCPUとメモリ間のスループットをボトルネックにするため、メモリ内表現の効率はその実行パフォーマンスに直接影響します。この場合、データをより密にパックするとは、単一のキャッシュラインフェッチでより多くのデータを取得できることを意味します。
  • ただし、データが連続して保存または処理されない場合、この推論は適用されません。

特定のコンテキストに限定されている場合でも、小さな整数の抽象化を提供するプログラミング言語の必要性

  • これらのニーズは、言語独自の標準ライブラリを含むソフトウェアライブラリの開発でよく発生します。以下にそのようなケースをいくつか示します。

相互運用性

  • 多くの場合、高水準プログラミング言語は、オペレーティングシステム、または他の低水準言語で記述されたソフトウェア(ライブラリ)と対話する必要があります。これらの低レベル言語は、さまざまなタイプのフィールドで構成されるレコードのメモリレイアウトの厳密な仕様である "structs" を使用して通信することがよくあります。
  • たとえば、高水準言語では、特定の外部関数がサイズ256のchar配列を受け入れるように指定する必要がある場合があります。 (Example。)
  • オペレーティングシステムやファイルシステムで使用される一部の抽象化では、バイトストリームを使用する必要があります。
  • 一部のプログラミング言語は、狭い整数をビットストリームとバイトストリームにパックおよびアンパックするのに役立つユーティリティ関数(BitConverterなど)を提供することを選択します。
  • これらの場合、より狭い整数型は、言語に組み込まれたプリミティブ型である必要はありません。代わりに、ライブラリタイプとして提供できます。

文字列処理

  • 文字列を操作することを主な設計目的とするアプリケーションがあります。したがって、文字列処理の効率は、これらのタイプのアプリケーションにとって重要です。

ファイル形式の処理

  • 多くのファイル形式は、Cのような考え方で設計されています。そのため、幅の狭いフィールドの使用が一般的でした。

望ましさ、ソフトウェア品質、およびプログラマーの責任

  • 多くのタイプのアプリケーションでは、整数の自動拡張は実際には望ましい機能ではありません。飽和もラップアラウンド(係数)もありません。
  • 多くのタイプのアプリケーションは、APIレベルなどのソフトウェアのさまざまな重要なポイントで最大の許容値をプログラマーが明示的に指定することで恩恵を受けます。

次のシナリオを検討してください。

  • ソフトウェアAPIはJSONリクエストを受け入れます。リクエストには、子リクエストの配列が含まれています。 JSONリクエスト全体は、Deflateアルゴリズムで圧縮できます。
  • 悪意のあるユーザーが10億の子リクエストを含むJSONリクエストを作成します。すべての子リクエストは同一です。悪意のあるユーザーは、システムが無駄な作業を行っているCPUサイクルを燃やすことを意図しています。圧縮により、これらの同一の子リクエストは非常に小さな合計サイズに圧縮されます。
  • データの圧縮サイズに対する事前定義の制限では不十分であることは明らかです。代わりに、APIは、APIに含めることができる子リクエストの数に事前定義された制限、および/またはデータのデフレートされたサイズに事前定義された制限を課す必要があります。

多くの場合、何桁も安全にスケールアップできるソフトウェアは、その目的のために設計されなければならず、複雑さが増しています。整数オーバーフローの問題が解消されても、自動的には提供されません。これは、言語設計の観点に答える完全な円になります。多くの場合、意図しない整数のオーバーフローが発生したときに(エラーまたは例外をスローすることによって)作業の実行を拒否するソフトウェアは、天文学的に大きな演算に自動的に準拠するソフトウェアよりも優れています。

これはOPの視点を意味し、

プリミティブ値に使用するバイト数を設定する必要があるのはなぜですか?

不正解です。プログラマーは、整数値が可能な最大値magnitudeを指定することを許可され、必要になる場合もあります。ソフトウェアの重要な部分を取り上げます。 庭先の答え が指摘しているように、プリミティブ型によって課される自然な制限はこの目的には役立ちません。言語は、プログラマーに大きさを宣言し、そのような制限を強制する方法を提供する必要があります。

6
rwong

それはすべてハードウェアから来ています。

1バイトは、ほとんどのハードウェアでアドレス可能な最小のメモリ単位です。

今述べたすべての型は、複数のバイトから構築されています。

1バイトは8ビットです。これにより、8つのブール値を表現できますが、一度に1つだけを調べることはできません。あなたは1に対応し、すべて8に対応します。

以前はそれほど単純でしたが、8ビットバスから16、32、そして現在は64ビットバスに移行しました。

つまり、バイトレベルでアドレス指定できる間は、隣接するバイトを取得せずにメモリから1バイトを取得することはできません。

このハードウェアに直面すると、言語設計者は、ハードウェアに適合するタイプを選択できるタイプを選択できるようにすることを選択しました。

そのような詳細は、特に任意のハードウェアでの実行を目的とする言語では抽象化でき、抽象化する必要があると主張できます。これにはパフォーマンスの問題が隠されていますが、それは正しいかもしれません。それはそのようには起こらなかっただけです。

Javaは実際にこれを試みます。バイトは自動的にIntに昇格されます。あなたがその中で深刻なビットシフト作業を最初にしようとするときにあなたを狂わせるという事実。

では、なぜうまくいかなかったのでしょうか。

Javaの大きなセールスポイントは、よく知られている優れたCアルゴリズムに腰を下ろし、Javaでタイプアップし、小さな調整を加えるだけで機能するということです。 Cはハードウェアに非常に近いです。

それを維持し、整数型からサイズを抽象化しても、一緒に機能しませんでした。

だから彼らは持つことができた。彼らはちょうどしませんでした。

おそらく、プログラマーは誰かが特定のサイズよりも大きい数を使用できるようにしたくないので、これによりユーザーはそれを制限できます。

これは正当な考え方です。これを行う方法があります。 クランプ機能 1。言語は、それらの型に任意の境界を焼き付けることもできます。そして、それらの境界がコンパイル時にわかっている場合、それらの数値の格納方法を最適化することができます。

Javaはその言語ではありません。

2
candied_orange

おそらく、これらの型がJavaに存在する重要な理由の1つは、単純であり、技術的ではないことです。

CおよびC++にもこれらのタイプがありました!

これが理由であるという証拠を提供することは困難ですが、少なくともいくつかの強力な証拠があります。 Oak Language 仕様(バージョン0.2)には次の文章が含まれています。

.1整数型

Oak言語の整数はCとC++の整数に似ていますが、2つの例外があります。すべての整数型はマシンに依存せず、Cが導入されてからの世界の変化を反映するように従来の定義の一部が変更されました。 4つの整数型の幅は8、16、32、および64ビットであり、unsigned修飾子が前に付いていない限り、署名されます。

したがって、問題は次のように要約できます。

Cで発明されたのはなぜshort、int、longなのか?

ここで尋ねられた質問に関して、手紙の質問への回答が満足できるものかどうかはわかりません。しかし、ここでの他の回答と組み合わせると、(Javaでの存在がC/C++からのレガシーだけであるかどうかに関係なく)これらのタイプを持つことが有益であることが明らかになる場合があります。

私が考えることができる最も重要な理由は

  • バイトは、アドレス指定可能な最小のメモリユニットです(CandiedOrangeについては既に説明しています)。 byteは、ファイルから、またはネットワーク経由で読み取ることができるデータの基本的なビルディングブロックです。 いくつかこれの明示的な表現が存在する必要があります(ほとんどの言語では、偽装されている場合でも存在します)。

  • 実際には、すべてのフィールドとローカル変数を単一の型を使用して表現し、この型をintと呼ぶことは理にかなっています。これについては、stackoverflowに関連する質問があります: Java AP​​Iがshortまたはbyteではなくintを使用するのはなぜですか? 。そこの私の回答で述べたように、より小さな型(byteおよびshort)を持つ理由の1つは、これらの型の arrays を作成できることです。 :Javaには、まだ「ハードウェアに近い」配列の表現があります。他の言語(およびInteger[n]配列などのオブジェクトの配列とは対照的)とは対照的に、int[n]配列は、値がヒープ全体に分散している参照のコレクションではありません。代わりにwillは実際にはn*4バイトの連続したブロック-既知のサイズとデータレイアウトを持つ1つのメモリチャンクです。 1000バイトを任意のサイズの整数値オブジェクトのコレクション、またはbyte[1000](1000バイトを必要とする)に格納することを選択した場合、後者は実際にメモリを節約する可能性があります。 (これの他のいくつかの利点はより微妙である可能性があり、ネイティブライブラリとJavaをインターフェイスするときにのみ明らかになります)


具体的にお伺いした点について:

渡された数値の大きさに応じて、サイズを動的に決定することはできませんか?

データのサイズを動的に設定するには、動的に変更できる必要があることを意味します。これはパフォーマンスの問題を引き起こす可能性がありますか?

まったく新しいプログラミング言語をゼロから設計することを検討した場合、変数のサイズを動的に設定することはおそらく可能です。私はコンパイラー構築の専門家ではありませんが、特にstrongly型付き言語がある場合、動的に変化する型のコレクションを賢明にマンガ化するのは難しいと思います。したがって、おそらく「汎用の任意精度の数値データ型」に格納されているすべての数値に要約されますが、これは確かにパフォーマンスに影響を与えます。もちろん、強く型付けされたプログラミング言語や任意のサイズの数値型を提供するプログラミング言語はありますが、このように進んだ実際の汎用プログラミング言語はないと思います。


サイドノート:

  • Oak仕様で言及されているunsigned修飾子について疑問に思ったかもしれません。事実、 "unsignedはまだ実装されていないため、実装されていない可能性もあります。" 。そして彼らは正しかった。

  • C/C++がこれらの異なる整数型を使用した理由に加えて、intのビット数がわからないほどひどく混乱した理由に疑問を感じるかもしれません。この理由は通常、パフォーマンスに関連しており、他の場所で調べることができます。

1
Marco13

いくつかの理由があります

(1)1バイト変数と1ロングの格納は重要ではありませんが、配列内の数百万の格納は非常に重要です。

(2)特定の整数サイズに基づく「ハードウェアネイティブ」演算は、はるかに効率的であり、一部のプラットフォームの一部のアルゴリズムでは、それが重要になる場合があります。

0
ddyer

確かに、パフォーマンスとアーキテクチャについてまだ教えていないことを示しています。

  • まず、すべてのプロセッサが大きな型を処理できるわけではないので、制限を理解し、それに取り組む必要があります。
  • 第2に、型が小さいほど、操作時のパフォーマンスが向上します。
  • また、サイズが重要であり、ファイルまたはデータベースにデータを格納する必要がある場合、サイズはすべてのデータのパフォーマンスと最終的なサイズの両方に影響します。たとえば、15列のテーブルがあり、最終的にいくつかのサイズになるとします。何百万ものレコード。各列に必要な小さいサイズを選択するか、最大のタイプを選択するだけの違いは、データの可能なギグと操作のパフォーマンスの時間の違いになります。
  • また、たとえばゲームのように、処理されるデータのサイズが大きな影響を与える複雑な計算にも適用されます。

データサイズの重要性を無視すると、常にパフォーマンスに影響します。必要なだけ多くのリソースを使用する必要がありますが、それ以上は常に使用する必要はありません。

これは、本当に単純なことを行うプログラムやシステムと、多くのリソースを必要とする非常に非効率的であり、そのシステムの使用を非常にコストがかかるものとの違いです。または、多くのことを実行しますが、他のシステムよりも速く実行され、実行するのに本当に安価なシステムです。