文字列を連結して1回呼び出すよりも、println()を頻繁に呼び出すのはどれほど悪いでしょうか?


23

コンソールへの出力はコストのかかる操作であることは知っています。コードを読みやすくするために、長いテキスト文字列を引数として使用するのではなく、関数を呼び出してテキストを2回出力する方がよい場合があります。

たとえば、どれだけ効率が悪いのか

System.out.println("Good morning.");
System.out.println("Please enter your name");

System.out.println("Good morning.\nPlease enter your name");

例では、違いは1回の呼び出しのみprintln()ですが、それ以上の場合はどうなりますか?

関連するメモでは、印刷するテキストが長い場合、ソースコードを表示しているときにテキストの印刷に関連するステートメントが奇妙に見えることがあります。テキスト自体を短くすることができないと仮定すると、何ができますか?これは、複数のprintln()呼び出しが行われる場合ですか?誰かがコード行を80文字(IIRC)を超えてはならないと言っていたので、どうしますか

System.out.println("Good morning everyone. I am here today to present you with a very, very lengthy sentence in order to prove a point about how it looks strange amongst other code.");

データが出力ストリームに書き込まれるたびにシステムコールが行われ、プロセスがカーネルモードに移行する必要があるため、C / C ++などの言語にも同じことが当てはまりますか?


これは非常に小さなコードですが、私は同じことを不思議に思っていると言わざるを得ません。これに対する答えを一度だけ決定するといいと
思い

@SimonAndréForsbergJavaが仮想マシンで実行されるためJavaに適用できるかどうかはわかりませんが、C / C ++などの低レベル言語では、何かが出力ストリームに書き込むたびにシステムコールが発生するため、コストがかかると思います作らなければなりません。

これはあまりにも考慮することがあります:stackoverflow.com/questions/21947452/...
HJK

1
私はここで要点がわからないと言わなければなりません。端末を介してユーザーと対話するとき、通常は印刷する量がそれほど多くないため、パフォーマンスの問題は想像できません。また、GUIまたはwebappを使用するアプリケーションは、ログファイルに書き込む必要があります(通常はフレームワークを使用)。
アンディ14

1
おはようと言っているのなら、あなたは1日に1回か2回それをします。最適化は問題ではありません。それが何か他のものである場合、問題があるかどうかを知るためにプロファイルする必要があります。ロギングに取り組んでいるコードは、複数行のバッファーを作成して、1回の呼び出しでテキストをダンプしない限り、コードを使用できなくします。
mattnz

回答:


29

緊張状態には、パフォーマンスと読みやすさの2つの「力」があります。

最初に3番目の問題に取り組みましょう。長い行です。

System.out.println("Good morning everyone. I am here today to present you with a very, very lengthy sentence in order to prove a point about how it looks strange amongst other code.");

これを実装して読みやすさを保つ最良の方法は、文字列の連結を使用することです。

System.out.println("Good morning everyone. I am here today to present you "
                 + "with a very, very lengthy sentence in order to prove a "
                 + "point about how it looks strange amongst other code.");

文字列定数の連結はコンパイル時に行われ、パフォーマンスにはまったく影響しません。行は読みやすく、先に進むことができます。

今、について:

System.out.println("Good morning.");
System.out.println("Please enter your name");

System.out.println("Good morning.\nPlease enter your name");

2番目のオプションは大幅に高速です。2Xほど速く提案します。...なぜですか?

作業の90%(エラーのマージンが広い)は、文字を出力にダンプすることとは関係ありませんが、出力を書き込むために出力を保護するために必要なオーバーヘッドです。

同期

System.outですPrintStream。私が知っているすべてのJava実装は、PrintStreamを内部的に同期しますGrepCodeのコードを参照してください!

これはあなたのコードにとって何を意味しますか?

これは、呼び出すたびにSystem.out.println(...)メモリモデルを同期し、ロックを確認して待機することを意味します。System.outを呼び出す他のスレッドもロックされます。

