2016年10月27日木曜日

【C#】関数指定とラムダ式の速度差(List.Sort)

◆概要
ラムダ式の方が早いと聞いたので、早速確認してみる。

◆使用データ
①10000, 10001, 10002, …を1000万件
②0, 1, 2, …を1,000万件

上記①②の計2,000万件のデータを一つのリストに入れてSort()メソッドを実行。
Sort()に渡す引数として、以下の(a)(b)を切り替えて、それぞれ速度比較してみる。
 (a)ラムダ式指定
 (b)関数(デリゲート)指定

データはかなりテキトー。入れ替えが発生し、同一データ有を迅速に作りたかっただけ。

◆結論
少しだけラムダ式の方が早い。
ちなみにDebugビルドだとラムダ式が19秒、関数指定が45秒など差が広がる。
今回の趣旨とはずれるが、引数無しSort()の方がもっと早い。IComparerは・・・いずれ試したい。
C++では仮想関数テーブルから選択処理が走って遅いイメージがあるが、C#ではどうなのか?

◆ソースコード1:ラムダ式でソート
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace ComparatorVsRambda
{
    class Program
    {
        static void Main(string[] args)
        {
            var list = Enumerable.Range(10 * 1000, 10 * 1000 * 1000).ToList();
            list.AddRange(Enumerable.Range(0, 10 * 1000 * 1000));

            var sw = new Stopwatch();

            sw.Start();
            list.Sort((left, right) => left - right);   // ★★★ここを変更する
            sw.Stop();
            Console.WriteLine(sw.Elapsed);
            Console.ReadKey();
        }

        static int compare(int left, int right)
        {
            return left - right;
        }
    }
}

◆結果1
1回目:15.32秒
2回目:14.32秒

◆ソースコード2:関数指定によるソート
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace ComparatorVsRambda
{
    class Program
    {
        static void Main(string[] args)
        {
            var list = Enumerable.Range(10 * 1000, 10 * 1000 * 1000).ToList();
            list.AddRange(Enumerable.Range(0, 10 * 1000 * 1000));

            var sw = new Stopwatch();

            sw.Start();
            list.Sort(compare);   // ★★★ここを変更する
            sw.Stop();
            Console.WriteLine(sw.Elapsed);
            Console.ReadKey();
        }

        static int compare(int left, int right)
        {
            return left - right;
        }
    }
}

◆結果2
1回目:16.99秒
2回目:16.47秒

◆ソースコード3:(参考)引数無しでソート
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace ComparatorVsRambda
{
    class Program
    {
        static void Main(string[] args)
        {
            var list = Enumerable.Range(10 * 1000, 10 * 1000 * 1000).ToList();
            list.AddRange(Enumerable.Range(0, 10 * 1000 * 1000));

            var sw = new Stopwatch();

            sw.Start();
            list.Sort();   // ★★★ここを変更する
            sw.Stop();
            Console.WriteLine(sw.Elapsed);
            Console.ReadKey();
        }

        static int compare(int left, int right)
        {
            return left - right;
        }
    }
}

◆結果3
1回目:4.14秒
2回目:4.32秒

◆環境
 ■速度確認した仮想マシン
  • CPU:2
  • メモリ:2GB
  • OS:Windows7
  • 仮想マシン:VMWare WorkStation10
  • Visual Studio 2015 Community
  • 構成:Releaseでビルド
  • デバッグ情報(ビルドの詳細設定):none
  • Visual Studio ホスティングプロセス:無効

 ■ホストPCの性能(参考)
  • CPU:Intel Core i5-4250U
  • メモリ:8GB
  • ドライブ:SSD(Plextor PX-512M3P)
  • OS:Windows10

2016年10月20日木曜日

【C#】Console.WriteLineはスレッドセーフなのか?

◆前置き
ConsoleクラスのAPI仕様

標準入力、標準出力、または標準エラー ストリームをリダイレクトするには、呼び出し、 Console.SetIn, 、Console.SetOut, 、または Console.SetError メソッドをそれぞれします。 これらのストリームを使用する I/O 操作は同期され、複数のスレッドがデータの読み取りまたはストリームに書き込むことができます。 つまり、このなど、通常は非同期のメソッド TextReader.ReadLineAsync, 、オブジェクトがコンソールのストリームを表す場合、同期的に実行します。

