Ionic Frameworkとは何ですか?

Ionic Frameworkは、Webテクノロジー(HTML、CSS、およびJavaScript)を使用して、高性能で高品質のモバイルおよびデスクトップアプリを構築するためのオープンソースUIツールキットです。

Ionic Frameworkは、フロントエンドのユーザーエクスペリエンス、またはアプリのUIの操作(コントロール、操作、ジェスチャー、アニメーション)に重点を置いています。学習は簡単で、Angularなどの他のライブラリやフレームワークとうまく統合できます。または、単純なスクリプトインクルードを使用して、フロントエンドフレームワークなしでスタンドアロンで使用できます。

現在、Ionic FrameworkはAngularおよびReactと 公式に統合されて おり、Vueのサポート は開発中です。飛び込む前にIonic Frameworkについて詳しく知りたい場合は、基本を説明するビデオを 作成しました。

目標

クロスプラットフォーム

ネイティブiOS、Android、デスクトップ、プログレッシブWebアプリとしてのWebなど、複数のプラットフォームで動作するアプリをビルドしてデプロイします-すべて1つのコードベースで。一度書くだけで、どこでも実行できます。

Web標準ベース

Ionic Frameworkは信頼性の高い上に構築されており、 標準化されたWebテクノロジー:HTML、CSS、およびJavaScript。カスタム要素やシャドウDOMなどの最新のWeb APIを使用します。このため、Ionicコンポーネントには安定したAPIがあり、単一のプラットフォームベンダーの気まぐれではありません。

美しいデザイン

クリーンでシンプル、機能的。Ionic Frameworkは、すべてのプラットフォームですぐに動作して表示できるように設計されています。事前に設計されたコンポーネント、タイポグラフィ、インタラクティブなパラダイム、豪華な(まだ拡張可能な)基本テーマから始めます。

シンプルさ

Ionic Frameworkは、シンプルさを念頭に置いて構築されているため、Ionicアプリの作成は楽しく、学びやすく、Web開発スキルのある人なら誰でもアクセスできます。

ライセンス

Ionic Frameworkは無料のオープンソースプロジェクトであり、許可されているMITライセンスの下でリリースされています。これは、個人または商業プロジェクトで無料で使用できることを意味します。MITは、jQueryやRuby on Railsなどの一般的なプロジェクトで使用されているライセンスと同じです。

このドキュメントコンテンツ(ionic-docsリポジトリにあります)は、Apache 2ライセンスの下でライセンスされています。

イオンCLI

公式 イオンCLI、またはコマンドラインインターフェイスは、Ionicアプリの足場をすばやく構築し、Ionic開発者に役立つコマンドを多数提供するツールです。Ionicのインストールと更新に加えて、CLIには組み込みの開発サーバー、ビルドおよびデバッグツールなどが付属しています。あなたがいる場合にはイオン性Appflowのメンバー、CLIを構築し、展開雲を行い、アカウントを管理するために使用することができます。

フレームワークの互換性

Ionicの過去のリリースはAngularと緊密に結合されていましたが、フレームワークのV4は、Angularなどの最新のJavaScriptフレームワークの統合により、スタンドアロンWebコンポーネントライブラリとして機能するように再設計されました。Ionicは、ReactやVueを含むほとんどのフロントエンドフレームワークで使用できますが、一部のフレームワークではWebコンポーネントを完全にサポートするためにshimが必要です。

JavaScript

Ionic 4の主な目標の1つは、コンポーネントをホストするための単一のフレームワークに関する厳しい要件を削除することでした。これは、Webページ内のスクリプトタグだけでコアコンポーネントがスタンドアロンで動作できることを意味します。フレームワークでの作業は、大規模なチームや大規模なアプリには適していますが、WordPressのようなコンテキストでも、Ionicを単一ページのスタンドアロンライブラリとして使用できるようになりました。

角度