シングルスレッドアプリケーションでSystem.out.println()は、多くの場合、システムのIOパフォーマンス、ファイルへの書き込み速度によって影響が制限されます。マルチスレッドアプリケーションでは、IOよりもロックが問題になることがあります。

フラッシング

各printlnはフラッシュされます。これにより、バッファーがクリアされ、バッファーへのコンソールレベルの書き込みがトリガーされます。ここで行われる労力は実装に依存しますが、フラッシュのパフォーマンスは、フラッシュされるバッファのサイズに関連する小さな部分にすぎないと一般に理解されています。メモリバッファがダーティとしてマークされ、仮想マシンがIOを実行しているなど、フラッシュに関連する大きなオーバーヘッドがあります。そのオーバーヘッドを2回ではなく1回発生させることは、明らかな最適化です。

いくつかの数字

次の小さなテストをまとめました。

public class ConsolePerf {

    public static void main(String[] args) {
        for (int i = 0; i < 100; i++) {
            benchmark("Warm " + i);
        }
        benchmark("real");
    }

    private static void benchmark(String string) {
        benchString(string + "short", "This is a short String");
        benchString(string + "long", "This is a long String with a number of newlines\n"
                  + "in it, that should simulate\n"
                  + "printing some long sentences and log\n"
                  + "messages.");

    }

    private static final int REPS = 1000;

    private static void benchString(String name, String value) {
        long time = System.nanoTime();
        for (int i = 0; i < REPS; i++) {
            System.out.println(value);
        }
        double ms = (System.nanoTime() - time) / 1000000.0;
        System.err.printf("%s run in%n    %12.3fms%n    %12.3f lines per ms%n    %12.3f chars per ms%n",
                name, ms, REPS/ms, REPS * (value.length() + 1) / ms);

    }


}

コードは比較的単純で、短い文字列または長い文字列を繰り返し出力して出力します。長い文字列には複数の改行が含まれています。それぞれ1000回の反復を印刷するのにかかる時間を測定します。

私はUNIX(Linux)のコマンド・プロンプトでそれを実行し、リダイレクトする場合STDOUT/dev/null、そして実際に結果を印刷STDERR、私は次の操作を実行できます。

java -cp . ConsolePerf > /dev/null 2> ../errlog

出力(errlog)は次のようになります。

Warm 0short run in
           7.264ms
         137.667 lines per ms
        3166.345 chars per ms
Warm 0long run in
           1.661ms
         602.051 lines per ms
       74654.317 chars per ms
Warm 1short run in
           1.615ms
         619.327 lines per ms
       14244.511 chars per ms
Warm 1long run in
           2.524ms
         396.238 lines per ms
       49133.487 chars per ms
.......
Warm 99short run in
           1.159ms
         862.569 lines per ms
       19839.079 chars per ms
Warm 99long run in
           1.213ms
         824.393 lines per ms
      102224.706 chars per ms
realshort run in
           1.204ms
         830.520 lines per ms
       19101.959 chars per ms
reallong run in
           1.215ms
         823.160 lines per ms
      102071.811 chars per ms

これは何を意味するのでしょうか?最後の「スタンザ」を繰り返します。

realshort run in
           1.204ms
         830.520 lines per ms
       19101.959 chars per ms
reallong run in
           1.215ms
         823.160 lines per ms
      102071.811 chars per ms

つまり、すべての意図と目的に対して、「長い」行は約5倍長く、複数の改行が含まれていても、出力には短い行とほぼ同じ時間がかかります。

長い目で見た場合の1秒あたりの文字数は5倍であり、経過時間はほぼ同じです.....

つまり、パフォーマンスは、印刷するものではなく、所有しているprintln のに比例します。

更新: / dev / nullではなくファイルにリダイレクトするとどうなりますか?

realshort run in
           2.592ms
         385.815 lines per ms
        8873.755 chars per ms
reallong run in
           2.686ms
         372.306 lines per ms
       46165.955 chars per ms

かなり遅いですが、比率はほぼ同じです。


いくつかのパフォーマンス値を追加しました。
rolfl