スレッド セーフ
この型はスレッド セーフです。


なるほど・・・、分からん。いや、だいたい分かるのだが、日本語訳ミスってるし(このなどとは一体・・・?コロラドみたいで語呂良くて、気に入ったぞ)、変な読点あるし、機械翻訳なのかな?スレッドセーフとは言ってるが、どの程度大丈夫なのか?メソッドの開始から終了までなのか?単に例外が発生しないだけで文字列がごちゃ混ぜになるのか?それとも・・・

ちなみにスレッドセーフとは・・・

一方のスレッドがあるデータを操作したことで他のスレッドが利用するデータが破壊されてしまったり、同じ手続きを同時に複数のスレッドが呼び出したことで予期しない結果が生じたりしないよう、対策を講じる必要がある。


ということで、動作確認してみる。

◆サンプルコード
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace ConsoleThreadSafe
{
    class Program
    {
        static void Main(string[] args)
        {
            var strAry = new string[] {
                "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA",
                "BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB",
                "CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC",
                "DDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDD",
                "EEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEE",
                "FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF",
            };
            Console.WriteLine("start.");
            foreach (var count in Enumerable.Repeat(0, 1000))
                strAry.AsParallel().ForAll(Console.WriteLine);
            Console.WriteLine("end.");
            Console.ReadKey();
        }
    }
}

◆実行結果(画面)


◆実行結果(まとめ)
・実行結果をエディタに貼り付け、start. end.の行を除くと6000行。
・また、全ての行が以下のいずれかであることが確認できた。
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC DDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDD EEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEE FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF

◆Console.Writeはどうか?
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace ConsoleThreadSafe
{
    class Program
    {
        static void Main(string[] args)
        {
            var strAry = new string[] {
                "This_is_a_pen,_but_I'm_not_a_pen.",
                "Hello_World.",
                "I'm_HogeHoge.",
                "My_name_is_Panda.",
                "C#_is_cool.",
                "I'm_fooooooooooooooooooooooool.",
            };
            Console.WriteLine("start");
            foreach (var count in Enumerable.Repeat(0, 1000))
                strAry.AsParallel().ForAll(Console.Write);
            Console.WriteLine("end");
            Console.ReadKey();
        }
    }
}


◆実行結果(画面)


◆実行結果(まとめ)
・実行結果をエディタに貼り付け、start endの行を除くと6000行。
・また、.で区切ると全ての文字列が以下のいずれかであることが確認できた。
C#_is_cool.
Hello_World.
I'm_HogeHoge.
I'm_fooooooooooooooooooooooool.
My_name_is_Panda.
This_is_a_pen,_but_I'm_not_a_pen.

※スペースを使わずに_(アンダースコア)を使っている理由
 コマンドプロンプトの右端にスペースがあると、コピー&ペーストの際にスペースが削除されるため、アンダースコアを使用している。リダイレクト使おうとも思ったが、本当に想定した検証になるか不安があったので使用しなかった。

◆課題

  • 本当にマルチスレッドになってるのか確認する。確認してないんじゃ、この記事意味ないよ?
  • Console.WriteLine(str)を実行して確認してみる。
  • Console.Write(str)を実行して確認してみる。
  • AsParrallesを辞めてみる。
  • 速度の確認をする。

【Excel】印刷はPDF保存してから行う

Excelの印刷は色々面倒※なので、pdf出力してから印刷が一番良い。2007はアドイン追加でpdf出力可能、2010以降は標準で出力可能。

※シート毎に両面印刷とか2in1印刷とかばらばらになったり、文字の見切れが有ったり。

2016年10月18日火曜日

【C#】例外キャッチ時の各メソッド・プロパティの文字列

C#で例外発生時にログファイルへ書き込む場合、e.ToString(),e.Message,…どのプロパティ・メソッドを使うべきか?それぞれのエラーメッセージを実際に動かして確認する。
◆結論
当然状況次第で必要なものを選択する必要がある。
ただ、通常はe.ToString()でいいと思われる。自作の例外は、場合によって、e.Messageでもいいかも。