角度は常にIonicの素晴らしさの中心にあります。コアコンポーネントはスタンドアロンWebコンポーネントライブラリとして機能するように記述されてい@ionic/angularますが、このパッケージにより、Angularエコシステムとの統合が簡単になります。@ionic/angularAngular開発者がIonic 2/3から来ることを期待するすべての機能を含み、AngularルーターなどのコアAngularライブラリと統合します。

反応する

Ionicは現在、人気のあるReactライブラリを公式にサポートしています。Ionic Reactを使用すると、React開発者は既存のWebスキルを使用して、iOS、Android、Web、およびデスクトップをターゲットとするアプリを構築できます。では@ionic/react、あなたはすべてのコアイオンのコンポーネントを使用しますが、ネイティブを使用したような感じがコンポーネントに反応する方法ですることができます。

今後のサポート

他のフレームワークのサポートは、将来のリリースで予定されています。現在、Vueの公式バインディングが開発されていますが、これらのフレームワークでは一部のコンポーネントがそのまま使用できます。

Ionic Framework V4 +

Ionic Framework V4は、パフォーマンス、互換性、および全体的な拡張性に重点を置いた、プロジェクトの基盤となるテクノロジと機能の大きな進歩です。V4は@ionic/angularパッケージを通じてAngularと密接に統合されていますが、フレームワークに依存しないため、他のJavaScriptフレームワーク(Vue、React、Preactなど)で動作するか、フレームワークをまったく使用しません。

V4では、Web標準に移行することで、Ionicのコアが、フレームワーク固有のモデルではなく、最新のブラウザーでサポートされている標準コンポーネントモデルに依存できるようになりました。これにより、ロード時間が短縮され、パフォーマンスが向上し、全体的なコードが少なくなります。

イオンAppflow

ライフサイクル全体でIonicアプリを管理できるように、オープンソースフレームワークとは別のIonic Appflowと呼ばれる本番アプリ用の商用アプリプラットフォームも提供しています

Ionic Appflowは、開発者とチームがネイティブアプリビルドをコンパイルし、集中型ダッシュボードからIonicアプリにライブコード更新を展開するのに役立ちます。オプションの有料アップグレードは、ワークフローの自動化、シングルサインオン(SSO)、接続されたサービスおよび統合へのアクセスなどのより高度な機能に利用できます。

AppflowにはIonicアカウントが必要で、その機能のいくつかを試してみたい人には無料の「スターター」プランが付属しています。

生態系

Ionic Frameworkは、コアチームによってフルタイムで積極的に開発および維持され、そのエコシステムは、その成長と採用を促進する開発者と貢献者の国際コミュニティによって導かれます。小規模および大規模の開発者および企業は、Ionicを使用して、あらゆる場所で実行される驚くべきアプリを構築および出荷しています。

コミュニティに参加する

世界中の200か国以上に数百万人のIonic開発者がいます。参加する方法は次のとおりです。

  • フォーラム:質問をしたり、アイデアを共有したりするのに最適な場所です。
  • Slack:開発者がリアルタイムで会ってチャットできる活気のある場所。
  • Twitter:更新を投稿し、Ionicコミュニティからのコンテンツを共有します。
  • GitHub:バグの報告や新機能のリクエストについては、ここで問題を作成してください。PRを歓迎します!
  • コンテンツのオーサリング:技術的なブログを作成するか、ストーリーをIonicコミュニティと共有します。
갑자계산

C#で干支の計算式を建ててみよう

干支 : 十干と十二支を組み合わせた60を周期とする数詞です。

始まり

60の干支を並んだ資料は簡単に手に入れますが。これを直接計算する方法について押してくれる資料はあまりありません。

最近はJavaばかり学んでいるカリキュラムなので、もともとしていたC#をすべて忘れては困るので、C#の知識と経験を少しでも生かしてみないとと思いました。

それで、タイムアタックのように挑んでみました。目標は三十分です。

アプローチ

数学的に美しい方法を探るのもいいことですが、アレイの特徴を活用することにしました。

いつ見ても、また理解できるし、難しくないコードがいいですね。

まず、干支をアレイとして書いてみます。