また"\n"、正しい行末記号ではない可能性のある問題も考慮する必要があります。println行を適切な文字で自動的に終了\nしますが、文字列に直接貼り付けると問題が発生する可能性があります。正しく実行したい場合は、文字列の書式設定またはline.separatorシステムプロパティ使用する必要がありますprintlnずっときれいです。
user2357112は、モニカをサポートしています14

3
これはすべて素晴らしい分析なので、確かに+1されますが、コンソール出力にコミットすると、これらの小さなパフォーマンスの違いが窓から飛び出すと私は主張します。プログラムのアルゴリズムが結果を出力するよりも高速に実行される場合(この小さなレベルの出力)、各文字を1つずつ印刷し、違いに気付かないことができます。
デビッドハークネス14

これは、出力が同期されるJavaとC / C ++の違いだと思います。これは、複数のスレッドがコンソールへの書き込みを試みた場合に、マルチスレッドプログラムを記述し、出力が文字化けするという問題があることを思い出したためです。誰でもこれを確認できますか?

6
また、ユーザー入力を待機する関数のすぐ隣に置いた場合、その速度はまったく問題にならないことも覚えておくことが重要です。
vmrob 14

2

がたくさんあるprintlnということは、設計上の問題ではないと思います。私が見る方法は、それが本当に問題であれば、静的コードアナライザーでこれを明確に行えるということです。

しかし、ほとんどの人はこのようなIOを実行しないため、問題はありません。本当に多くのIOを実行する必要がある場合、入力がバッファリングされるときにバッファ化されたもの(BufferedReader、BufferedWriterなど)を使用します。の束printlnまたは少数println

元の質問に答えるために。私が言うにはprintln、ほとんどの人が使用するようにいくつかのものを印刷するために使用するのは悪くありませんprintln


1

CやC ++のような高レベル言語では、これはJavaほど問題ではありません。

まず、CとC ++はコンパイル時の文字列連結を定義するため、次のようなことができます。

std::cout << "Good morning everyone. I am here today to present you with a very, "
    "very lengthy sentence in order to prove a point about how it looks strange "
    "amongst other code.";

このような場合、文字列を連結することは、あなたができる最適化だけではありません。通常(など)コンパイラに依存します。むしろ、CおよびC ++標準で直接必要です(翻訳のフェーズ6:「隣接する文字列リテラルトークンは連結されます。」)。

CとC ++は、コンパイラーと実装に少し余分な複雑さを犠牲にしますが、プログラマーから効率的に出力を生成する複雑さを隠すために、もう少し努力します。Javaはアセンブリ言語によく似ています。各呼び出しSystem.out.printlnは、コンソールにデータを書き込むために、基礎となるオペレーティングシステムの呼び出しに直接変換されます。バッファリングで効率を改善したい場合は、個別に提供する必要があります。

これは、たとえば、C ++では、前の例を次のように書き換えることを意味します。

std::cout << "Good morning everyone. I am here today to present you with a very, ";
std::cout << "very lengthy sentence in order to prove a point about how it looks ";       
std::cout << "strange amongst other code.";

...通常、1は効率にほとんど影響しません。を使用するたびcoutに、データがバッファに格納されます。そのバッファーは、バッファーがいっぱいになるか、コードが使用からの入力を読み取ろうとしたときに、基になるストリームにフラッシュされます(withなどstd::cin)。

iostreamsには、sync_with_stdioiostreamsからの出力をCスタイルの入力(例:)と同期させるかどうかを決定するプロパティもありますgetchar。デフォルトでsync_with_stdioはtrueに設定されているため、たとえばに書き込みstd::cout、次に経由getcharで読み取る場合、書き込まれたデータは呼び出さcoutれたときにフラッシュさgetcharれます。sync_with_stdiofalseに設定して無効にすることができます(通常はパフォーマンスを向上させるために行われます)。