◆メインコード①(InnerException無し)
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace ExCatchStr
{
    class Program
    {
        static void Main(string[] args)
        {
            try
            {
                int zero = 0;
                int tmp = 0 / zero;
            }
            catch (Exception e)
            {
                var str = e.Message;  // ★★★ この行を変更する
                Console.WriteLine(str);
            }
            Console.ReadKey();
        }
    }
}

◆結果
No コード 結果 時間
1 e.Message 0 で除算しようとしました。
2 e.Source
(namespaceが出てる?)
ExCatchStr
3 e.StackTrace 場所 ExCatchStr.Program.Main(String[] args) 場所 c:\users\owner\documents\visual studio 2015\Projects\ExCatchStr\ExCatchStr\Program.cs:行 16
4 e.Data System.Collections.ListDictionaryInternal
5 e.InnerException
(コンソール上は空だが、nullが返されている)
6 e.HResult -2147352558
7 e.TargetSite Void Main(System.String[])
8 e.ToString() System.DivideByZeroException: 0 で除算しようとしました。
場所 ExCatchStr.Program.Main(String[] args) 場所 c:\users\owner\documents\visual studio 2015\Projects\ExCatchStr\ExCatchStr\Program.cs:行 16
9 e.ToString().Replace(
Environment.NewLine, "")
System.DivideByZeroException: 0 で除算しようとしました。 場所 ExCatchStr.Program.Main(String[] args) 場所 c:\users\owner\documents\visual studio 2015\Projects\ExCatchStr\ExCatchStr\Program.cs:行 16

◆メインコード②(InnerException有り)
いずれ確認予定。

◆実行環境
 ■VMware WorkStation10上の仮想マシン
  ・CPU:2
  ・メモリ:2GB
  ・OS:Windows7
  ・MSVS ver:Visual Studio 2015 Community

 ■ホストPCの性能(参考)
  ・CPU:Intel Core i5-4250U
  ・メモリ:8GB
  ・ドライブ:SSD(Plextor PX-512M3P)
  ・OS:Windows10

2016年10月11日火曜日

【PowerShell】Windows起動、終了時刻を表示

■Win10
Get-EventLog -LN System |
    % EventID -in (7001, 7002) |
    select TimeWritten, EventID, Message -F 20
Read-Host "Input Key"
Read-Host "Input Key"

※高速スタートアップが有効だと、6005,6006は起動、終了時に記録されないとのこと。そのため、ログオン・ログオフ時刻を表示する。
また、Read-Host×2は-Fが非同期になってしまう対策。

◆Windowsの起動・終了時刻を表示するスクリプト(正確にはイベントログ開始・終了時刻)。
①Win10 PowerShell5.1.1※1
Get-EventLog -LogName System |
    where {($_.EventID -eq 6005) -or ($_.EventID -eq 6006)} |
    select -First 10 |
    foreach { Write-Host $_.TimeWritten, $_.Message }

Read-Host "Enterキーを入力して下さい。"

②Win10 PowerShell5.1.1(別解)※1
Get-EventLog -LogName System |
    where EventID -in (6005, 6006) |
    select -First 10 |
    foreach { Write-Host $_.TimeWritten, $_.Message }

Read-Host "Enterキーを入力して下さい。"

③PowerShell3.0、4.0、5.0※2(①~④中で一番簡単な方法)
Get-EventLog -LN System |
    where EventID -in (6005, 6006) |
    select TimeWritten, Message -F 10 

Read-Host "Enterキーを入力して下さい。"

④PowerShell2.0
Get-EventLog -LogName System |
    where {($_.EventID -eq 6005) -or ($_.EventID -eq 6006)} |
    select TimeWritten, Message -First 10 

Read-Host "Enterキーを入力して下さい。"

◆①、②の実行結果


■③、④の実行結果


※1 select TimeWritten, Message -First 10は実行できるはずだが、実行すると何故かRead-Hostが先に実行され、その後イベントログが表示されてしまう。仕方無く、foreachとWrite-Hostを使っている。しかし、PowerShell3.0以下ではselect TimeWritten, Message -First 10が動作する。

