web-dev-qa-db-ja.com

Javaでのテンプレート「メタプログラミング」は良い考えですか?

非常にパフォーマンスに敏感な(1秒あたり数百万回と呼ばれる)いくつかの機能を持つかなり大きなプロジェクトにソースファイルがあります。実際、以前のメンテナは、単一の関数で条件式をチェックするのに費やす時間を節約するために、それぞれわずかに異なる関数の12のコピーを書くことにしました。

残念ながら、これはコードが維持すべきPITAであることを意味します。重複するコードをすべて削除して、テンプレートを1つだけ記述します。ただし、言語Javaはテンプレートをサポートしていないため、ジェネリックがこれに適しているかどうかはわかりません。

私の現在の計画は、代わりに関数の12のコピーを生成するファイルを書くことです(実際には、1回のみ使用できるテンプレートエキスパンダー)。もちろん、ファイルをプログラムで生成する必要がある理由について、豊富な説明を提供します。

私の懸念は、これが将来のメンテナの混乱につながり、変更後にファイルを再生成し忘れた場合、または(さらに悪いことに)プログラムで生成されたファイルを変更した場合、厄介なバグを引き起こす可能性があることです。残念ながら、C++ですべてを書き直すまでは、これを修正する方法はありません。

このアプローチのメリットはデメリットを上回りますか?代わりに:

  • パフォーマンスヒットを取り、単一の保守可能な関数を使用します。
  • 機能を12回複製する必要がある理由の説明を追加し、メンテナンスの負担を優しくしてください。
  • ジェネリックをテンプレートとして使用しようとします(おそらくそのようには機能しません)。
  • コードをパフォーマンスに依存する単一の関数にしたことで、古いメンテナに怒鳴りつけてください。
  • パフォーマンスと保守性を維持する他の方法は?

追伸プロジェクトの設計が悪いため、関数のプロファイリングはかなりトリッキーです...しかし、前のメンテナは、パフォーマンスヒットが受け入れられないことを確信しています。これは5%以上を意味すると思いますが、それは私の推測です。


おそらく、もう少し詳しく説明する必要があります。 12のコピーは非常によく似たタスクを実行しますが、微妙な違いがあります。違いは関数全体のさまざまな場所にあるため、残念ながら、多くの、多くの、条件付きステートメントがあります。効果的に6つの「モード」の操作と2つの「パラダイム」の操作(私が作成した単語)があります。この機能を使用するには、操作の「モード」と「パラダイム」を指定します。これは決して動的ではありません。各コードは、1つのモードとパラダイムを使用します。 12のモードとパラダイムのペアはすべて、アプリケーションのどこかで使用されます。関数は適切にfunc1からfunc12と名付けられ、偶数は2番目のパラダイムを表し、奇数は最初のパラダイムを表します。

保守性が目標である場合、これはこれまでで最悪の設計に過ぎないと認識しています。しかし、「十分に速い」ようで、このコードはしばらく変更を必要としませんでした...元の関数が削除されていないことにも注意する価値があります(私が知る限り、デッドコードですが)。なので、リファクタリングは簡単です。

29
Fengyang Wang

これは非常に悪い状況です。できるだけ早くこれをリファクタリングする必要があります-これは最悪の場合の技術的負債です-コードの実際の重要性さえ知りませんは-重要であると推測するだけです。

できるだけ早くソリューションについて:
実行できることは、カスタムコンパイルステップを追加することです。実際にかなり簡単なMavenを使用している場合、他の自動化ビルドシステムもこれに対応する可能性があります。 .Javaよりもdifferent拡張子の付いたファイルを記述し、そのようなファイルをソースで検索して実際の.Javaを再生成するカスタムステップを追加します。自動生成されたファイルにhuge免責事項を追加して、変更しないように説明することもできます。

長所と一度生成されたファイルの使用:開発者は.Javaに対する変更を機能させることができません。コミットする前に実際に自分のマシンでコードを実行すると、変更が効果がないことがわかります(笑)。そして多分彼らは免責事項を読むでしょう。この特定のファイルは別の方法で変更する必要があることを覚えておくと、チームメイトや将来の自分を信用しないのは間違いありません。 JUnitはテストを実行する前にプログラムをコンパイルするため(そしてファイルを再生成するため)、自動テストも可能です。

