Closed5

WriteLine(int)よりもWriteLine(string, object)の方が速い

kumavalekumavale

以下のコードを比べると、どちらが速いだろうか?
答えは後者

System.Console.WriteLine(1);
System.Console.WriteLine("{0}", 1);

なぜ????
直感的に前者の方が速そうなんだが…

kumavalekumavale

WriteLine(int)の処理を追ってみる

※ソースコードはReferenceSourceで確認できる。

  1. 内部のTextWriterWriteLine(int)を呼んでいる
public static void WriteLine(int value)
{
    Out.WriteLine(value);
}
  1. Write(int)WriteLine()を呼んでいる
    WriteLine()は本筋ではないので無視
public virtual void WriteLine(int value) {
    Write(value);
    WriteLine();
}
  1. intToStringを呼んで、それを引数にしてWrite(string)を呼んでいる
    気がかりなのは、FormatProviderというものを引数にしている点(ひとまず無視)
public virtual void Write(int value) {
    Write(value.ToString(FormatProvider));
}
  1. Write(char[])を呼んでいる
public virtual void Write(String value) {
    if (value != null) Write(value.ToCharArray());
}
  1. Write(char[], int, int)を呼んでいる
public virtual void Write(char[] buffer) {
    if (buffer != null) Write(buffer, 0, buffer.Length);
}
  1. エラーチェックの後、文字列の長さだけWrite(char)を呼んでいる
// Writes a range of a character array to the text stream. This method will
// write count characters of data into this TextWriter from the
// buffer character array starting at position index.
//
public virtual void Write(char[] buffer, int index, int count) {
    if (buffer==null)
        throw new ArgumentNullException("buffer", Environment.GetResourceString("ArgumentNull_Buffer"));
    if (index < 0)
        throw new ArgumentOutOfRangeException("index", Environment.GetResourceString("ArgumentOutOfRange_NeedNonNegNum"));
    if (count < 0)
        throw new ArgumentOutOfRangeException("count", Environment.GetResourceString("ArgumentOutOfRange_NeedNonNegNum"));
    if (buffer.Length - index < count)
        throw new ArgumentException(Environment.GetResourceString("Argument_InvalidOffLen"));
    Contract.EndContractBlock();

    for (int i = 0; i < count; i++) Write(buffer[index + i]);
}
kumavalekumavale

WriteLine(string, object)の処理を追ってみる

  1. 内部のTextWriterWriteLine(string, object)を呼んでいる
public static void WriteLine(String format, Object arg0)
{
    Out.WriteLine(format, arg0);
}
  1. String.Formatstringにしたものを引数にしてWriteLine(string)を呼んでいる

この時点でWriteLine(int)の場合はWrite(string)WriteLine()に分けていたのに、直接WriteLine(string)を呼んでいる!?

public virtual void WriteLine(String format, Object arg0)
{
    WriteLine(String.Format(FormatProvider, format, arg0));
}
  1. WriteLine(string)では、単にWrite(string)WriteLine()を呼ぶのではなく、何かしているぞ🤔
// Writes a string followed by a line terminator to the text stream.
//
public virtual void WriteLine(String value) {

    if (value==null) {
        WriteLine();
    }
    else {
        // We'd ideally like WriteLine to be atomic, in that one call
        // to WriteLine equals one call to the OS (ie, so writing to 
        // console while simultaneously calling printf will guarantee we
        // write out a string and new line chars, without any interference).
        // Additionally, we need to call ToCharArray on Strings anyways,
        // so allocating a char[] here isn't any worse than what we were
        // doing anyways.  We do reduce the number of calls to the 
        // backing store this way, potentially.
        int vLen = value.Length;
        int nlLen = CoreNewLine.Length;
        char[] chars = new char[vLen+nlLen];
        value.CopyTo(0, chars, 0, vLen);
        // CoreNewLine will almost always be 2 chars, and possibly 1.
        if (nlLen == 2) {
            chars[vLen] = CoreNewLine[0];
            chars[vLen+1] = CoreNewLine[1];
        }
        else if (nlLen == 1)
            chars[vLen] = CoreNewLine[0];
        else
            Buffer.InternalBlockCopy(CoreNewLine, 0, chars, vLen * 2, nlLen * 2);
        Write(chars, 0, vLen + nlLen);
    }
    /*
       Write(value);  // We could call Write(String) on StreamWriter...
       WriteLine();
     */
}

英訳
WriteLineはアトミックであることが理想で、WriteLineの1回の呼び出しはOSの1回の呼び出しに等しくなります(つまり、コンソールに書き込みながら同時にprintfを呼び出せば、干渉なしに文字列と改行文字を書き出すことが保証されます)。
さらに、Stringに対してToCharArrayを呼び出す必要があるので、ここでchar[]を確保することは、これまでやっていたことよりも悪いことではありません。 この方法で、バッキングストアの呼び出し回数を減らせる可能性があります。

kumavalekumavale

バッキングストアの呼び出しが1回になるってことかな??
String.FormatのコストよりもOSの標準出力の呼び出しコストの方が大きいから、速くなるんだろうなぁ

因みに改行なしのWrite(int)Write(string, object)だと前者の方が速い。

kumavalekumavale

Stopwatchを使った計測

for (int i = 0; i < N; i++) {
    Console.WriteLine(1);
}
N=100000000
WriteLine(1.ToString()) 4.079 sec
WriteLine(1) 5.902 sec

まとめ

Out.WriteLine(String)を呼ぶようにした方が速くなる。

このスクラップは2023/03/15にクローズされました