static string[] gap = new string[] { "갑", "을", "병", "정", "무", "기", "경", "신", "임", "계" };
static string[] ja = new string[] { "자", "축", "인", "묘", "진", "사", "오", "미", "신", "유", "술", "해" };

ほら、簡単ですね。

具現

年度を干支に変換

연도를 간지로 바꿔봅시다. 생각하기 편하게 올해(2017, 정유년)를 기준으로 생각해봅니다.

올해와 목표년도의 차이를 구하고 단순히 각각의 배열 길이로 나누어 나머지를 구하면 됩니다. 그 다음 올해의 간지만큼 더해주면 끝이죠.

하지만 여기서 의외로 연도를 간지로 바꾸는데는 구현에 약간의 애를 먹었는데, 앞으로는 문제 없이 구하는데 거꾸로는 엉뚱한게 나왔던 것입니다.

고민하다가 원인은 음수 처리에 있었다는 걸 알았습니다.

private static string YearToGapja(int i) {
            int cha = i - 2017;
            //기준점을 2017년으로 잡아봅니다.
            int gap_cha = cha % gap.Length;
            int ja_cha = cha % ja.Length;

            gap_cha = (gap_cha + 3) % gap.Length;
            ja_cha = (ja_cha + 9) % ja.Length;

            if (gap_cha < 0) {
                gap_cha = gap.Length + gap_cha;
            }
            if (ja_cha < 0) {
                ja_cha = ja.Length + ja_cha;
            }

            return gap[gap_cha] + ja[ja_cha];
        }

음수 처리로 한 번 더 길이를 더해주면 끝에서부터 음수로 세어간 위치를 구할 수 있습니다. 이렇게 하면 몇 년이고 틀리지 않고 다 구할 수 있죠.

아, 여기서 옥에티가 있군요. 기원전을 지원하는 것입니다. 그냥 해서는 0년이 포함되어버리죠.

private static int MinusToBC(int i) {
    if(i < 0) { //
       return ++i;
    }else if(i == 0) {
       return int.MinValue;
    }
    return i;
}

이렇게 음수에 한해서는 입력값을 실제보다 1 더해주면 됩니다.

0을 입력한 경우를 별도로 구분하기 위해 int.MinValue로 넣어줬습니다. 이는 Int32 범위의 최하 숫자를 가져올 수 있는데, 이걸로 무효한 숫자로 처리하면 크게 유용성에 간섭받지 않고 잘 확인할 수 있습니다.

int.MinValue와 같으면 계산을 포기한다는 구절을 넣는 것으로 충분하죠.

한편, Console.ReadLine()을 통해 입력값을 받는 만큼, 이것이 유효한 숫자인지 판별할 필요가 있습니다.

do {
    Console.Write("희망 서력 년도(기원전=>마이너스) 또는 갑자 입력: ");
    input = Console.ReadLine();
} while ((!int.TryParse(input, out i) && !isGapja(input)) || input == "0");

이를 통해 input이 숫자가 아니거나, 갑자가 아닌 경우 또는 0을 입력한 경우 모두 다시 읽으려고 시도하겠군요.

int.TryParse는 Parse 실패시에 out i 값이 0이라고 합니다.

private static bool isGapja(string input) {
            if (input.Length == 2) {
                if(!(gap.Contains(input[0].ToString()))) {
                    return false;
                }
                if (!(ja.Contains(input[1].ToString()))) {
                    return false;
                }
                return true;
            }
            return false;
        }

위에서 등장한 IsGapja라는 함수를 만들어두면 반복문을 돌 때 input의 간지 유효성을 확인하고 실제 갑자를 갖고 있는지 확인할 수 있습니다.

결과 확인

여기까지 하면 결과가 시원하게 나옵니다.

희망 서력 년도(기원전=>마이너스) 또는 갑자 입력: 2017
결과: 정유

희망 서력 년도(기원전=>마이너스) 또는 갑자 입력: 2002
결과: 임오

희망 서력 년도(기원전=>마이너스) 또는 갑자 입력: 1988
결과: 무진