※2 動作するバージョンと動作しないバージョンがある。Win10が駄目っぽい。

  • ×Win10(2016/10/18現在) PowerShell5.1.14393.206
  • ×Win10 PowerShell5.0.10240.16384
  • 〇Win8 PowerShell3.0
  • 〇Win7 PowerShell5.0.10586.117
  • 〇Win7 PowerShell4.0

2016年10月8日土曜日

【Java】例外キャッチ時の各メソッドの文字列

Javaで例外発生時にログファイルへ書き込む場合、e.getMessage(),e.toString(),…どのメソッドを使うべきか?それぞれのエラーメッセージを実際に動かして確認する。

◆メインコード
package main;

import java.io.IOException;
import java.io.PrintWriter;
import java.io.StringWriter;

public class Main {
  public static void main(String[] args) {
    try {
      int tmp = 0 / 0;
    } catch (Exception e) {
      String s = e.getMessage();    // ★★★この行を変更する。
      System.err.println(s);
    }
  }

  public static String exToStr(Exception arg) {  // 後述する。
    try (StringWriter sw = new StringWriter()) {
      arg.printStackTrace(new PrintWriter(sw));
      return sw.toString();
    } catch (IOException e) {
      e.printStackTrace();
    }
    return "";
  }
}

◆結果
No コード 結果 時間※
1 e.getMessage() / by zero 1ms
2 e.getLocalizedMessage()
(例外クラスによって文字列が変わる。)
/ by zero 2ms
3 e.toString() java.lang.ArithmeticException: / by zero 231ms
4 StringWriterによる変換
(上記のメインコードのexToStr()を参照のこと。)
java.lang.ArithmeticException: / by zero
  at main.Main.main(Main.java:12)
1973ms
5 ExceptionUtils.getStackTrace(e)
(apache commons Lang)
java.lang.ArithmeticException: / by zero
  at main.Main.main(Main.java:12)
