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


20

Javaではプリミティブ型がありbyteshortintlongとのために同じものfloatdouble。プリミティブ値に使用するバイト数を設定する必要があるのはなぜですか?渡される数値の大きさに応じて、サイズを動的に決定することはできませんでしたか?

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

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

私はまだ、シングルintfloatタイプを使用するシンプルな方法で多くのことが得られたと思いますが、Javaがこの方法を採用しないことに決めた特別な理由はありましたか?


4
投票者には、この質問は、コンパイラの研究者が回答を求めている質問に関連していると付け加えます
rwong

数値に追加した場合、タイプを動的に変更する必要があると思いますか?タイプを変更したいですか?数値がintUnknown alpha = a + bとして初期化されている場合。コンパイラでは少し難しいと思いますか?なぜこれがJavaに固有なのですか?
パパラッチ

@Paparazzi既存のプログラミング言語と実行環境(コンパイラ、インタプリタなど)があり、実際の値の大きさに基づいて動的な幅の整数を格納します(加算操作の結果など)。結果は次のとおりです。CPUで実行されるコードがより複雑になります。その整数のサイズは動的になります。メモリから動的な幅の整数を読み取るには、複数のトリップが必要になる場合があります。フィールド/要素内に動的な幅の整数を含む構造体(オブジェクト)および配列も、動的なサイズを持つ場合があります。
rwong

1
@tofroわかりません。10進数、2進数など、好きな形式で番号を送信するだけです。シリアル化は完全に直交する問題です。
ガーデンヘッド

1
@gardenhead確かに直交していますが、Javaで記述されたサーバーとCで記述されたクライアントの間で通信する場合を考えてみてください。もちろん、これ専用インフラストラクチャで解決できます。例えばdevelopers.google.com/protocol-buffersのようなものがあります。しかし、これは、ネットワークを介して整数を転送するという小さな目的のための大きなハンマーです。(これはここでは強力な議論ではありませんが、おそらく考慮すべき点です-詳細を議論することはコメントの範囲を超えています)。
マルコ13

回答:


16

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

代替案

たった1種類の自然数を持つプログラミング言語を作成することは確かに可能です(そして非常に簡単です)nat。学術研究に使用されるほとんどすべてのプログラミング言語(PCF、System Fなど)には、この単一の数値型があります。しかし、実際の言語設計は優雅さだけではありません。また、パフォーマンスを考慮する必要があります(パフォーマンスが考慮される範囲は、言語の意図するアプリケーションによって異なります)。パフォーマンスには、時間とスペースの両方の制約が含まれます。

スペースの制約

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

効率

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

浮動小数点型

これまでの説明では、整数のみを対象としていました。浮動小数点型は複雑な獣であり、非常に微妙なセマンティクスとエッジケースを備えています。したがって、我々は簡単に置き換えることができるにもかかわらずintlongshort、およびbyteシングルとnatタイプは、浮動小数点数の種類も何明確ではないです。プログラミング言語では実数は存在できないため、明らかに実数ではありません。合理的な数値でもありません(必要に応じて合理的なタイプを作成するのは簡単ですが)。基本的に、IEEEは実数を近似的に並べる方法を決定し、それ以来、すべての言語(およびプログラマー)がそれらに固執しています。

最後に:

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

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


2
私たちはフロートを持っているという事実に本当の鍵は、我々は彼らのための専用ハードウェアがあるということです
JKを。

また、型の数値境界のエンコードは、依存型の言語では絶対に行われますが、他の言語ではあまり行われません

3
列挙型は整数と同等ではありません。列挙型は、合計タイプの使用モードにすぎません。一部の言語が列挙型を整数として透過的にエンコードするという事実は、悪用可能な機能ではなく、言語の欠陥です。
ガーデンヘッド

1
私はエイダに精通していません。例えば、整数を任意の型に制限できtype my_type = int (7, 2343)ますか?
ガーデンヘッド

1
うん。構文は次のようになります。type my_type is range 7..2343
Devsman