와우. 이렇게 하였더니 목표 30분보다 10분 빠르게 끝났네요.

하지만 15분 냉장고를 부탁해 컷에는 실패했습니다. 다음에는 분발합시다!

간지를 연도로

그 다음으로 간지를 연도로 변환해보겠습니다. 60년마다 돌아오니까 환갑이라고 하죠. 아무래도 여러 번 있을 것이고, 시작 년도와 끝 년도도 중요하겠죠.

유연한 적용을 위해서 일부러 시작 년도와 끝 년도를 매개변수로 받았습니다.

그리고 이제 끝없는 고민을 합니다. 위의 간지 변환 사례를 역으로 방정식을 세우면 그만일까요?

아무래도 머리가 핑핑돌고 문제가 생겼을 때 어떤 방식을 써야할지 수알못에게 두려움이 비칩니다.

이럴 때는 컴퓨터를 믿고 왕도를 걸어보기로 합니다. 사실 목표 간지와 기준 간지의 차이만큼을 알고 있으면 그 +/- 60만 반복하면 결과가 됩니다.

그러나 목표 간지와 기준 간지 차이를 구하기엔, 쉽지 않습니다. 계산식을 더 많이 고민하면 되겠지만요.

수학자가 아닌 프로그래머에겐, 코드가 더 많은 답을 줍니다.

private static string[] EnumGapja() {
            string[] s = new string[60];
            for(int i = 0; i < 60; i++) {
                s[i] = gap[i % gap.Length] + ja[i % ja.Length];
            }
            return s;
        }

이렇게 하면 위 전역변수를 활용하여 60갑자가 뚝딱 완성됩니다.

나머지가 끊임없이 찬조출연하더군요.

private static int[] GapjaToYear(string input, int min, int max) {
            string[] eg = EnumGapja();
            int ifrom_gapja=Array.IndexOf(eg, input);

            //서기 4년은 갑자년.
            //4*60n=갑자
            //4*60n+ifrom_gapja=갑자+ifrom_gapja
            List ilist = new List();
            for (int i = 4 + ifrom_gapja; i < max; i+=60) {
                if(i < min) {
                    continue;
                }
                ilist.Add(i);
            }

            return ilist.ToArray();
        }

이제 한없이 길어지던 코드가 한결 간결해졌습니다.

단순히 60갑자 목록에서 index의 차이만 알게 되는 것으로 추가해야할 년도를 알 수 있습니다.

갑자년이 서기 4년이라고 합니다. 이 갑자년과의 차이 = index라는 걸 쉽게 예측할 수 있으니까요.

min년부터 max년까지 반복문을 돌리면 그만이겠습니다.

희망 서력 년도(기원전=>마이너스) 또는 갑자 입력: 무자
28년 88년 148년 208년 268년 328년 388년 448년 508년 568년 628년 688년 748년 808년 868년 928년 988년 1048년 1108년 1168년 1228년 1288년 1348년 1408년 1468년 1528년 1588년 1648년 1708년 1768년 1828년 1888년 1948년 2008년 2068년 2128년 2188년 2248년 2308년 2368년 2428년 2488년 2548년 2608년 2668년 2728년 2788년 2848년 2908년 2968년 3028년 3088년 3148년 3208년 3268년 3328년 3388년 3448년 3508년 3568년 3628년 3688년 3748년 3808년 3868년 3928년 3988년 4048년 4108년 4168년 4228년 4288년 4348년 4408년 4468년 4528년 4588년 4648년 4708년 4768년 4828년 4888년 4948년 5008년 5068년 5128년 5188년 5248년 5308년 5368년 5428년 5488년 5548년 5608년 5668년 5728년 5788년 5848년 5908년 5968년 6028년 6088년 6148년 6208년 6268년 6328년 6388년 6448년 6508년 6568년 6628년 6688년 6748년 6808년 6868년 6928년 6988년 7048년 7108년 7168년 7228년 7288년 7348년 7408년 7468년 7528년 7588년 7648년 7708년 7768년 7828년 7888년 7948년 8008년 8068년 8128년 8188년 8248년 8308년 8368년 8428년 8488년 8548년 8608년 8668년 8728년 8788년 8848 년 8908년 8968년 9028년 9088년 9148년 9208년 9268년 9328년 9388년 9448년 9508년 9568년 9628년 9688년 9748년 9808년 9868년 9928년 9988년