sync_with_stdioスレッド間の同期の度合いも制御します。同期がオンになっている場合(デフォルト)、複数のスレッドからiostreamに書き込むと、スレッドからのデータがインターリーブされますが、競合状態は防止されます。IOW、プログラムは実行されて出力を生成しますが、一度に複数のスレッドがストリームに書き込む場合、異なるスレッドからのデータの任意の混合は通常、出力をほとんど役に立たなくします。

あなたがオンにするとオフに同期し、その後、複数のスレッドからのアクセスを同期することは完全にだけでなく、あなたの責任となります。複数のスレッドからの同時書き込みは、データ競合を引き起こす可能性があります。つまり、コードの動作が未定義です。

概要

C ++のデフォルトでは、速度と安全性のバランスをとろうとします。結果は、シングルスレッドコードではかなり成功しますが、マルチスレッドコードではそれほど成功しません。マルチスレッドコードは通常、一度に1つのスレッドのみがストリームに書き込みを行って有用な出力を生成することを保証する必要があります。


1.ストリームのバッファリングをオフにすることは可能ですが、実際にそうするのは非常に珍しいことです。誰かがそれを行うとき/場合は、おそらくパフォーマンスに影響するにもかかわらずすべての出力がすぐにキャプチャされるようにするなど、かなり特定の理由によるものです。いずれにせよ、これはコードが明示的に行う場合にのみ発生します。


13
CやC ++などの高レベル言語では、これはJavaの場合よりも問題が少ないです。」-何ですか?CおよびC ++は、Javaよりも低レベルの言語です。また、ラインターミネータを忘れました。
user2357112は、Monicaを14

1
全体を通して、私はJavaが低レベル言語であることの客観的な基礎を指摘しています。あなたが話しているラインターミネーターがわからない。
ジェリーコフィン14

2
Javaはコンパイル時の連結も行います。たとえば、"2^31 - 1 = " + Integer.MAX_VALUEは単一のインターン文字列として保存されます(JLS Sec 3.10.5および15.28)。
200_success 14

2
@ 200_success:コンパイル時に文字列の連結を行うJavaは、§15.18.1:「式がコンパイル時の定数式(§15.28)でない限り、Stringオブジェクトが新しく作成されます(§12.5)」と思われます。これにより、コンパイル時に連結を行う必要がありますが、必要ではありません。つまり、入力がコンパイル時定数でない場合、結果を新たに作成する必要がありますが、コンパイル時定数である場合、どちらの方向にも要件はありません。コンパイル時の連結を要求するには、その(暗黙の)「if」を実際に「if and only if」を意味するものとして読む必要があります。
ジェリー

2
@Phoshi:リソースで試すことは、RAIIに漠然と似ていることすらありません。RAIIでは、クラスがリソースを管理できますが、リソースを試すには、リソースを管理するクライアントコードが必要です。一方の機能と他方の機能の欠如(より正確には抽象化)は完全に関連しています。実際、ある言語を別の言語よりも高いレベルにするものです。
ジェリー

1

ここではパフォーマンスが実際に問題になるわけではありませんが、一連のprintlnステートメントの読みやすさの悪さは、設計面の欠落を示しています。

なぜ多くのprintlnステートメントのシーケンスを書くのですか?--helpコンソールコマンドのテキストのように、固定テキストブロックが1つだけの場合は、個別のリソースとして使用し、要求に応じて画面に読み込んで画面に書き込む方がはるかに良いでしょう。

ただし、通常は動的部分と静的部分が混在しています。一方で、いくつかの裸の注文データがあり、他方でいくつかの固定された静的テキスト部分があり、これらを一緒に混合して注文確認シートを形成する必要があるとしましょう。繰り返しますが、この場合も、別のressourceテキストファイルを用意することをお勧めします。ressourceは、実行時に実際の注文データに置き換えられる何らかの種類のシンボル(プレースホルダー)を含むテンプレートになります。

プログラミング言語を自然言語から分離することには、多くの利点があります。その中には国際化があります。ソフトウェアで多言語になりたい場合は、テキストを翻訳する必要があります。また、テキストの修正だけが必要な場合、ミススペルを修正するなど、コンパイル手順が必要な理由もあります。

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