[〜#〜]編集[〜#〜]

コメントで判断すると、これが無期限にこの作業を行う方法であり、プロジェクトの他のパフォーマンスが重要な部分にデプロイしても問題ないように、答えが出ました。

簡単に言えば、そうではありません。

独自のミニ言語を作成し、そのためのコードジェネレーターを作成して維持するという余分な負担は、将来のメンテナーに教えることは言うまでもなく、長期的には地獄です。上記は、より安全な方法で問題を処理する場合にのみ、長期的なソリューションに取り組んでいます。それが取るものは私の上にあります。

23
Ordous

メンテナンスは本当ですか、それともあなたを困らせますか?気になるだけの場合は、そのままにしておきます。

パフォーマンスの問題は本当ですか、それとも以前の開発者だけが考えたのですかですか?多くの場合、パフォーマンスの問題は、プロファイラーが使用されている場合でも、考えられている場所ではありません。 (私は thisテクニック を使用して確実にそれらを見つけます。)

したがって、問題以外の問題に対する醜い解決策がある可能性がありますが、実際の問題に対する醜い解決策になる可能性もあります。疑わしい場合は、そのままにしておきます。

16
Mike Dunlavey

実際のプロダクション条件(実際のメモリ消費、ガベージコレクションを現実的にトリガーするなど)の場合よりも確実に確認してください。これらの個別のメソッドはすべて、(1つのメソッドしか持つのではなく)パフォーマンスの違いを生み出します。これには数日かかりますが、これから作業するコードを簡略化することで、数週間節約できます。

また、doですべての関数が必要であることがわかった場合は、 Javassist を使用してプログラムでコードを生成できます。他の人が指摘したように、 Maven で自動化できます。

10
Shivan Dragon

このシステムに何らかのテンプレートプリプロセッサ\コード生成を含めないのはなぜですか?残りのコードをコンパイルする前に、追加のJavaソースファイルを実行および出力するカスタムの追加ビルドステップ。これは、wsdlおよびxsdファイルからのWebクライアントの組み込みがしばしば機能する方法です。

もちろん、そのプリプロセッサ\コードジェネレーターのメンテナンスは必要ですが、重複するコアコードのメンテナンスについて心配する必要はありません。

コンパイル時間Javaコードが発行されているため、余分なコードの代償としてパフォーマンスが低下することはありませんが、複製されたすべてのコードではなくテンプレートを使用することで、メンテナンスが簡単になります。

Javaジェネリックは、言語の型消去のためにパフォーマンス上の利点はありません。代わりに単純なキャストが使用されます。

C++テンプレートについての私の理解から、それらはテンプレートの呼び出しごとにいくつかの関数にコンパイルされます。たとえば、std :: vectorに格納する型ごとに、コンパイル時のコードが重複して複製されます。

6
Peter Smith

正気はありませんJavaメソッドは12のバリアントを持つのに十分な長さになる可能性があります...そしてJITCは長いメソッドを嫌います-それは単に適切にそれらを最適化することを拒否します。メソッドを2つの短いメソッドに分割します。

複数のコピーがあるOTOHは、同じものがあったとしても意味があるかもしれません。それぞれが異なる場所で使用されると、それらは異なるケースに最適化されます(JITCがプロファイルし、まれなケースを例外的なパスに配置します。

正当な理由があるとすれば、コードの生成は大したことではないと私は思います。オーバーヘッドはかなり低く、適切に名前が付けられたファイルはすぐにソースにつながります。少し前にソースコードを生成したとき、私は// DO NOT EDITすべての行に...十分に節約できると思います。

2
maaartinus

あなたが言った「問題」に間違いは何もありません。私が知っていることから、これは、DBサーバーが優れたパフォーマンスを得るために使用する設計とアプローチの正確な種類です。

特定の条件が適用される場合、結合、選択、集計など、あらゆる種類の操作のパフォーマンスを最大化できるようにするための特別なメソッドがたくさんあります。

要するに、あなたが考えるようなコード生成は悪い考えです。おそらく、この図を見て、DBがあなたと同様の問題をどのように解決するかを確認する必要があります。enter image description here

1
randomA

いくつかの賢明な抽象化をまだ使用できるかどうかを確認して、たとえば テンプレートメソッドパターン は、一般的な機能の理解しやすいコードを記述し、メソッド間の違いを12のサブクラスの「パターン記述に従って」「基本操作」に移動します。これにより、保守性とテスト容易性が大幅に向上し、JVMがしばらくするとプリミティブ操作のメソッド呼び出しをインライン化できるため、実際には現在のコードと同じパフォーマンスになる可能性があります。もちろん、これをパフォーマンステストで確認する必要があります。

1

Scala言語を使用すると、特殊なメソッドの問題を解決できます。

Scalaはメソッドをインライン化できます。これは(簡単な高次関数の使用と組み合わせて)コードの重複を無料で回避することを可能にします—これは回答で述べた主な問題のように見えます。

ただし、Scalaには構文マクロがあり、コンパイル時にタイプセーフな方法でコードを使用して多くのことを実行できます。

また、ジェネリックで使用した場合のプリミティブ型のボックス化の一般的な問題もScalaで解決できます。@specializedアノテーションを使用することで、プリミティブのジェネリック特殊化を自動的に実行してボックス化を回避できます。これは言語自体に組み込まれています。したがって、基本的には、Scalaで1つのジェネリックメソッドを記述します。このメソッドは、特殊なメソッドの速度で実行されます。また、高速の汎用算術も必要な場合は、Typeclassの「パターン」を使用して、さまざまな数値型の演算と値を注入することで、簡単に実行できます。

また、Scalaは他の多くの点で素晴​​らしいです。また、Scala <-> Javaの相互運用性は優れているため、すべてのコードをScalaに書き直す必要はありません。プロジェクトのビルドには、必ず SBT(scalaビルドツール) を使用してください。

0
Display Name