:)

전체 소스

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace Gapja {
    class Program {
        //4 5 6 7 8 9 0 1 2 3
        static string[] gap = new string[] { "갑", "을", "병", "정", "무", "기", "경", "신", "임", "계" };
        static string[] ja = new string[] { "자", "축", "인", "묘", "진", "사", "오", "미", "신", "유", "술", "해" };
        static void Main(string[] args) {
            //2017 = 정유년 = 4/9

            while (true) {
                string input = ""; int i = 0;
                do {
                    Console.Write("희망 서력 년도(기원전=>마이너스) 또는 갑자 입력: ");
                    input = Console.ReadLine();
                } while ((!int.TryParse(input, out i) && !isGapja(input)) || input == "0");
                i = MinusToBC(i);
                if(i != int.MinValue) {
                    Console.WriteLine("결과: {0}", YearToGapja(i));
                } else {
                    var result = GapjaToYear(input, 1, 10000);
                    foreach(var r in result) {
                        Console.Write("{0}년 ",r);
                    }
                }
                Console.WriteLine();
            }
        }

        private static bool isGapja(string input) {
            if (input.Length == 2) {
                if(!(gap.Contains(input[0].ToString()))) {
                    return false;
                }
                if (!(ja.Contains(input[1].ToString()))) {
                    return false;
                }
                return true;
            }
            return false;
        }

        private static int MinusToBC(int i) {
            if(i < 0) { //
                return ++i;
            }else if(i == 0) {
                return int.MinValue;
            }
            return i;
        }

        private static string YearToGapja(int i) {
            int cha = i - 2017;
            //기준점을 2017년으로 잡아봅니다.
            int gap_cha = cha % gap.Length;
            int ja_cha = cha % ja.Length;

            gap_cha = (gap_cha + 3) % gap.Length;
            ja_cha = (ja_cha + 9) % ja.Length;

            if (gap_cha < 0) {
                gap_cha = gap.Length + gap_cha;
            }
            if (ja_cha < 0) {
                ja_cha = ja.Length + ja_cha;
            }

            return gap[gap_cha] + ja[ja_cha];
        }

        private static string[] EnumGapja() {
            string[] s = new string[60];
            for(int i = 0; i < 60; i++) {
                s[i] = gap[i % gap.Length] + ja[i % ja.Length];
            }
            return s;
        }

        private static int[] GapjaToYear(string input, int min, int max) {
            string[] eg = EnumGapja();
            int ifrom_gapja=Array.IndexOf(eg, input);

            //서기 4년은 갑자년.
            //4*60n=갑자
            //4*60n+ifrom_gapja=갑자+ifrom_gapja
            List ilist = new List();
            for (int i = 4 + ifrom_gapja; i < max; i+=60) {
                if(i < min) {
                    continue;
                }
                ilist.Add(i);
            }

            return ilist.ToArray();
        }
    }
}

正規表現レファレンス

はじめ

正規表現(Regular Expression)とは特定の規則を持っている文字列の集合を表現するのに使われている形式言語です。これをうまく使いこなせるなら、コードのラインが確実に減るに違いありません。これから、正規表現の使用例を具体的に調べて、活用方案の例を見てみましょう。