9

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

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

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

  3. メモリーの浪費:メモリーのアライメントをあまり気にしないハードウェアがたくさんあります(Intel x86およびx64アーキテクチャーがその例です)。そのハードウェア上の100バイトの配列は、100バイトのメモリーしか占有できません。ただし、もうバイトがなく、代わりにlongを使用する必要がある場合、同じ配列が1桁以上のメモリを占有します。また、バイト配列は非常に一般的です。

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

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

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

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


6

他の回答で説明したポイントを繰り返さないように、代わりに複数の視点の概要を説明します。

言語設計の観点から

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

歴史的理由

これは、Javaの歴史に関するウィキペディアの記事ですでに説明されており、Marco13の回答でも簡単に説明されています

私はそれを指摘するだろう:

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

効率性の理由

効率はいつ重要ですか?

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

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

  • コンピューターのメモリはかつては貴重なリソースでした。当時は、コンピューターで処理できるアプリケーションデータのサイズはコンピューターのメモリ量によって制限されていましたが、巧妙なプログラミング(実装するにはさらにコストがかかる)を使用して回避できる可能性があります。

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

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

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

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

相互運用性

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

ストリング処理

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

ファイル形式の処理

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

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

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

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

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

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

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

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

正しくありません。プログラマーは、ソフトウェアの重要な部分で、整数値がとることができる最大の大きさを指定することを許可される必要があります。以下のようgardenheadの答えは指摘し、プリミティブ型によって課された自然の限界は、この目的のために有用ではありません。この言語は、プログラマが規模を宣言し、そのような制限を実施する方法を提供する必要があります。


2

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

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

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

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

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

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

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

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

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

では、なぜうまく機能しなかったのでしょうか?

当時のJavaの大きなセールスポイントは、既知の優れたCアルゴリズムを使用して座ってJavaで入力し、わずかな調整を加えるだけで機能することでした。Cはハードウェアに非常に近いです。

そのままにして、整数型からサイズを抽象化することは、うまくいきませんでした。

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

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

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

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


言語は、任意の境界をその型に焼き込むことができます」そして、実際、パスカルにはサブレンジ型のこの形式があります。
ピーターテイラー

1

おそらく、これらの型がJavaに存在する重要な理由の1つは、単純で悲惨なほど非技術的です。

CとC ++にもこれらの型がありました!

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

3.1整数型

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

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

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

ここで尋ねられた質問の文脈において、手紙の質問に対する答えが満足のいくものであるかどうかはわかりません。しかし、ここでの他の回答と組み合わせて、これらのタイプを使用することが有益であることが明らかになるかもしれません(Javaでのそれらの存在がC / C ++からのレガシーにすぎないかどうかに関係なく)。

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

  • バイトは、アドレス可能な最小のメモリユニットです(CandiedOrangeが既に述べたように)。A byteはデータの基本的な構成要素であり、ファイルから、またはネットワーク経由で読み取ることができます。これのいくつかの明示的な表現が存在する必要があります(そして、それが時々変装する場合でも、ほとんどの言語に存在します)。

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


あなたが特に尋ねた点に関して:

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

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

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


サイドノート:

  • unsignedOak仕様で言及されている修飾子について疑問に思ったことがあるかもしれません。実際には、unsignedまだ実装されていません。実装されない可能性があります。」というコメントも含まれています。。そして彼らは正しかった。

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


0

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

  • まず、すべてのプロセッサが大きな型を処理できるわけではないため、制限を理解してそれを操作する必要があります。
  • 第二に、より小さな型は、操作を行う際のパフォーマンスが高いことを意味します。
  • また、サイズが重要です。データをファイルまたはデータベースに保存する必要がある場合、サイズはすべてのデータのパフォーマンスと最終サイズの両方に影響します。たとえば、15列のテーブルがあり、複数の何百万もの記録。各列に必要な小さいサイズを選択した場合と、最大のタイプを選択した場合の違いは、操作のパフォーマンスにおける可能なデータのギグと時間の違いになります。
  • また、たとえばゲームのように、処理中のデータのサイズが大きな影響を与える複雑な計算にも適用されます。

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

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


0

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

(1)1バイトの変数と1つの長い変数のストレージは重要ではありませんが、配列内の数百万のストレージは非常に重要です。

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

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.