1930ms
6 e.getStackTrace().toString() [Ljava.lang.StackTraceElement;@1db9742 491ms
※100万回実行した時の処理時間で、以下のコード(100万回でこの時間なら気にする必要無い気もする)
String s = "";
long start = System.nanoTime();
for (int i = 0; i < 1000 * 1000 * 1; i++)
  s = ExceptionUtils.getStackTrace(e);    // ★★★この行を変更する。
long end = System.nanoTime();
System.out.println((end - start) / 1000000  + "ms");
◆実行環境
 ■VMware WorkStation10上の仮想マシン
  ・CPU:2
  ・メモリ:2GB
  ・OS:Windows7
  ・Java ver:eclipse同梱のJava8
  ・IDE:eclipse(pleiades) Version: Neon.1 Release (4.6.1)

 ■ホストPCの性能(参考)
  ・CPU:Intel Core i5-4250U
  ・メモリ:8GB
  ・ドライブ:SSD(Plextor PX-512M3P)
  ・OS:Windows10

2016年10月6日木曜日

【C#】Debug版とRelease版の速度差

C++では特にstlやboostあたりを使うと、Debug版とRelease版の間で速度差が生まれる。C#では速度がどの程度になるか試してみる。いずれもビルド後にエクスプローラからexeファイルを実行。

◆結論:思った程、速度差は無いが、Release版の方が早い。ただし、Release版はデフォルトから設定を変更している(後述)。下記ではListクラスを試したが、別のクラスではもっと開きがあるかも。

【1】Listの末尾に1000万回文字列を追加
■コード①
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace Performance
{
    class Program
    {
        static void Main(string[] args)
        {
            System.Diagnostics.Stopwatch sw = new System.Diagnostics.Stopwatch();
            sw.Start();

            var list = new List<string>();
            for (int i = 0; i < 1000 * 1000 * 10; i++)
                list.Add(i + "");

            sw.Stop();

            Console.WriteLine("処理時間:" + sw.Elapsed);
            Console.ReadKey();
        }
    }
}


■結果①
Release版1回目:4.95秒
Release版2回目:4.98秒
Debug版1回目:4.97秒
Debug版2回目:4.97秒

【2】Listに100万回文字列を追加し、全て配列外

■コード②
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace Performance
{
    class Program
    {
        static void Main(string[] args)
        {
            var sw = new System.Diagnostics.Stopwatch();
            sw.Start();

            var list = new List<string>();
            for (int i = 0; i < 1000 * 1000; i++)
                try { list.Insert(i + 1, i + ""); }
                catch { }   // 例外無視は、絶対に真似しない事。

            sw.Stop();

            Console.WriteLine("処理時間:" + sw.Elapsed);
            Console.ReadKey();
        }
    }
}


■結果②
Release版1回目:13.77秒
Release版2回目:13.40秒
Debug版1回目:13.55秒
Debug版2回目:13.50秒

【3】Listの10億回シーケンシャルリード
■コード③
using System;
using System.Collections.Generic;
using System.Linq;

namespace Performance
{
    class Program
    {
        static void Main(string[] args)
        {
            var list = Enumerable.Range(0, 1000 * 1000 * 100).ToList();  // varはList<int>

            System.Diagnostics.Stopwatch sw = new System.Diagnostics.Stopwatch();
            sw.Start();

            long sum = 0;
            foreach (var tmp in Enumerable.Range(0, 10)) // ↑のRange()で10億指定するとToList()でOutOfMemoryとなるため、仕方なく2重ループ
                foreach (var elem in list)
                    sum += elem;
                
            sw.Stop();

            Console.WriteLine("処理時間:" + sw.Elapsed);
            Console.WriteLine("sum:" + sum);
            Console.ReadKey();
        }
    }
}



■結果③
Release版1回目:5.49秒
Release版2回目:5.13秒
Debug版1回目:7.84秒
Debug版2回目:7.81秒

【4】Listの10億回インデックスリード
■コード④
using System;
using System.Collections.Generic;
using System.Linq;

namespace Performance
{
    class Program
    {
        static void Main(string[] args)
        {
            var list = Enumerable.Range(0, 1000 * 1000 * 100).ToList();  // varはList<int>

            System.Diagnostics.Stopwatch sw = new System.Diagnostics.Stopwatch();
            sw.Start();

            long sum = 0;
            foreach (var tmp in Enumerable.Range(0, 10)) // ↑のRange()で10億指定するとToList()でOutOfMemoryとなるため、仕方なく2重ループ
                foreach (var elem in list)
                    sum += list[elem];
                
            sw.Stop();

            Console.WriteLine("処理時間:" + sw.Elapsed);
            Console.WriteLine("sum:" + sum);
            Console.ReadKey();
        }
    }
}

■結果④
Release版1回目:6.77秒
Release版2回目:7.26秒
Debug版1回目:12.80秒
Debug版2回目:13.21秒


◆環境
■VMware WorkStation10上の仮想マシン
・CPU:2
・メモリ:2GB
・OS:Windows7
・Visual Studio 2015 Community

■ホストPCの性能(参考)
・CPU:Intel Core i5-4250U
メモリ:8GB
ドライブ:SSD(Plextor PX-512M3P)
・OS:Windows10

◆デバッグ版とリリース版の設定差異
・デバッグ版は新規プロジェクト作成から変更なし。
・リリース版は新規プロジェクト作成からpdb未生成、ホスティングプロセス無効の計2箇所のみ変更。

・Debug版(デバッグ情報有り)


・Release版(デバッグ情報無し)


・Debug版(ホスティングプロセス有効)


・Release版(ホスティングプロセス無効)


ちなみに、【2】に関しては、VisualStudio上で実行するとブレークポイントが無くてもRelease版の方が早い。


2016年10月5日水曜日

【Win10】shutdownは 田+X,U,U(Winロゴキー+X,U,U)

 古いWin10ではWinロゴキー↑↑Enter↑↑Enterの順に押せばよかったが、Win10のAnniversaryにアップデートすると、スタートメニューが変わり、これができなくなる。
 Anniversaryでのシャットダウンは以下のショートカットキーが使える。

・Winロゴ+X、U、Uの順に押下※。
・Winロゴ+D、Alt+F4、Enterの順に押下※。
・Ctrl+Alt+Delete、↑、Enterの順に押下。

※Winロゴはキーボードの左下の方にある田のキー。(キーボードによっては無かったり、別の場所にあったりする。)

+X, U, U の順に押下