使用例

  • PHPのpreg_match関数
    • $subject = "abcdef";
      $pattern = '/^def/';
      preg_match($pattern, $subject, $matches, PREG_OFFSET_CAPTURE, 3);
      print_r($matches);
      
  • C#のRegExクラス
    • class TestRegularExpressionValidation
      {
          static void Main()
          {
              string[] numbers = 
              {
                  "123-555-0190", 
                  "444-234-22450", 
                  "690-555-0178", 
                  "146-893-232",
                  "146-555-0122",
                  "4007-555-0111", 
                  "407-555-0111", 
                  "407-2-5555", 
              };
      
              string sPattern = "^\\d{3}-\\d{3}-\\d{4}$";
      
              foreach (string s in numbers)
              {
                  System.Console.Write("{0,14}", s);
      
                  if (System.Text.RegularExpressions.Regex.IsMatch(s, sPattern))
                  {
                      System.Console.WriteLine(" - valid");
                  }
                  else
                  {
                      System.Console.WriteLine(" - invalid");
                  }
              }
      
              // Keep the console window open in debug mode.
              System.Console.WriteLine("Press any key to exit.");
              System.Console.ReadKey();
          }
      }
      /* Output:
            123-555-0190 - valid
           444-234-22450 - invalid
            690-555-0178 - valid
             146-893-232 - invalid
            146-555-0122 - valid
           4007-555-0111 - invalid
            407-555-0111 - valid
              407-2-5555 - invalid
      */
  • JavaのRegExクラス
    • import java.util.regex.Matcher;
      import java.util.regex.Pattern;
      
      public class RegexMatches {
      
         public static void main( String args[] ) {
            // String to be scanned to find the pattern.
            String line = "This order was placed for QT3000! OK?";
            String pattern = "(.*)(\\d+)(.*)";
      
            // Create a Pattern object
            Pattern r = Pattern.compile(pattern);
      
            // Now create matcher object.
            Matcher m = r.matcher(line);
            if (m.find( )) {
               System.out.println("Found value: " + m.group(0) );
               System.out.println("Found value: " + m.group(1) );
               System.out.println("Found value: " + m.group(2) );
            }else {
               System.out.println("NO MATCH");
            }
         }
      }
  • javascriptのリテラルまたは、RegExpクラス
    • var myRe = /d(b+)d/g;
      var myArray = myRe.exec("cdbbdbsbz");
  • URL Rewrite Rule
    • rewrite ^/blog/sitemap(-+([a-zA-Z0-9_-]+))?\.xml$ "/blog/sitemap$2.xml" last;
      rewrite ^/blog/sitemap(-+([a-zA-Z0-9_-]+))?\.xml$ "/blog/index.php?xml_sitemap=params=$2" last;
      rewrite ^/blog/sitemap(-+([a-zA-Z0-9_-]+))?\.xml\.gz$ "/blog/index.php?xml_sitemap=params=$2;zip=true" last;
      rewrite ^/blog/sitemap(-+([a-zA-Z0-9_-]+))?\.html$ "/blog/index.php?xml_sitemap=params=$2;html=true" last;
      rewrite ^/blog/sitemap(-+([a-zA-Z0-9_-]+))?\.html.gz$ "/blog/index.php?xml_sitemap=params=$2;html=true;zip=true" last;

レファレンス

続きを読む “正規表現レファレンス”

TickCount 研究

物事の始まり

