e-Taxの平成25年分(2013年分)確定申告書等作成コーナーの先物取引(日経225先物など)の入力を支援するAutoItスクリプト。
去年のものとまったく同じ。
使い方は2013年のエントリ、2012年のエントリ、2011年のエントリを参照。
スクリプトはetax-future-2013.au3。
2014/02/05
2013/03/20
e-Tax平成24年分(2012年分)確定申告の先物取引のAutoItによる自動入力
e-Taxの平成24年分(2012年分)確定申告書等作成コーナーの先物取引(日経225先物など)の入力を支援するAutoItスクリプト。
去年のものとまったく同じ。 使い方は去年のエントリ、一昨年のエントリを参照。
スクリプトはetax-future-2012.au3。
去年のものとまったく同じ。 使い方は去年のエントリ、一昨年のエントリを参照。
スクリプトはetax-future-2012.au3。
2012/03/19
e-Tax平成23年分(2011年分)確定申告の先物取引のAutoItによる自動入力
e-Taxの平成23年分(2011年分)確定申告書等作成コーナーの先物取引(日経225先物など)の入力を支援するAutoItスクリプト。
基本的に去年のものと同じ。 違うのは最後の一件を入力した後に次に進まないようにしたことだけ(e-Taxの使い勝手はホントにアレ)。使い方は去年のエントリを参照。
スクリプトはetax-future-2011.au3。
基本的に去年のものと同じ。 違うのは最後の一件を入力した後に次に進まないようにしたことだけ(e-Taxの使い勝手はホントにアレ)。使い方は去年のエントリを参照。
スクリプトはetax-future-2011.au3。
2011/01/23
e-Tax平成22年分(2010年分)確定申告の先物取引と配当のAutoItによる自動入力
e-Taxの平成22年分(2010年分)確定申告書等作成コーナーの先物取引(日経225先物など)と上場株式の配当の入力を支援するAutoItスクリプトを書いた。
動機はただ単純に手動で入力するのが面倒だったから。特に「先物取引に係る雑所得等」は最大で990件(330ページ * 3件)入力可能にされているのに、構造化したデータをアップロードするなどの方式が提供されずにWebブラウザで手動での入力が想定されているのはおかしい。面倒ってレベルじゃない。
去年もAutoItのスクリプトを書いたけど、去年のものはキー入力やマウス操作の命令ばかり使ったもので処理が遅くて不安定で良い出来ではなかった。今年はIE操作用のUDF "IE.au3"を使ってそれなりにまともなものができたと思うので晒してみる。
動作を確認した環境は次の通り。
内容の簡単な説明としては、普通にInternetExplorerを操作してデータを入力するページ(「先物取引に係る雑所得等」ページとか)を開いた状態にしてAutoItのスクリプトを実行するというのが主操作手順。そうするとAutoItがデータ(タブ区切りテキスト形式で事前に作成しておく)を入力してくれて、それが完了したらまた自分でInternetExplorerを操作してデータを保存するとか次の処理に進んだりできる。
AutoItで処理するのはデータの入力部分だけで、保存しておいた確定申告書データを読み込んで作成を再開するとかデータの入力後に確定申告書データを保存するとかの処理は、自然に手動で操作するだけで済むようになっている(AutoItで処理するよりも柔軟かつ分かりやすい)。またAutoItで処理する範囲が小さくて済むためAutoItのスクリプトも簡単になっている。
処理の所要時間は私の環境で1件の入力に約0.8秒だった。まあこんなものだと思う。
オンライン送信ではなく郵送すれば済む話だけど、折角可能なんだからオンライン送信に固執するつもりで書いた。
テンプレートはetax-future-sample.xlsx。
入力項目は「【確定申告書作成コーナー】-先物取引に係る雑所得等」ページ(https://www.keisan.nta.go.jp/h22/syotoku/ta_subB62.jsp)の取引の内訳と全く同じ。
etax-future-sample.xlsxの列順でデータを作成してタブ区切りテキスト形式で保存して、項目の説明である先頭の2行を削除すればいい。入力データのサンプルはetax-future-sample.txt。
入力データ作成時の注意点:
その状態でAutoItのスクリプトetax-future-2010.au3を実行する。
スクリプトの10行目付近の$filename変数が入力データ(タブ区切りテキスト形式)のファイル名。
とりあえず次のようにしてある。この場合etax-future-2010.txtという名前のファイルが読み込まれる。
1件入力するごとに進捗を標準出力に書き出すのでそれを確認できる環境でスクリプトを実行するのが良い。
コマンドラインでもいいし、AutoIt Script Editor(SciTE4AutoIt3)(AutoIt Full Installationに含まれる)でスクリプトを開いてメニューの「Tools」-「Go」で実行すると、エディタのOutputペインが開いてそれが標準出力になるので、スクリプトを変更して実行する場合にはこれが便利。
スクリプトやテンプレートとサンプルはksksts / junk / source – Bitbucketのetax-dividend-*という名前のファイル。
対象としているのは「【確定申告書作成コーナー】-配当所得、配当控除(上場株式等)」ページ(https://www.keisan.nta.go.jp/h22/syotoku/ta_subX5b.jsp)への入力。
これは「配当所得、配当控除(取引区分の選択)」ページで「1 上場株式等」-「(2) 源泉徴収口座への受入れを行っていない配当等」を選択したケースのこと。
動機はただ単純に手動で入力するのが面倒だったから。特に「先物取引に係る雑所得等」は最大で990件(330ページ * 3件)入力可能にされているのに、構造化したデータをアップロードするなどの方式が提供されずにWebブラウザで手動での入力が想定されているのはおかしい。面倒ってレベルじゃない。
去年もAutoItのスクリプトを書いたけど、去年のものはキー入力やマウス操作の命令ばかり使ったもので処理が遅くて不安定で良い出来ではなかった。今年はIE操作用のUDF "IE.au3"を使ってそれなりにまともなものができたと思うので晒してみる。
動作を確認した環境は次の通り。
- Windows 7 Pro. x64
- InternetExplorer 8
- AutoIt v3 (v3.3.6.1)
- 2011/01/22時点のe-Tax平成22年分確定申告書作成コーナー
内容の簡単な説明としては、普通にInternetExplorerを操作してデータを入力するページ(「先物取引に係る雑所得等」ページとか)を開いた状態にしてAutoItのスクリプトを実行するというのが主操作手順。そうするとAutoItがデータ(タブ区切りテキスト形式で事前に作成しておく)を入力してくれて、それが完了したらまた自分でInternetExplorerを操作してデータを保存するとか次の処理に進んだりできる。
AutoItで処理するのはデータの入力部分だけで、保存しておいた確定申告書データを読み込んで作成を再開するとかデータの入力後に確定申告書データを保存するとかの処理は、自然に手動で操作するだけで済むようになっている(AutoItで処理するよりも柔軟かつ分かりやすい)。またAutoItで処理する範囲が小さくて済むためAutoItのスクリプトも簡単になっている。
処理の所要時間は私の環境で1件の入力に約0.8秒だった。まあこんなものだと思う。
オンライン送信ではなく郵送すれば済む話だけど、折角可能なんだからオンライン送信に固執するつもりで書いた。
先物取引のデータの入力支援のスクリプトの使い方
入力データの作成
入力する先物取引の内訳をタブ区切りテキスト形式で作成する。テンプレートはetax-future-sample.xlsx。
入力項目は「【確定申告書作成コーナー】-先物取引に係る雑所得等」ページ(https://www.keisan.nta.go.jp/h22/syotoku/ta_subB62.jsp)の取引の内訳と全く同じ。
etax-future-sample.xlsxの列順でデータを作成してタブ区切りテキスト形式で保存して、項目の説明である先頭の2行を削除すればいい。入力データのサンプルはetax-future-sample.txt。
入力データ作成時の注意点:
- 正しいデータを作成すること
- 作成されたデータはそのまま入力ページへの入力に使われる
- スクリプトではデータのバリデーションをしていない。おかしなデータが含まれていると入力ページのバリデーション処理によりメッセージを通知するダイアログが表示されるため、データの入力処理がそこで止まってしまう。
スクリプトの実行
InternetExplorerを操作して(保存しておいた確定申告書データから作成を再開するなどして)「【確定申告書作成コーナー】-先物取引に係る雑所得等」ページ(https://www.keisan.nta.go.jp/h22/syotoku/ta_subB62.jsp)を開いておく。その状態でAutoItのスクリプトetax-future-2010.au3を実行する。
スクリプトの10行目付近の$filename変数が入力データ(タブ区切りテキスト形式)のファイル名。
とりあえず次のようにしてある。この場合etax-future-2010.txtという名前のファイルが読み込まれる。
;$filename = "etax-future-sample.txt" ; 取引の内訳データのファイル名(タブ区切りテキストファイル) $filename = "etax-future-2010.txt" ; 取引の内訳データのファイル名(タブ区切りテキストファイル)
1件入力するごとに進捗を標準出力に書き出すのでそれを確認できる環境でスクリプトを実行するのが良い。
コマンドラインでもいいし、AutoIt Script Editor(SciTE4AutoIt3)(AutoIt Full Installationに含まれる)でスクリプトを開いてメニューの「Tools」-「Go」で実行すると、エディタのOutputペインが開いてそれが標準出力になるので、スクリプトを変更して実行する場合にはこれが便利。
配当のデータの入力支援のスクリプトの使い方
構成や使い方は先物取引のデータ入力と同じなので省略。スクリプトやテンプレートとサンプルはksksts / junk / source – Bitbucketのetax-dividend-*という名前のファイル。
対象としているのは「【確定申告書作成コーナー】-配当所得、配当控除(上場株式等)」ページ(https://www.keisan.nta.go.jp/h22/syotoku/ta_subX5b.jsp)への入力。
これは「配当所得、配当控除(取引区分の選択)」ページで「1 上場株式等」-「(2) 源泉徴収口座への受入れを行っていない配当等」を選択したケースのこと。
2010/09/29
C#の構造体の引き渡し方によるパフォーマンスの違い
2016/02/20追記--
yosolaさんの指摘の通り調査用のコードにミスがありました。
このエントリの結果は間違いです。調査用のコードを修正したエントリをご覧ください。
--
メソッドの引数として構造体を渡す場合にちょっと気になったので調査用のコードを書いた。
比較したのは次の3つのケース。
結果は次の通り。
インターフェース経由が遅いのは疑問。型変換的な処理が入ってしまうのかな。
メソッドの引数に構造体を渡す場合に(変更しないのに)ref修飾子を使いまくるか、素直にコピーを発生させるか迷う。
yosolaさんの指摘の通り調査用のコードにミスがありました。
このエントリの結果は間違いです。調査用のコードを修正したエントリをご覧ください。
--
メソッドの引数として構造体を渡す場合にちょっと気になったので調査用のコードを書いた。
比較したのは次の3つのケース。
- 構造体を値渡し
- 構造体を参照渡し(ref修飾子)
- 構造体が実装するインターフェースで渡す
using System; using System.Collections.Generic; using System.Diagnostics; using System.Linq; using System.Text; namespace StructInterface { interface ITuple { int X { get; set; } int Y { get; set; } int Z { get; set; } } struct Point : ITuple { public int X { get; set; } public int Y { get; set; } public int Z { get; set; } public Point(int x, int y, int z) : this() { X = x; Y = y; Z = z; } public void AddStruct(Point point) { X += point.X; Y += point.Y; Z += point.Z; } public void AddRefStruct(ref Point point) { X += point.X; Y += point.Y; Z += point.Z; } public void AddInterface(ITuple tuple) { X += tuple.X; Y += tuple.Y; Z += tuple.Z; } public override bool Equals(object obj) { if (!(obj is Point)) return false; var p = (Point)obj; var result = X == p.X && Y == p.Y && Z == p.Z; return result; } public override int GetHashCode() { var result = X ^ Y ^ Z; return result; } public override string ToString() { var str = string.Format("{0}, {1}, {2}, ", X, Y, Z); return str; } } class Program { static void Main(string[] args) { for (var size = 1000; size <= 1000000; size *= 10) Run(size); } static void Run(int size) { Console.WriteLine("size: {0}", size); var count = 10; var sw = new Stopwatch(); var structTicks = 0L; var refStructTicks = 0L; var interfaceTicks = 0L; for (var c = 0; c <= count; c++) { var random = new Random(count); var points = Enumerable.Range(0, size) .Select(_ => new Point(random.Next(), random.Next(), random.Next())) .ToListksksts / junk / source — bitbucket.org(); // Point.AddStruct sw.Reset(); sw.Start(); var sp = new Point(); foreach (var point in points) sp.AddStruct(point); sw.Stop(); structTicks += sw.ElapsedTicks; // Point.AddRefStruct sw.Reset(); sw.Start(); var rsp = new Point(); foreach (var point in points) { var p = new Point(point.X, point.Y, point.Z); rsp.AddRefStruct(ref p); } sw.Stop(); refStructTicks = sw.ElapsedTicks; // Point.AddInterface sw.Reset(); sw.Start(); var ip = new Point(); foreach (var point in points) ip.AddInterface(point); sw.Stop(); interfaceTicks += sw.ElapsedTicks; // correctness if (!sp.Equals(rsp)) Console.WriteLine("!sp.Equals(rsp)"); if (!sp.Equals(ip)) Console.WriteLine("!sp.Equals(ip)"); } Console.WriteLine("AddStruct: {0} ({1:F})", structTicks / count, structTicks / (double)structTicks); Console.WriteLine("AddRefStruct: {0} ({1:F})", refStructTicks / count, refStructTicks / (double)structTicks); Console.WriteLine("AddInterface: {0} ({1:F})", interfaceTicks / count, interfaceTicks / (double)structTicks); } } }
結果は次の通り。
size: 1000 AddStruct: 832 (1.00) AddRefStruct: 31 (0.04) AddInterface: 789 (0.95) size: 10000 AddStruct: 1023 (1.00) AddRefStruct: 119 (0.12) AddInterface: 1803 (1.76) size: 100000 AddStruct: 9916 (1.00) AddRefStruct: 1100 (0.11) AddInterface: 16277 (1.64) size: 1000000 AddStruct: 102814 (1.00) AddRefStruct: 11699 (0.11) AddInterface: 159081 (1.55)感想としては、はじめ参照渡しが早いことを意外に感じた(値型と参照型のインスタンス作成の速度差から値型のインスタンスのコピーはそれほどの負荷にならないと思っていた)けど、コピーを発生させるよりは参照渡しにした方が軽いということは納得できた。
インターフェース経由が遅いのは疑問。型変換的な処理が入ってしまうのかな。
メソッドの引数に構造体を渡す場合に(変更しないのに)ref修飾子を使いまくるか、素直にコピーを発生させるか迷う。
2010/03/21
Google Chromeの検索エンジンの設定
Firefoxから移行してある程度経ったのにGoogle Chromeのデフォルトの検索エンジンの設定が気に入らない。悪いというわけじゃなくてFirefoxを使っていた時と同じような検索結果が欲しいから、Google Chromeの検索エンジンの設定を変更してみた。
まずはデフォルトのGoogleでの検索エンジン設定を削除する。
追加するのはGoogleの日本語版と英語版の2つ。
日本語版(google.co.jp):
まずはデフォルトのGoogleでの検索エンジン設定を削除する。
追加するのはGoogleの日本語版と英語版の2つ。
日本語版(google.co.jp):
- 名前とキーワード: google.co.jp
- URL: http://www.google.co.jp/search?q=%s&hl=jp&lr=lang_ja&safe=off&ie=utf-8&oe=utf-8&aq=t
- 名前とキーワード: google.com
- URL: http://www.google.com/search?q=%s&hl=en&safe=off
2010/01/23
MSTestExpressionAssertion - VisualStudioの単体テスト用のGallio(MbUnit)のAssertEx.Thatの改造版
VisualStudio 2008の単体テスト機能のカスタマイズ(VS2008 custom assertion example)の続き。
これも放置していたけど一段落つけるまでやった。
前のエントリで書いた通りGallioのAssertEx.Thatで構成要素の式ををキャプチャしてその結果を出力できるのがおもしろくて同じようなことをVisualStudioの単体テストで使いたいと思ったのが動機。
いじり方としては、Gallioのソースから欲しいファイルを部分を抜き出してそのまま使ったり変更したりしてMSTestExpressionAssertion.dllという名前のアセンブリを作った。
これに含まれるAssertExクラスのIsTrueメソッドとIsFalseメソッドが、Microsoft.VisualStudio.TestTools.UnitTesting.AssertクラスのIsTrueとIsFalseの式ツリーを受け取るバージョン相当。
MSTestExpressionAssertion.dllを参照に追加して、
テストコード:
テストコード:
例外をスローするテストコード:
例外クラスの型とスタックトレースの両方をうまく維持する方法が分らなかった(というか多分ない)から、スローされる例外クラスを優先した。そのためスタックトレースにMSTestExpressionAssertionのコードも含まれてしまっている。
Gallioからの主な変更内容:
Gallioのソースからの変更部分はmodified-files.diff。
これも放置していたけど一段落つけるまでやった。
前のエントリで書いた通りGallioのAssertEx.Thatで構成要素の式ををキャプチャしてその結果を出力できるのがおもしろくて同じようなことをVisualStudioの単体テストで使いたいと思ったのが動機。
いじり方としては、Gallioのソースから欲しいファイルを部分を抜き出してそのまま使ったり変更したりしてMSTestExpressionAssertion.dllという名前のアセンブリを作った。
これに含まれるAssertExクラスのIsTrueメソッドとIsFalseメソッドが、Microsoft.VisualStudio.TestTools.UnitTesting.AssertクラスのIsTrueとIsFalseの式ツリーを受け取るバージョン相当。
MSTestExpressionAssertion.dllを参照に追加して、
AssertEx.IsTrue(() => true)と書いたテストは成功して、
AssertEx.IsTrue(() => false)と書いたテストは失敗する。
テストコード:
var p = new Point(1.0, 2.0); var tole = 1.0e-12; AssertEx.IsFalse(() => Math.Abs(p.CalculateDistance(Point.ORIGIN) - Math.Sqrt(5.0)) <= tole);エラーメッセージ:
AssertEx.IsFalse failed. Math.Abs((p.CalculateDistance(Point.ORIGIN) - Math.Sqrt(5))) <= tole: True Math.Abs((p.CalculateDistance(Point.ORIGIN) - Math.Sqrt(5))): 0 p.CalculateDistance(Point.ORIGIN) - Math.Sqrt(5): 0 p.CalculateDistance(Point.ORIGIN): 2.23606797749979 p: (1, 2) Point.ORIGIN: (0, 0) Math.Sqrt(5): 2.23606797749979 tole: 1E-12まあこんなものかと。 式の中でのリテラルが浮動小数点数ではなくなっていたり、結果の出力はToString()メソッドにしているせいでそのあたりにも少し不満があるけど。
テストコード:
var m = Matrix.MakeRotation(Math.PI * 0.25); var tole = 1.0e-12; AssertEx.IsFalse(() => m.Transform(new Point(1.0, 0.0)).CalculateDistance(new Point(Math.Sqrt(2.0) * 0.5, Math.Sqrt(2.0) * 0.5)) <= tole);エラーメッセージ:
AssertEx.IsFalse failed. m.Transform(new Point(1, 0)).CalculateDistance(new Point((Math.Sqrt(2) * 0.5), (Math.Sqrt(2) * 0.5))) <= tole: True m.Transform(new Point(1, 0)).CalculateDistance(new Point((Math.Sqrt(2) * 0.5), (Math.Sqrt(2) * 0.5))): 1.11022302462516E-16 m.Transform(new Point(1, 0)): (0.70710678118654757, 0.70710678118654746) m: (0.70710678118654757, 0.70710678118654757, 0.70710678118654746, 0) new Point(1, 0): (1, 0) new Point((Math.Sqrt(2) * 0.5), (Math.Sqrt(2) * 0.5)): (0.70710678118654757, 0.70710678118654757) Math.Sqrt(2) * 0.5: 0.707106781186548 Math.Sqrt(2): 1.4142135623731 Math.Sqrt(2) * 0.5: 0.707106781186548 Math.Sqrt(2): 1.4142135623731 tole: 1E-12この程度で既にうるさく感じる。 "Math.Sqrt(2) * 0.5"(とその下の"Math.Sqrt(2)")が2回出力されるのもどうかと思うけど、オブジェクトが変更されるケースも当然あるから同一の式&結果ならまとめるとかいうのもあまりよくない気がする。 細かいけどデフォルトのフォント設定ではmの結果のインデントがずれる(結果が複数行の場合のインデント処理をせっかく入れたのに)。
例外をスローするテストコード:
AssertEx.IsTrue(() => string.Format("{0}", null) == "");エラーメッセージ:
テスト メソッド MSTestExpressionAssertionTest.AssertExSample.Test02 は例外をスローしました: System.ArgumentNullException: 値を Null にすることはできません。 パラメータ名: args。スタックトレース:
System.String.Format(IFormatProvider provider, String format, Object[] args) lambda_method(ExecutionScope ) Gallio.Common.Linq.ExpressionInstrumentor.Intercept[T](Expression expr, Func`1 continuation) Intercept[T](Expression expr, Func`1 continuation) Gallio.Common.Linq.ExpressionInstrumentor.InterceptNonVoid[T](Expression expr, Func`1 continuation) lambda_method(ExecutionScope ) Gallio.Common.Linq.ExpressionInstrumentor.Intercept[T](Expression expr, Func`1 continuation) Intercept[T](Expression expr, Func`1 continuation) Gallio.Common.Linq.ExpressionInstrumentor.InterceptNonVoid[T](Expression expr, Func`1 continuation) lambda_method(ExecutionScope ) Eval(Expression`1 condition) MSTestExpressionAssertion.AssertEx.IsTrue(Expression`1 condition, String message, Object[] parameters) MSTestExpressionAssertion.AssertEx.IsTrue(Expression`1 condition) MSTestExpressionAssertionTest.AssertExSample.Test02() C:\home\development\projects\bitbucket\junk\cs\MSTestExpressionAssertion\MSTestExpressionAssertionTest\AssertExSample.cs 内: 行 42スローされた例外クラスはSystem.ArgumentNullExceptionクラス。
例外クラスの型とスタックトレースの両方をうまく維持する方法が分らなかった(というか多分ない)から、スローされる例外クラスを優先した。そのためスタックトレースにMSTestExpressionAssertionのコードも含まれてしまっている。
Gallioからの主な変更内容:
- 式ツリーに含まれる定数式以外のすべての式とその結果を出力するようにした
- 式の中で発生した例外はcatchせずにそのまま挙げるようにした
- 結果の出力はToString()メソッドを使うようにした
- すべての式を出力するための式のフォーマッタが面倒だった。GallioのExpressionFormattingRule.csをベースにしたExpressionFormatter.cs(と補助的にExpressionExtensions.cs)がその部分。staticメンバやthisのメンバの判定処理についてはもっとうまい方法があるかも。
- デバッガで楽にテストの式にステップインできるようにMSTestExpressionAssertionプロジェクトのReleaseビルドでは/debug:none指定。デバッグシンボルがないアセンブリを読み込むと警告を表示するのがデフォルトなのがうざい。対象のコードにDebuggerStepThroughAttributeやDebuggerHiddenAttributeやDebuggerNonUserCodeAttributeを指定しようかと思ったけどGallioのコードを変更するのを避けるため止めた。
- でも式が連続して実行される感じではなくなっている(結果を取得するため分解して書き換えているから)のでステップ実行が微妙。まあ使えなくはない。
- それにしてもExpressionInstrumentor.csとExpressionFormattingRule.csはうまい。こんなのよく書くなと同時によく書けるなと思う。
Gallioのソースからの変更部分はmodified-files.diff。
2010/01/16
MiniTwitter-ma - MiniTwitterの複数アカウント対応版+α
MiniTwitterの複数アカウント対応の続き。
放置していたけど手をつけたからには一段落するまでやっておく。
(まめ)しばやんさんのMiniTwitterのバージョン1.05.2(たぶんchangeset 62377)をベースにして複数アカウント対応(同時に扱えるのではなくアカウントを切り替えられるという感じ)とたくさんのポストを表示できるタイムライン表示機能を追加してみた。
複数アカウント対応については次の通り。
「What's happening?」の右側に現在のアカウントを表示するボタンを追加。このボタンをクリックするとアカウントを選択するメニューがポップアップされる。
複数のアカウントの登録できるように設定ダイアログを変更。
タイムライン表示の変更については次の通り。
スクリーンショットは「標準」と「タイト」。どちらもテーマは「ネットブック最適化」を選択。
「タイト」の表示は「標準」をベースにしてフォントサイズと行の高さとマージンを小さめに変更したもの。
ソースはBitbucketのMiniTwitter-ma。オリジナルのMiniTwitterからの変更箇所はmodified-files.diff。
ライセンスはオリジナルと同じくApache License, Version 2.0。
使う人がいるとはあまり思わないけど一応ビルド済みバイナリをWindows LiveのSkyDriveに置いた。
やってみた感想:
放置していたけど手をつけたからには一段落するまでやっておく。
(まめ)しばやんさんのMiniTwitterのバージョン1.05.2(たぶんchangeset 62377)をベースにして複数アカウント対応(同時に扱えるのではなくアカウントを切り替えられるという感じ)とたくさんのポストを表示できるタイムライン表示機能を追加してみた。
複数アカウント対応については次の通り。
「What's happening?」の右側に現在のアカウントを表示するボタンを追加。このボタンをクリックするとアカウントを選択するメニューがポップアップされる。
複数のアカウントの登録できるように設定ダイアログを変更。
タイムライン表示の変更については次の通り。
スクリーンショットは「標準」と「タイト」。どちらもテーマは「ネットブック最適化」を選択。
「タイト」の表示は「標準」をベースにしてフォントサイズと行の高さとマージンを小さめに変更したもの。
ソースはBitbucketのMiniTwitter-ma。オリジナルのMiniTwitterからの変更箇所はmodified-files.diff。
ライセンスはオリジナルと同じくApache License, Version 2.0。
使う人がいるとはあまり思わないけど一応ビルド済みバイナリをWindows LiveのSkyDriveに置いた。
やってみた感想:
- WPFおもしろい(しばやんさん&MiniTwitterに感謝)。
- Apache Licenseの4-2が微妙。変更したファイルの中に変更箇所を明示する必要はなく、変更したファイルがあればそれを明示しろと解釈した(※それでは条項を満たさないと思われる場合は教えてください)。
- 相場関連を別アカウントでやっていたから複数アカウント対応が欲しかったけどアカウントを分けない方が良いように思えてきた。分けたときは「人生オワタ\(^o^)/」とか素人丸出しなポストと結び付けたくないなと思っていたけど、前者になるような取引をしそうにないし、後者はそもそも素人レベルなんだからそれを隠そうとするのもどうよって思うようになってきた。フォローする人たちが全然違うけどアカウントを分けるのを止めてまとめようかと思う。
- オリジナルのMiniTwitterのタイムライン表示はスペースを贅沢に使いすぎ、件数少なすぎ、どんだけの解像度で使っているんだよw、というくらいに思っていたけど自然に作るとそんな感じになると思った。
- その上で余白少なめ、行間小さめなタイムライン表示を追加したらEee PC 900-Xでも使えなくないな、と。ただしUIデザインセンスのない効率性重視(<-機能的に必要なものを詰め込む傾向)だなぁ。
- いじってみた上でやっぱりWPF面白い。学習が必要。
2009/12/19
MiniTwitterの複数アカウント対応
TwitterクライアントをEchofonからMiniTwitterに切り替えようかと思ってMiniTwitterを複数アカウント対応にしてみていた。対応の仕方としてはEchofonのように複数のアカウント情報を保存&容易に切り替え可能という感じ、複数アカウントを同時に操作とかは要らない(それだったら複数のクライアントを使えばいい)。
きっかけは普段使いのWebブラウザをFirefoxからGoogle Chromeに変更しようかと考えたこと。面倒くさくなってきてもう止めるかもしれないから今のうちに書いておく。
ベースはChange Set 61673。これをやっている間にバージョン1.05系列がリリースされてた。
変更内容は次の通り。
メインウィンドウのupdateボタンの右に最後にログインしたユーザーを表示。
Echofonと同じようにこのログインユーザー表示をクリックすると切り替えるアカウントを選択するメニューがポップアップ。ここでアカウントを選択するとログインユーザーが切り替わる。
アカウント情報を登録したり編集したりするフォームは至ってノーマルのはず。
ログ保存機能は使う気がないから後回し。
もうちょっとやりたいことをやったら変更部分の扱いをどうするか考える。
MiniTwitter勝手に改造版のコードも見てみる。
この程度のことをしただけなのにXAMLとWPFについてけっこう調べて勉強になった。XAML/WPFおもしろそう。これからはWindowsフォームなんか使わないでWPFを使おう。
きっかけは普段使いのWebブラウザをFirefoxからGoogle Chromeに変更しようかと考えたこと。面倒くさくなってきてもう止めるかもしれないから今のうちに書いておく。
ベースはChange Set 61673。これをやっている間にバージョン1.05系列がリリースされてた。
変更内容は次の通り。
メインウィンドウのupdateボタンの右に最後にログインしたユーザーを表示。
Echofonと同じようにこのログインユーザー表示をクリックすると切り替えるアカウントを選択するメニューがポップアップ。ここでアカウントを選択するとログインユーザーが切り替わる。
アカウント情報を登録したり編集したりするフォームは至ってノーマルのはず。
ログ保存機能は使う気がないから後回し。
もうちょっとやりたいことをやったら変更部分の扱いをどうするか考える。
MiniTwitter勝手に改造版のコードも見てみる。
この程度のことをしただけなのにXAMLとWPFについてけっこう調べて勉強になった。XAML/WPFおもしろそう。これからはWindowsフォームなんか使わないでWPFを使おう。
2009/11/22
VisualStudio 2008の単体テスト機能のカスタマイズ(VS2008 custom assertion example)
結構前にGallioのAssertEx.Thatの式ツリーの要素をキャプチャして出力できる点がすごいと思ってちょっといろいろいじっていた。Gallioは素晴らしいけどちょっと重い感じがするし、機能とか装備されているものが豊富なのはいいんだけど、もうちょっと簡単にいじれる大きすぎないフレームワークを使いたいというのが感想。
最近それをある程度の形にしようと思って、アレンジしてVisual Studio 2008の単体テスト機能で使えるようにしようとしてVisual Studio 2008の単体テスト機能のカスタマイズを調べてみた。
基本的には自前のassertionでテスト失敗の場合はAssertFailedExceptionをスローすればいいんだけど、そうするとスタックトレースに診断処理や例外をスローする処理のメソッドが含まれてしまう。
これはDebuggerHiddenAttributeやDebuggerNonUserCodeAttributeを使っても避けられない。デバッガとスタックトレースは別。
スタックトレースに含まれないように回避するための属性はないみたいなので、スタックトレースを操作する方向で行こうとすると例外のスタックトレース(Exception.StackTraceプロパティとか)はStackTrace型ではなくて文字列だったりする上にSetterがない。
どうすればいいの?例外のスタックトレースは変更できないの?と思ったけどよく見るとException.StackTraceプロパティはvirtual宣言されているから派生させてオーバーライドすれば、このプロパティで返される値は変更できる。
それでこんなコードでスタックトレースに含められないようにできそう。
Visual Studioの単体テスト機能でテストを失敗させた場合のスタックトレースにExpressionAssertクラスの処理が含まれない。
Visual Studioに統合された単体テストの機能以外で試してもいないし、例外クラスがAssertFailedExceptionではないという点が気になるけど、とりあえず手軽にするならこの方法でもいいかもしれない。
手間は増えるだろうけどInternalPreserveStackTraceを使ったやり方のほうがちゃんとしていそう。
最近それをある程度の形にしようと思って、アレンジしてVisual Studio 2008の単体テスト機能で使えるようにしようとしてVisual Studio 2008の単体テスト機能のカスタマイズを調べてみた。
基本的には自前のassertionでテスト失敗の場合はAssertFailedExceptionをスローすればいいんだけど、そうするとスタックトレースに診断処理や例外をスローする処理のメソッドが含まれてしまう。
これはDebuggerHiddenAttributeやDebuggerNonUserCodeAttributeを使っても避けられない。デバッガとスタックトレースは別。
スタックトレースに含まれないように回避するための属性はないみたいなので、スタックトレースを操作する方向で行こうとすると例外のスタックトレース(Exception.StackTraceプロパティとか)はStackTrace型ではなくて文字列だったりする上にSetterがない。
どうすればいいの?例外のスタックトレースは変更できないの?と思ったけどよく見るとException.StackTraceプロパティはvirtual宣言されているから派生させてオーバーライドすれば、このプロパティで返される値は変更できる。
それでこんなコードでスタックトレースに含められないようにできそう。
using System; using System.Collections.Generic; using System.Diagnostics; using System.Linq; using System.Linq.Expressions; using System.Text; using Microsoft.VisualStudio.TestTools.UnitTesting; namespace MSTestCustomAssertion { public class ExpressionAssert { public static void IsTrue(Expression<Func<bool>> expr) { var result = expr.Compile().Invoke(); if (!result) throw new FailedException("ExpressionAssert.IsTrue failed"); } public class FailedException : AssertFailedException { public FailedException() : base() { } public FailedException(string msg) : base(msg) { } public override string StackTrace { get { var lines = base.StackTrace.Split(new string[] { Environment.NewLine, }, StringSplitOptions.None); var index = Array.FindIndex(lines, x => !x.Contains("ExpressionAssert.")); var st = string.Join(Environment.NewLine, lines, index, lines.Length - index); return st; } } } } }ksksts / junk / source — bitbucket.org
Visual Studioの単体テスト機能でテストを失敗させた場合のスタックトレースにExpressionAssertクラスの処理が含まれない。
Visual Studioに統合された単体テストの機能以外で試してもいないし、例外クラスがAssertFailedExceptionではないという点が気になるけど、とりあえず手軽にするならこの方法でもいいかもしれない。
手間は増えるだろうけどInternalPreserveStackTraceを使ったやり方のほうがちゃんとしていそう。
2009/11/15
配列やList<T>をIList<T>型のインデクサでアクセスするとパフォーマンスが悪い(don't use IList<T>'s indexer)
C#でのソートアルゴリズムのまとめ(sort algorithms in C#)とかIList<T>の走査処理の比較(インデクサ/GetEnumerator/foreach)で「配列の添字アクセスが遅い」とか書いたけどこれは間違い。「配列をIList<T>型にキャストしてインデクサでアクセスすると遅い」というのが正しい。
実際には配列をそのまま添字でアクセスする処理はList<T>のインデクサアクセスよりも速い。
それにList<T>もIList<T>型にキャストしてインデクサでアクセスすのは(List<T>のインデクサよりも)遅い。
インターフェースってこんなに影響するものなのか。配列やList<T>の特殊な最適化処理があったりしてそれが適用されなくなったりした影響なのかな。
測定した結果は次の通り。
int[]に対する配列の添字アクセスが最も速くてIList<T>のインデクサを通すとなぜか10倍弱の時間がかかるようになる。
List<T>のインデクサアクセスは配列の添字アクセスより遅いらしい。これもIList<T>のインデクサを通すと配列ほどじゃないけど遅くなる。
IListArrayWrapperは値型の配列をラップしてポインタを使ったunsafeなインデクサを実装したクラス。
ジェネリックに実装しようとしたけどジェネリック引数の型のポインタを取得する方法が分からなかった。"where T : struct"の指定があればOKにしてもいいんじゃないかと思うけど、とりあえずできなかったからint型用のコードにした。
ダメだったコード:
まとめとしては、配列とIList<T>を実装したランダムアクセス可能なコレクションの両方を処理する&パフォーマンスを気にするコードを書く場合は分けたほうがいいみたい。
実際には配列をそのまま添字でアクセスする処理はList<T>のインデクサアクセスよりも速い。
それにList<T>もIList<T>型にキャストしてインデクサでアクセスすのは(List<T>のインデクサよりも)遅い。
インターフェースってこんなに影響するものなのか。配列やList<T>の特殊な最適化処理があったりしてそれが適用されなくなったりした影響なのかな。
測定した結果は次の通り。
int[]に対する配列の添字アクセスが最も速くてIList<T>のインデクサを通すとなぜか10倍弱の時間がかかるようになる。
List<T>のインデクサアクセスは配列の添字アクセスより遅いらしい。これもIList<T>のインデクサを通すと配列ほどじゃないけど遅くなる。
array: 662 (1.00) (IList<T>)array: 6259 (9.45) list: 1325 (2.00) (IList<T>)list: 1592 (2.40) IListArrayWrapper: 1166 (1.76)ksksts / junk / source — bitbucket.org
IListArrayWrapperは値型の配列をラップしてポインタを使ったunsafeなインデクサを実装したクラス。
ジェネリックに実装しようとしたけどジェネリック引数の型のポインタを取得する方法が分からなかった。"where T : struct"の指定があればOKにしてもいいんじゃないかと思うけど、とりあえずできなかったからint型用のコードにした。
ダメだったコード:
public class IListArrayWrapper<T> where T : struct { public T[] Source { get; set; } unsafe public T this[int index] { get { fixed (T* p = &Source[0]) return *(p + index); } set { fixed (T* p = &Source[0]) *(p + index) = value; } } public IListArrayWrapper(IList<T> source) { Source = (T[])source; } }ksksts / junk / source — bitbucket.org
まとめとしては、配列とIList<T>を実装したランダムアクセス可能なコレクションの両方を処理する&パフォーマンスを気にするコードを書く場合は分けたほうがいいみたい。
C#でのソートアルゴリズムのまとめ(sort algorithms in C#)
stereopsis : graphics : radix tricksやSTLportのソースをパクったりしたC#でのソートのまとめ。
それぞれのアルゴリズムとか関連エントリとか。
配列に対するソートとList<T>に対するソートに対するソートとの差やEnumerable.OrderBy メソッド (System.Linq)とかも含めた測定結果は次の通り。
まとめ:
それぞれのアルゴリズムとか関連エントリとか。
- ksksts's blog: C#で実装したradix sortとArray.Sortのquick sortとの比較
- ksksts's blog: C#で符号付整数/浮動小数点数対応の基数ソート(radix sort)
- ksksts's blog: C#でin place merge sortを書いてみたらかなり遅かった
- ksksts's blog: バッファを使用するマージソートも書いてみた(merge sort with buffer in C#)
- ksksts's blog: イントロソートも書いてみた(introsort in C#)
配列に対するソートとList<T>に対するソートに対するソートとの差やEnumerable.OrderBy メソッド (System.Linq)とかも含めた測定結果は次の通り。
size: 1000 Array.Sort: 1186 (1.00) List.Sort: 1050 (0.89) Array.OrderBy: 6592 (5.56) List.OrderBy: 3305 (2.78) CombSort Array: 31971 (26.94) CombSort List: 6909 (5.82) NumberRadixSorter.Sort Array: 4943 (4.17) NumberRadixSorter.Sort List: 2291 (1.93) InPlaceMergeSorter.Sort Array: 40326 (33.98) InPlaceMergeSorter.Sort List: 12642 (10.65) MergeSorter.Sort Array: 24463 (20.61) MergeSorter.Sort List: 9604 (8.09) IntroSorter.Sort Array: 22909 (19.30) IntroSorter.Sort List: 4158 (3.50) size: 10000 Array.Sort: 12771 (1.00) List.Sort: 12497 (0.98) Array.OrderBy: 41599 (3.26) List.OrderBy: 41126 (3.22) CombSort Array: 465802 (36.47) CombSort List: 101879 (7.98) NumberRadixSorter.Sort Array: 27803 (2.18) NumberRadixSorter.Sort List: 22234 (1.74) InPlaceMergeSorter.Sort Array: 553976 (43.38) InPlaceMergeSorter.Sort List: 202408 (15.85) MergeSorter.Sort Array: 224039 (17.54) MergeSorter.Sort List: 128939 (10.10) IntroSorter.Sort Array: 158357 (12.40) IntroSorter.Sort List: 52881 (4.14) size: 100000 Array.Sort: 146888 (1.00) List.Sort: 145938 (0.99) Array.OrderBy: 520984 (3.55) List.OrderBy: 518649 (3.53) CombSort Array: 6044506 (41.15) CombSort List: 1311650 (8.93) NumberRadixSorter.Sort Array: 279857 (1.91) NumberRadixSorter.Sort List: 218608 (1.49) InPlaceMergeSorter.Sort Array: 8215845 (55.93) InPlaceMergeSorter.Sort List: 2860999 (19.48) MergeSorter.Sort Array: 2931658 (19.96) MergeSorter.Sort List: 1869126 (12.72) IntroSorter.Sort Array: 1945971 (13.25) IntroSorter.Sort List: 658093 (4.48) size: 1000000 Array.Sort: 1666666 (1.00) List.Sort: 1668472 (1.00) Array.OrderBy: 6961424 (4.18) List.OrderBy: 6901036 (4.14) CombSort Array: 76084478 (45.65) CombSort List: 16674472 (10.00) NumberRadixSorter.Sort Array: 2864637 (1.72) NumberRadixSorter.Sort List: 2245710 (1.35) InPlaceMergeSorter.Sort Array: 110817348 (66.49) InPlaceMergeSorter.Sort List: 38880094 (23.33) MergeSorter.Sort Array: 35271234 (21.16) MergeSorter.Sort List: 20756358 (12.45) IntroSorter.Sort Array: 24093398 (14.46) IntroSorter.Sort List: 8047601 (4.83)ksksts / junk / source — bitbucket.org
まとめ:
- 安定でないソートならArray.Sort メソッド (System)かList(T).Sort メソッド (System.Collections.Generic)
- 安定なソートならEnumerable.OrderBy メソッド (System.Linq)
- STLportのソースをパクった結果としては速い順にIntroSort(STLのstd::sort), InPlaceMergeSort(STLのstd::stable_sortのバッファを使わない版), MergeSort(STLのstd::stable_sortのバッファを使う版)。そしてRadixSort(基数ソート)はこいつらより速い。
配列の添字でのランダムアクセスはList<T>のインデクサでのアクセスより遅い?(これはIList<T>を経由しているのが原因だったみたい ksksts's blog: 配列やList<T>をIList<T>型のインデクサでアクセスするとパフォーマンスが悪い(don't use IList<T>'s indexer))
2009/11/14
イントロソートも書いてみた(introsort in C#)
バッファを使わないマージソートとバッファを使うマージソートのついでにイントロソートもSTLportからパクってみた。
STLportの_algo.c, _heap.h, _heap.cの__introsort_loop, __unguarded_partition, __final_insertion_sort, __unguarded_linear_insert, __partial_sort, __make_heap, __adjust_heap, __push_heap_aux, __pop_heap_aux, sort_heapあたりをC#で書いただけ。
かなり手抜きだと思う。マージソートとの速度差をみたいだけだから。
結果は次の通り。
Array.Sortの十数倍の時間がかかっている。バッファを使用するマージソートよりは多少速い。
ソート対象を配列からList<T>に変更するだけで数倍良くなるはずだから後でまとめて試してみる。
今になって基数ソート(radix sort)がかなり速いということを実感。
STLportの_algo.c, _heap.h, _heap.cの__introsort_loop, __unguarded_partition, __final_insertion_sort, __unguarded_linear_insert, __partial_sort, __make_heap, __adjust_heap, __push_heap_aux, __pop_heap_aux, sort_heapあたりをC#で書いただけ。
かなり手抜きだと思う。マージソートとの速度差をみたいだけだから。
結果は次の通り。
Array.Sortの十数倍の時間がかかっている。バッファを使用するマージソートよりは多少速い。
ソート対象を配列からList<T>に変更するだけで数倍良くなるはずだから後でまとめて試してみる。
今になって基数ソート(radix sort)がかなり速いということを実感。
size: 1000 IntroSorter.Sort: 20622 (18.78) Array.Sort: 1098 (1.00) size: 10000 IntroSorter.Sort: 139073 (10.98) Array.Sort: 12667 (1.00) size: 100000 IntroSorter.Sort: 1771360 (12.22) Array.Sort: 144995 (1.00) size: 1000000 IntroSorter.Sort: 21275858 (12.85) Array.Sort: 1656005 (1.00)ksksts / junk / source — bitbucket.org
2009/11/12
バッファを使用するマージソートも書いてみた(merge sort with buffer in C#)
in-place merge sortをEQUATEC Profilerでプロファイルしてみたけど特におかしいと感じるところは見つからず、結局マージ回数が多すぎじゃないかと思う(あとお手玉Rotatenってやっぱり速いのかなと)。
in-placeじゃないアルゴリズムとの比較をするためにSTLportの_algo.cのstable_sortから__stable_sort_adaptive, __merge_sort_with_buffer, __chunk_insertion_sort, __merge_sort_loop, mergeあたりを辿って、ソート対象の要素個数分のバッファを使用するマージソートをC#で書いてみた。
結果は下記の通り。in-place merge sortよりは良い。でもまだArray.Sortの十数倍の時間がかかっている。
いろいろ小賢しいことをやっているからもっとシンプルな素直なコードを書いてみたほうがいいかもしれない。
in-placeじゃないアルゴリズムとの比較をするためにSTLportの_algo.cのstable_sortから__stable_sort_adaptive, __merge_sort_with_buffer, __chunk_insertion_sort, __merge_sort_loop, mergeあたりを辿って、ソート対象の要素個数分のバッファを使用するマージソートをC#で書いてみた。
結果は下記の通り。in-place merge sortよりは良い。でもまだArray.Sortの十数倍の時間がかかっている。
いろいろ小賢しいことをやっているからもっとシンプルな素直なコードを書いてみたほうがいいかもしれない。
size: 1000 InPlaceMergeSorter.Sort: 37314 (33.24) MergeSorter.Sort: 21869 (19.48) Array.Sort: 1122 (1.00) size: 10000 InPlaceMergeSorter.Sort: 502465 (39.24) MergeSorter.Sort: 198861 (15.53) Array.Sort: 12805 (1.00) size: 100000 InPlaceMergeSorter.Sort: 7425488 (47.00) MergeSorter.Sort: 2556782 (16.18) Array.Sort: 157988 (1.00) size: 1000000 InPlaceMergeSorter.Sort: 98928451 (58.09) MergeSorter.Sort: 31298754 (18.38) Array.Sort: 1703143 (1.00)ksksts / junk / source — bitbucket.org
2009/11/10
C#でin place merge sortを書いてみたらかなり遅かった
C#で簡単に使いまわせる安定なソートを用意しておこうと思ってIn place stable Sort (merge sort)を参考に書いてみたらかなり遅かった。
In place stable Sort (merge sort)のソースの不自然さが気になったからSTLportの_algo.cとか_algobase.cとかも参考にした。
Array.Sortの数十倍時間がかかるとか、これじゃあまり使う気にならない。
何かミスっているのかもしれないし、in placeでなければもうちょっとましかもしれない。明日もう少し調べてみる。
In place stable Sort (merge sort)のソースの不自然さが気になったからSTLportの_algo.cとか_algobase.cとかも参考にした。
Array.Sortの数十倍時間がかかるとか、これじゃあまり使う気にならない。
何かミスっているのかもしれないし、in placeでなければもうちょっとましかもしれない。明日もう少し調べてみる。
size: 1000 merge sort: 37580 (30.34) Array.Sort: 1238 (1.00) size: 10000 merge sort: 506318 (41.50) Array.Sort: 12199 (1.00) size: 100000 merge sort: 7170923 (50.21) Array.Sort: 142813 (1.00) size: 1000000 merge sort: 97304833 (60.94) Array.Sort: 1596662 (1.00)ksksts / junk / source — bitbucket.org
2009/11/07
C#で符号付整数/浮動小数点数対応の基数ソート(radix sort)
Radium Software Development経由でRadix Sort Revisitedを見て、さらに2006-07-23 - togeの日記経由でstereopsis : graphics : radix tricksも読んだから書いてみた。
ksksts's blog: C#で実装したradix sortとArray.Sortのquick sortとの比較で実行速度で.NET FrameworkのArray.Sort メソッド (System)とかを上回るのは難しいと感じた時点で目的を実装例を示すだけに変更。Decimal型やString型に対応しようと思っていたけど、面倒くさくなったから符号なし整数型、符号付整数型、浮動小数点数型で止めとく。
stereopsis : graphics : radix tricksのコードを参考にして書いた。
おおまかな処理の流れは次の通り。
インターフェースはこんな感じ。
RadixSorter.cs
ソートに使用するキーを指定しながらソートできるのがおもしろい。
あとは書いてみて思ったこととか。
ヒストグラムをずらして作っておくと加算する処理が簡単になること、降順にソートする場合はヒストグラムの加算の処理で対応できること。
NumberRadixSorter.csのSort(IList list, SortOrder sortOrder)
符号付整数型でも同様。ただし符号ビットを含む部分だけ工夫する。
NumberRadixSorter.csのSort(IList list, SortOrder sortOrder)
浮動小数点数の場合は、要素が数値でそれを変更できる場合はヒストグラムを作るときにビットをflipして、ソート後に元に戻すとかできる(stereopsis : graphics : radix tricksのコードでやっている)。
NumberRadixSorter.csのSort(IList list, SortOrder sortOrder)
ksksts / junk / source — bitbucket.org
ksksts's blog: C#で実装したradix sortとArray.Sortのquick sortとの比較で実行速度で.NET FrameworkのArray.Sort メソッド (System)とかを上回るのは難しいと感じた時点で目的を実装例を示すだけに変更。Decimal型やString型に対応しようと思っていたけど、面倒くさくなったから符号なし整数型、符号付整数型、浮動小数点数型で止めとく。
stereopsis : graphics : radix tricksのコードを参考にして書いた。
おおまかな処理の流れは次の通り。
- ヒストグラムをまとめて作成(histgramming)
- ヒストグラムの値を加算(sum the histgrams)
- 要素の並べ替え(read/write histgram, copy)
インターフェースはこんな感じ。
RadixSorter.cs
public static void Sort<T>(IList<T> list, Func<T, UInt32> converter, SortOrder sortOrder)
ソートに使用するキーを指定しながらソートできるのがおもしろい。
struct Pair<TFirst, TSecond> { public TFirst First { get; set; } public TSecond Second { get; set; } }このPair型に対して、
RadixSorter.Sort(result, x => x.Second, RadixSorter.SortOrder.Ascending); RadixSorter.Sort(result, x => x.First, RadixSorter.SortOrder.Ascending);とすると、次のCompareTo(Firstで比較&Firstが等しい場合はSecondで比較)でソートした場合と同じ結果が得られる。
public int CompareTo(Pair<TFirst, TSecond> x) { var result = First.CompareTo(x.First); if (result == 0) result = Second.CompareTo(x.Second); return result; }
あとは書いてみて思ったこととか。
ヒストグラムをずらして作っておくと加算する処理が簡単になること、降順にソートする場合はヒストグラムの加算の処理で対応できること。
NumberRadixSorter.csのSort(IList
// histgramming var histgramOffset = ascending ? 1 : -1; foreach (var x in list) { var y = x; for (var p = 0; p < tables.Length; p++) { tables[p][(y + histgramOffset) & 0xFF]++; y = y >> 8; } } // sum the histgrams if (ascending) { foreach (var table in tables) { table[0] = 0; for (var n = 1; n < table.Length; n++) table[n] += table[n - 1]; } } else { foreach (var table in tables) { table[0xFF] = 0; for (var n = 0xFE; n >= 0x00; n--) table[n] += table[n + 1]; } }
符号付整数型でも同様。ただし符号ビットを含む部分だけ工夫する。
NumberRadixSorter.csのSort(IList
// sum the histgrams if (ascending) { for (var p = 0; p < tables.Length - 1; p++) { var table = tables[p]; table[0] = 0; for (var n = 1; n < table.Length; n++) table[n] += table[n - 1]; } { var table = tables[tables.Length - 1]; table[0x80] = 0; for (var n = 0x81; n <= 0xFF; n++) table[n] += table[n - 1]; table[0x00] += table[0xFF]; for (var n = 0x01; n < 0x80; n++) table[n] += table[n - 1]; } } else { for (var p = 0; p < tables.Length - 1; p++) { var table = tables[p]; table[0xFF] = 0; for (var n = 0xFE; n >= 0x00; n--) table[n] += table[n + 1]; } { var table = tables[tables.Length - 1]; table[0x7F] = 0; for (var n = 0x7E; n >= 0x00; n--) table[n] += table[n + 1]; table[0xFF] += table[0x00]; for (var n = 0xFE; n >= 0x80; n--) table[n] += table[n + 1]; } }
浮動小数点数の場合は、要素が数値でそれを変更できる場合はヒストグラムを作るときにビットをflipして、ソート後に元に戻すとかできる(stereopsis : graphics : radix tricksのコードでやっている)。
NumberRadixSorter.csのSort(IList
// histgramming var histgramOffset = ascending ? 1 : -1; for (var n = 0; n < list.Count; n++) { var y = BitConverter.DoubleToInt64Bits(list[n]); y ^= -(Int64)((UInt64)y >> 63) | unchecked((Int64)0x8000000000000000); // flip list[n] = BitConverter.Int64BitsToDouble(y); for (var p = 0; p < tables.Length; p++) { tables[p][(y + histgramOffset) & 0xFF]++; y = y >> 8; } }
// read/write histgram, copy IList<Double> array = new Double[list.Count]; Action swap = () => { var temp = list; list = array; array = temp; }; for (var p = 0; p < tables.Length - 1; p++) { var table = tables[p]; foreach (var x in list) { var y = BitConverter.DoubleToInt64Bits(x); y = (y >> p * 8) & 0xFF; array[table[y]] = x; table[y]++; } swap(); } { var p = tables.Length - 1; var table = tables[p]; foreach (var x in list) { var w = BitConverter.DoubleToInt64Bits(x); var y = (w >> p * 8) & 0xFF; w ^= (Int64)((UInt64)w >> 63) - 1 | unchecked((Int64)0x8000000000000000); // flip back array[table[y]] = BitConverter.Int64BitsToDouble(w); table[y]++; } swap(); }
ksksts / junk / source — bitbucket.org
2009/11/06
Decimal型のフォーマット
Decimal.GetBits メソッド (System)で解説されている。
指数部が10の累乗なんだからSingle/Doubleとは違う分かりやすい値に変えた。
結果は次の通り。0.0Mと-0.0Mの指数部は1になるみたい。
Decimal 数値のバイナリ表現は、1 ビットの符号、96 ビットの整数、および整数値を除算し、小数部を指定するために使用するスケール ファクタから構成されます。スケール ファクタは黙示的に数値 10 になり、0 から 28 の範囲の指数で累乗されます。Decimal.GetBits メソッドの戻り値はint[]でいまいち分かりにくいから確認用のコードを書いた(下手なコードだと思うけど良い方法を思いつけなかった)。
戻り値は、4 要素の 32 ビット符号付き整数配列です。
この配列の 1 ~ 3 番目の要素は、96 ビット整数の下位 32 ビット、中位 32 ビット、および上位 32 ビットをそれぞれ格納しています。
4 番目の要素は、スケール ファクタと符号を格納しています。この要素は、次に示す部分から構成されています。
ビット 0 ~ 15 の下位ワードは未使用で、0 である必要があります。
ビット 16 ~ 23 には、0 から 28 までの範囲の指数部を格納する必要があります。この指数部は、整数を除算する 10 の累乗を示します。
ビット 24 ~ 30 は未使用で、0 である必要があります。
ビット 31 は符号を格納している必要があります。0 は正、1 は負を表します。
ビット形式では負の 0 と正の 0 が区別されます。これらの値はすべての演算で等値として扱われます。
指数部が10の累乗なんだからSingle/Doubleとは違う分かりやすい値に変えた。
unsafe private static void PrintDecimal() { Func<Decimal, byte[]> toBytes = x => { var p = (byte*)&x; var bytes = new byte[sizeof(Decimal)]; for (var n = 0; n < bytes.Length; n++) bytes[n] = *p++; return bytes; }; WriteLine("Decimal:"); WriteLine("0.0M", toBytes(0.0M)); WriteLine("-0.0M", toBytes(-0.0M)); WriteLine("0.12M", toBytes(0.12M)); WriteLine("1.2M ", toBytes(1.2M)); WriteLine("12M ", toBytes(12M)); WriteLine("120M ", toBytes(120M)); WriteLine("1200M", toBytes(1200M)); WriteLine("-0.12M", toBytes(-0.12M)); WriteLine("-1.2M ", toBytes(-1.2M)); WriteLine("-12M ", toBytes(-12M)); WriteLine("-120M ", toBytes(-120M)); WriteLine("-1200M", toBytes(-1200M)); WriteLine("Decimal.MinValue", toBytes(Decimal.MinValue)); WriteLine("Decimal.MaxValue", toBytes(Decimal.MaxValue)); WriteLine(""); } private static void WriteLine(string value) { Console.WriteLine(value); } private static void WriteLine(string label, byte[] bytes) { Console.Write("{0}: 0x", label); var bs = (byte[])bytes.Clone(); Array.Reverse(bs); foreach (var x in bs) Console.Write("{0:X2}", x); Console.WriteLine(); }ksksts / junk / source — bitbucket.org
結果は次の通り。0.0Mと-0.0Mの指数部は1になるみたい。
Decimal: 0.0M: 0x00000000000000000000000000010000 -0.0M: 0x00000000000000000000000080010000 0.12M: 0x000000000000000C0000000000020000 1.2M : 0x000000000000000C0000000000010000 12M : 0x000000000000000C0000000000000000 120M : 0x00000000000000780000000000000000 1200M: 0x00000000000004B00000000000000000 -0.12M: 0x000000000000000C0000000080020000 -1.2M : 0x000000000000000C0000000080010000 -12M : 0x000000000000000C0000000080000000 -120M : 0x00000000000000780000000080000000 -1200M: 0x00000000000004B00000000080000000 Decimal.MinValue: 0xFFFFFFFFFFFFFFFFFFFFFFFF80000000 Decimal.MaxValue: 0xFFFFFFFFFFFFFFFFFFFFFFFF00000000
2009/11/04
IList<T>の走査処理の比較(インデクサ/GetEnumerator/foreach)
twitterで「IList<t>を走査する場合でforとforeachとで意外に差が出た」とか書いたら「IListの内部実装にもよりますけど、結構変わったりしますよねー」とRTがあった。
それで処理速度を計測するコードを書いてみたらよく分からない結果になった。
結果は次の通り。
配列ではインデクサでのアクセスがforeachより遅いのにList<t>では逆になるのが意味が分からない。あと両方でGetEnumeratorでのアクセスがforeachよりも速いのも意味が分からない(foreachの方が最適化する余地があるんじゃないかと思う)。
こんな処理にフォーカスした速度なんかほとんどのケースで無視していいだろうから、インデクサでのアクセスが必要じゃない処理では常にforeachを使うけど。
書きながらList<t>.ForEachを思い出した(IList<t>には無い)。使える場合ならforeachよりもそれを使うと思う。ILとかコンパイル後のコードはたぶん見ない。
それで処理速度を計測するコードを書いてみたらよく分からない結果になった。
using System; using System.Collections.Generic; using System.Diagnostics; using System.Linq; using System.Text; namespace IListEnumerationBenchmark { class Program { static void Main(string[] args) { var size = 1000000; var count = 10; var sw = new Stopwatch(); Action<IList<int>> run = list => { var useIndexerTicks = 0L; var useEnumeratorTicks = 0L; var useForeachTicks = 0L; for (var c = 0; c < count; c++) { var random = new Random(c); for (var n = 0; n < list.Count; n++) list[n] = random.Next(int.MinValue, int.MaxValue); // use indexer sw.Reset(); sw.Start(); var useIndexerResult = UseIndexer(list); sw.Stop(); useIndexerTicks += sw.ElapsedTicks; // use enumerator sw.Reset(); sw.Start(); var useEnumeratorResult = UseEnumerator(list); sw.Stop(); useEnumeratorTicks += sw.ElapsedTicks; // use foreach sw.Reset(); sw.Start(); var useForeachResult = UseForeach(list); sw.Stop(); useForeachTicks += sw.ElapsedTicks; // correctness if (useIndexerResult != useForeachResult) Console.WriteLine("useIndexerResult: {0}, useForeachResult: {1}", useIndexerResult, useForeachResult); if (useEnumeratorResult != useForeachResult) Console.WriteLine("useEnumeratorResult: {0}, useForeachResult: {1}", useEnumeratorResult, useForeachResult); } Console.WriteLine("use indexer: {0} ({1:F})", useIndexerTicks / count, useIndexerTicks / (double)useForeachTicks); Console.WriteLine("use enumerator: {0} ({1:F})", useEnumeratorTicks / count, useEnumeratorTicks / (double)useForeachTicks); Console.WriteLine("use foreach: {0} ({1:F})", useForeachTicks / count, useForeachTicks / (double)useForeachTicks); Console.WriteLine(); }; // int[] { Console.WriteLine("int[]: "); var array = new int[size]; run(array); } // List<int> { Console.WriteLine("List<int>: "); var list = new List<int>(); for (var n = 0; n < size; n++) list.Add(0); run(list); } } static int UseIndexer(IList<int> list) { var result = 0; for (var n = 0; n < list.Count; n++) result = unchecked(result + list[n]); return result; } static int UseEnumerator(IList<int> list) { var result = 0; var e = list.GetEnumerator(); while (e.MoveNext()) result = unchecked(result + e.Current); return result; } static int UseForeach(IList<int> list) { var result = 0; foreach (var x in list) result = unchecked(result + x); return result; } } }ksksts / junk / source — bitbucket.org
結果は次の通り。
配列ではインデクサでのアクセスがforeachより遅いのにList<t>では逆になるのが意味が分からない。あと両方でGetEnumeratorでのアクセスがforeachよりも速いのも意味が分からない(foreachの方が最適化する余地があるんじゃないかと思う)。
こんな処理にフォーカスした速度なんかほとんどのケースで無視していいだろうから、インデクサでのアクセスが必要じゃない処理では常にforeachを使うけど。
書きながらList<t>.ForEachを思い出した(IList<t>には無い)。使える場合ならforeachよりもそれを使うと思う。ILとかコンパイル後のコードはたぶん見ない。
int[]: use indexer: 578500 (5.57) use enumerator: 87133 (0.84) use foreach: 103895 (1.00) List<int>: use indexer: 132922 (0.76) use enumerator: 154705 (0.89) use foreach: 174277 (1.00)追記: 上の結果はIList<T>を通した結果だから不正確。ksksts's blog: 配列やList<T>をIList<T>型のインデクサでアクセスするとパフォーマンスが悪い(don't use IList<T>'s indexer)を参照。
2009/11/03
C#で実装したradix sortとArray.Sortのquick sortとの比較
radix sort(基数ソート)のコードを書いてて、適当に処理速度を比較してみたら遅かったから簡単なベンチマークを書いてみた。
比較するのは次の3つ。
結果は次の通り。
適当に比較したときは処理時間がArray.Sortの2倍以上で話にならないと思っていたけど、要素数が膨大になるにつれてそれなりに差が小さくなってきて少し嬉しい。
comb sortは遅いけどバブルソートの改良版と考えるとそれなりに良いような気がする。
比較するのは次の3つ。
- radix sort(WikipediaのRadix sortとstereopsisのradix tricksを参考にして書いたコード)
- comb sort(WikipediaのComb sortを参考に書いたコード、というかほぼそのまま)
- quick sort(.NET Framework 3.5のArray.Sort)
using System; using System.Collections.Generic; using System.Diagnostics; using System.Linq; using System.Text; namespace SortBenchmark { class Program { static void Main(string[] args) { for (var size = 1000; size <= 1000000; size *= 10) Run(size); } static void Run(int size) { Console.WriteLine("size: {0}", size); var count = 10; var sw = new Stopwatch(); var radixSortTicks = 0L; var combSortTicks = 0L; var arraySortTicks = 0L; for (var c = 0; c < count; c++) { var random = new Random(c); var list = new UInt32[size]; for (var n = 0; n < list.Length; n++) list[n] = (UInt32)random.Next(Int32.MinValue, Int32.MaxValue); // radix sort var radixSortResult = (IList<UInt32>)list.Clone(); sw.Reset(); sw.Start(); RadixSort(radixSortResult); sw.Stop(); radixSortTicks += sw.ElapsedTicks; // comb sort var combSortResult = (IList<UInt32>)list.Clone(); sw.Reset(); sw.Start(); CombSort(combSortResult); sw.Stop(); combSortTicks += sw.ElapsedTicks; // Array.Sort var arraySortResult = list.ToArray(); sw.Reset(); sw.Start(); Array.Sort(arraySortResult); sw.Stop(); arraySortTicks += sw.ElapsedTicks; // correctness for (var n = 0; n < size; n++) { if (radixSortResult[n] != arraySortResult[n]) Console.WriteLine("radixSortResult[{0}]: {1}, arraySortResult[{0}]: {2}", n, radixSortResult[n], arraySortResult[n]); if (combSortResult[n] != arraySortResult[n]) Console.WriteLine("combSortResult[{0}]: {1}, arraySortResult[{0}]: {2}", n, combSortResult[n], arraySortResult[n]); } } Console.WriteLine("radix sort: {0} ({1:F})", radixSortTicks / count, radixSortTicks / (double)arraySortTicks); Console.WriteLine("comb sort: {0} ({1:F})", combSortTicks / count, combSortTicks / (double)arraySortTicks); Console.WriteLine("Array.Sort: {0} ({1:F})", arraySortTicks / count, arraySortTicks / (double)arraySortTicks); } static void RadixSort(IList<UInt32> list) { var tables = new int[sizeof(UInt32)][]; for (var p = 0; p < tables.Length; p++) tables[p] = new int[0xFF + 1]; // histgramming foreach (var x in list) { var y = x; for (var p = 0; p < tables.Length; p++) { tables[p][(y + 1) & 0xFF]++; y = y >> 8; } } // sum the histgrams foreach (var table in tables) { table[0] = 0; for (var n = 1; n < table.Length; n++) table[n] += table[n - 1]; } // read/write histgram, copy IList<UInt32> array = new UInt32[list.Count]; Action swap = () => { var temp = list; list = array; array = temp; }; for (var p = 0; p < tables.Length; p++) { var table = tables[p]; foreach (var x in list) { var y = (x >> p * 8) & 0xFF; array[table[y]] = x; table[y]++; } swap(); } } static void CombSort(IList<UInt32> list) { const double shrinkFactor = 1.247330950103979; // 1.0 / (1.0 - 1.0 / Math.Pow(Math.E, Math.PI)); var gap = list.Count; var swapped = true; while (gap > 1 || swapped) { if (gap > 1) gap = (int)(gap / shrinkFactor); swapped = false; for (int i = 0; i + gap < list.Count; i++) { if (list[i].CompareTo(list[i + gap]) > 0) { var t = list[i]; list[i] = list[i + gap]; list[i + gap] = t; swapped = true; } } } } } }ksksts / junk / source — bitbucket.org
結果は次の通り。
適当に比較したときは処理時間がArray.Sortの2倍以上で話にならないと思っていたけど、要素数が膨大になるにつれてそれなりに差が小さくなってきて少し嬉しい。
comb sortは遅いけどバブルソートの改良版と考えるとそれなりに良いような気がする。
size: 1000 radix sort: 3541 (2.88) comb sort: 27689 (22.50) Array.Sort: 1230 (1.00) size: 10000 radix sort: 20451 (1.68) comb sort: 417744 (34.33) Array.Sort: 12167 (1.00) size: 100000 radix sort: 207950 (1.45) comb sort: 5288921 (36.95) Array.Sort: 143143 (1.00) size: 1000000 radix sort: 2081360 (1.30) comb sort: 65056745 (40.50) Array.Sort: 1606207 (1.00)
2009/11/01
C#で浮動小数点数のバイナリ表現(ビットパターン)を取得する
double型の値のバイト表現をlong型で得るために、BitConverter.DoubleToInt64Bitsを使う場合とunsafeで無理やりキャストするのではどちらが速いのか比較してみた。
手元の環境ではBitConverter.DoubleToInt64Bitsの方が速かった(経過時間が50%)けど、ほとんどのケースでは相対的に十分軽い処理になるだろうからどっちでもいい。ただキャストを使うやり方はunsafeの指定が必要だし、見た目もアレだからfloat型を相手にする場合以外は使わないと思う(BitConverter.FloatToInt32Bitsは存在しない)。ストップウォッチのリセットを忘れてた。処理速度に差はない。
そもそもdouble型と整数型との&演算子があればこんなことしなくていいのが残念。
そもそもdouble型と整数型との&演算子があればこんなことしなくていいのが残念。
using System; using System.Collections.Generic; using System.Diagnostics; using System.Linq; using System.Text; namespace BinaryRepresentation { class Program { static void Main(string[] args) { var sw = new Stopwatch(); var random = new Random(0); var num = 1000000; var vals = new double[num]; for (var n = 0; n < vals.Length; n++) vals[n] = random.NextDouble(); // BitConverter.DoubleToInt64Bits var bc = new long[vals.Length]; { sw.Reset(); sw.Start(); for (var n = 0; n < vals.Length; n++) bc[n] = BitConverter.DoubleToInt64Bits(vals[n]); sw.Stop(); Console.WriteLine("BitConverter.DoubleToInt64Bits: {0}", sw.ElapsedMilliseconds); } // unsafe cast var uc = new long[vals.Length]; unsafe { sw.Reset(); sw.Start(); for (var n = 0; n < vals.Length; n++) { var val = vals[n]; uc[n] = *(long*)&val; } sw.Stop(); Console.WriteLine("unsafe cast: {0}", sw.ElapsedMilliseconds); } // correctness for (var n = 0; n < vals.Length; n++) if (bc[n] != uc[n]) Console.WriteLine("bc[{0}]: {1:X}, uc[{0}]: {2:X}", n, bc[n], uc[n]); } } }ksksts / junk / source — bitbucket.org
登録:
投稿 (Atom)