web-dev-qa-db-ja.com

文字列を連結して1回呼び出すよりも、println()を呼び出すことがどれほど悪いですか?

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

たとえば、どれほど効率が悪いか

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

vs.

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

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

関連する注記として、印刷するテキストが長い場合、ソースコードを表示しているときにテキストの印刷に関するステートメントが奇妙に見えることがあります。テキスト自体を短くすることができないと仮定すると、何ができますか?これは、複数のprintln() callsが行われる場合でしょうか?かつて誰かがコード行を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++などの言語にも同じことが当てはまりますか?

23
Celeritas

ここでは、緊張状態にある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");
_

vs.

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

2番目のオプションは大幅に高速です。約2倍の速さでお勧めします。

作業の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倍で、経過時間はほぼ同じです。

言い換えると、パフォーマンスはwhatではなく、所有しているprintlnのnumberを基準にスケーリングします。

更新:/ 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
_

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

30
rolfl

printlnsがたくさんあるのはデザインの問題ではないと思います。私が見る方法では、これが本当に問題である場合、静的コードアナライザーでこれを明確に行うことができます。

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

元の質問に答えます。ほとんどの人がprintlnを使用するのと同じように、printlnを使用していくつかの情報を出力するのは悪くないと思います。

2
InformedA

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を使用するたびに、データがバッファに格納されます。そのバッファーは、バッファーがいっぱいになったとき、またはコードが使用から入力を読み取ろうとしたときに(std::cinなどを使用して)、基になるストリームにフラッシュされます。

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

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

off同期をオンにすると、複数のスレッドからのアクセスの同期も完全にあなたの責任になります。複数のスレッドからの同時書き込みにより、データ競合が発生する可能性があります。これは、コードに未定義の動作があることを意味します。

概要

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


1.ストリームのバッファリングをオフにすることは可能ですが、実際にはそうすることは非常にまれであり、誰かがそうする場合、それはおそらくかなり特殊な理由です。 。いずれの場合でも、これはコードが明示的に行う場合にのみ発生します。

1
Jerry Coffin

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

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

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

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

1
rplantiko