この疑問が出たのは、ただString(あるいはC#などの言語ではstringとも言えます。以下string)とStringBuilder、StringBufferの性能の違いに対する初心者向けの話でした。

stringは実際には読み出し専用であることは、素人以外は大体知っている事実です。immutableと言いますが、変わらないということです。よって、『+』や『concat』をすると、stringはそのたびに、新しいインスタンスをリターンするようになります。

C++では、strcat()で文字列を修正するとき、あらかじめ大きいスペースを割り当てると、速度が飛躍的に上がるといいますが、C#とJavaでは文字列の修正は難しいので、ほかの方法が必要になり、それはMSDNにも出ています。JavaとC#が似ていることは、コピーしたらすぐわかるところですが。

効果はあるのか

それで、その違いはどうやって判りますか。Javaの先生は文字列を作り出し続けるコードを出して、「まぁ、全然変わらないじゃん」と言いましたが、実際に大きい差があることはある程度、コードからも把握できます。

/* string performance in C# */
            string me = "";
            Console.WriteLine("String START");
            var cnt = Environment.TickCount;
            for (int i = 0; i < 100000; i++)
            {
                me += "a";
            }
            Console.WriteLine("Time: "+(Environment.TickCount - cnt).ToString());
            Console.WriteLine("END");
            //Console.WriteLine(me);

            StringBuilder sb = new StringBuilder();
            Console.WriteLine("StringBuilder START");
            cnt = Environment.TickCount;
            for (int i = 0; i < 100000; i++)
            {
                sb.Append("a");
            }
            Console.WriteLine("Time: " + (Environment.TickCount - cnt).ToString());
            Console.WriteLine("END");

C# コード

/* string performance */
        String me = "";
        System.out.println("String START");
        long cnt = System.currentTimeMillis();
        for (int i = 0; i < 100000; i++)
        {
            me += "a";
        }
        System.out.println("Time: "+(System.currentTimeMillis() - cnt));
        System.out.println("END");

        StringBuffer sb = new StringBuffer();
        System.out.println("StringBuffer START");
        System.out.printf("capacity: %d, length: %d\n", sb.capacity(), sb.length());
        cnt = System.currentTimeMillis();
        for (int i = 0; i < 100000; i++)
        {
            sb.append("a");
        }
        System.out.println("Time: " + (System.currentTimeMillis() - cnt));
        System.out.printf("capacity: %d, length: %d\n", sb.capacity(), sb.length());
        System.out.println("END");

Javaコードは次のように結果が出ました。

String START
Time: 7467
END
StringBuffer START
capacity: 16, length: 0
Time: 9
capacity: 294910, length: 200000
END

中途半端なもんではないですね。10万個のStringにつけながら、7秒以上かかりました。一方、 C#は長ければ、5秒以内程度で、コンパイラー最適化のすごさを分かります。StringBufferとStringBuilderも10msを下回り、0msが出る時もありました。刹那と言っても、過言ではありません。

さすがに、Heapをどれほど、使い切ったり、新しいメモリーの割り当てを避けて、有効に扱うのが大切なのか、目に見える実験でした。

浮かび始める疑問

面白いところは、JavaのStringBufferとは違って、C#のStringBuilderの所要時間が0msと出たところです。コード一つでもなく、10万なのに果たしてできるのか?誤差はないのか?気になりました。

早速、Environment.TickCountを疑いました。実はTickCountでコードのパフォーマンスを比較する時代は、マルチプロセッサの到来で、もう過ぎたという意見もあります。コードの後先はあっても、働くタイミングは異なるかも知らないという、スレッドの危険が日常化した時代ですからね。

C#のゆーざーはどういう方法を使っているのでしょうか。

  • Environment.TickCount
  • GetTickCount / GetTickCount64
  • DateTime.Now(UtcNow).Ticks
  • Stopwatch
  • static Stopwatch.GetTimestamp

Environment.TickCount

簡単で、使いやすいのはEnvironment static classのメンバーであるTickCountです。パソコンのアップタイムをミリセカンドで見せるGetTickCount()とはほぼ同じ機能をしていて、余計に符号が付く整数(int)なので、24.9日以上つけっぱなしのパソコンでは問題が発生します。Int32.MaxValueを超えるとInt32.MinValueに戻ってから始めるらしいです。当然、差を求めると、符号が逆になってしまいます。MSDNではInt32.MaxValueとの&(AND)演算で一時解決したようです。(Math.Abs()より早く符号がなくなりますね。)

GetTickCount64

VistaからできたWin32 APIであるGetTickCount64は符号なしlongタイプでリターンします。システムが始まった時間から積み重ねていくミリセカンドの数字を表すには、十分な長さだと言えるでしょう。しかし、本当に遊ばないで、たとえば、節電、ハイバネーションなどなく、起動時間を求めるには、QueryUnbiasedInterruptTimeが必要となります。

DateTime.Now(UtcNow).Ticks

西暦1年1月1日午前零時から、100ナノセカンドで経った時間を表します。うるう秒などはなく、本当の時間とは差が少なくないらしいです。

Stopwatch

Stopwatchはnewキーワードなく、インスタンス化できるStaticメソッドであるStartNew()を使って、時間を図り始め、Stop()を呼び出した後、ElapsedMillisecondsなどのプロパティで経過時間がわかるようになります。

  • IsHighResolutionプロパティは現在パソコンのハードウェアが別のハイレゾ性能カウンター機能があるかどうかを確認できる読み出し専用のブール属性です。今どきのパソコンはほとんどTrueがリターンされるようです。
  • Frequencyプロパティはタイマーのtick周期をリターンします。
    long frequency = Stopwatch.Frequency;
        Console.WriteLine("  Timer frequency in ticks per second = {0}",
            frequency);
        long nanosecPerTick = (1000L*1000L*1000L) / frequency;
        Console.WriteLine("  Timer is accurate within {0} nanoseconds", 
            nanosecPerTick);
    • このようなコードで、Stopwatchの正確度があらわれます。
        Timer frequency in ticks per second = 2343750
        Timer is accurate within 426 nanoseconds

static Stopwatch.GetTimestamp

Stopwatchのインスタンス化後の時間も無駄だとみる一部のユーザーはGetTimestamp static メソッドの性能に注目をしました。

  • ハイレゾ性能カウンターがあるときには、現在値をリターンします。
  • 一般のシステム時計が使われている場合、DateTime.Ticksと同じ結果が出ます。

ベンチマーク

Repeating measurement 3 times in loop of 10,000,000:

Measured: GetTickCount64() [ms]: 228
Measured: Environment.TickCount [ms]: 50
Measured: DateTime.UtcNow.Ticks [ms]: 83
Measured: Stopwatch: .ElapsedMilliseconds [ms]: 856
Measured: static Stopwatch.GetTimestamp [ms]: 476
Measured: Stopwatch+conversion to DateTime [ms]:  811

Measured: GetTickCount64() [ms]: 219
Measured: Environment.TickCount [ms]: 50
Measured: DateTime.UtcNow.Ticks [ms]: 84
Measured: Stopwatch: .ElapsedMilliseconds [ms]: 847
Measured: static Stopwatch.GetTimestamp [ms]: 465
Measured: Stopwatch+conversion to DateTime [ms]:  799

Measured: GetTickCount64() [ms]: 235
Measured: Environment.TickCount [ms]: 51
Measured: DateTime.UtcNow.Ticks [ms]: 84
Measured: Stopwatch: .ElapsedMilliseconds [ms]: 851
Measured: static Stopwatch.GetTimestamp [ms]: 470
Measured: Stopwatch+conversion to DateTime [ms]:  791

Compare that with DateTime.Now.Ticks [ms]: 1150

General Stopwatch information:
- Using high-resolution performance counter for Stopwatch class.
- Stopwatch accuracy- ticks per microsecond (1000 ms): 2.3
 (Max. tick resolution normally is 100 nanoseconds, this is 10 ticks/microsecond.)
- Approximated capacity (maxtime) of TickCount [dd:hh:mm:ss] 25:20:31:23

Done.

シングルスレッドのみ使うように設定したうえ、スレッドの優先順位を高めて、Stopwatchを利用し、1000万会呼び出しにそれぞれかかった時間です。

TickCountが共通的に早いのかと思うと、StopwatchはElapsedMillisecondsの表示のための手順もあるので、 表示をせずに、ただStart()とStop()を繰り返すのにどうかを確認した方がよかったかなと思いました。

ソースの出所: https://stackoverflow.com/questions/243351/environment-tickcount-vs-datetime-now

それで?

GetTickCountは混沌と混沌の歴史です。まさか、20余日パソコンをつけっぱなしにするあほがいるかと思った先人の誤りでしょうね。DWORDとして、符号をなくしてせいぜい40余日です。

普段使うことで問題はなさそうですが、何が長くかかり、メソッドやクラスの特徴さえ知っておけば、長引く作業を後回しもでき、パフォーマンスも向上できると思います。

これからも、気になることはぜひ実験してみないとわからないのが当然だということでしょう。