Hatena::ブログ(Diary)

駅長の業務日誌 このページをアンテナに追加 RSSフィード

2011-07-04

もとさや、カスタムイヤフォン

もう2年近く前に書いた記事、その後ケーブルがぶっちぎれてしまい(;_;、この半年ほどで他の2つほど浮気したんだけど、結局元のさやに戻ってしまった。

サウンドケージPCS500IIは、5万円以下のイヤフォンでは今でも最高峰じゃないかと思う。というかこれ以上があるのだろうか‥‥。イヤースピーカーとかもすごいという話だけど持ち歩きに難があるし、やっぱ私のライフスタイルだとイヤフォンであることは大事な要素なので、その当たりを踏まえて浮気歴をさかのぼってみるっす。


SHURE SE535

SHURE 高遮音性イヤホン メタリックブロンズ SE535-V-J

SHURE 高遮音性イヤホン メタリックブロンズ SE535-V-J

ケーブルぶっちぎれて最初に浮気したのがこれ。「バランスド・アーマチュア型じゃないとね!」という独断と偏見の元アマゾンをぽちっ。第一印象は‥‥「なんだこの低音!?」いあ、とにかく重低音域の出方がすごい。ゴゴゴゴ‥‥クラスで響いてくる。一方高音は、出てるのかどうか最初は判断が難しかった。低音に負けてる感じがかなりしてたかな。

「慣らし運転が終われば‥‥」と思って2か月ほど。‥‥やっぱ一番高い所が出てない気がする。低音に引っ張られて聞こえてないのかとも思いましたが、もともと上が〜19kHzって事なんですね。私はかなり上まで聞こえるほうなので、やっぱりこれ1本で済ます気にはなれなくて、結局物欲に負けて次をぽちってしまいましたとさ(ぉぃ)

なお、このイヤフォン、遮音はかなりすごいです。さすが作りなれてるメーカーですので、私の感想はかなりワガママの領域に入っていることにご注意くだされ。


SONY ウォークマン NW-A857

SONY ウォークマン Aシリーズ [メモリータイプ] 64GB ホワイト NW-A857/W

SONY ウォークマン Aシリーズ [メモリータイプ] 64GB ホワイト NW-A857/W

「イヤフォン買ったらプレイヤーも最高級品は当然だね!」と、まるで当然のごとくぽちっ。何がいいって、イコライザどんだけ弄ってもデジタルクリッピングしないまじめな設計。iPodのイコライザくそ過ぎる><。やっぱ、S-MASTERはいいよねーと、これは今も愛用しております。


SONY EX500SL

SONY 密閉型インナーヘッドフォン EX500SL ブラック MDR-EX500SL/B

SONY 密閉型インナーヘッドフォン EX500SL ブラック MDR-EX500SL/B

ぽちっとな(アマゾンのクリックは使用上の注意をよく読み、用法、用量を守り正しくお使い下さい)。第一印象は「音場スゲー!」。SE535に比べてフラットな感じ。ただ、下は同じくらい、上は間違いなくこっちの方が出てます。全体にすっきり聞かせる音で万人受けするのではないでしょうか?

ただ、気になったのは

  • やたらと風の音を拾う
  • 微妙に違う、なんていうかちり紙1枚、間に挟んだ感じ

風を拾うのは形が悪すぎる><。SE535に比べても遮音に優れてるわけでは無い上に、風に逆らった形状してるせいでとにかく「ゴーーーっ」という音を拾います。風が強い日だと音が負けちゃう(涙)

後、これは感覚の問題かもしれないけど、とにかく「ちょっと違う」んですね。音域はかなり出てるのに、微妙に音が甘い。構造上あちこちにフィルタが入ってるようで、音響特性を揃えようとしすぎてるのかなあ。

あと、イコライザを弄ると、女性ボーカルの高音に出るブレスが耳にきつすぎて、弄りようがなかった。かといって下げるのもなんか違うなーという、なんとなくすっきりしない感がずっと漂っていたのです。


結局元さやなのだ

そして結局、マイヤーさんに連絡してドライバユニットだけ新しいの買いました。なぜか姉妹品のPCS300IIまで送ってくるという‥‥、いやいやおじさん、ちゃんと儲かる仕事しようよ><。せっかくなので聞き比べてみたりしましたが、結果、やっぱり自分にはPCS500IIが良いなという結論。さて次の記事ではその音の良さに自分なりに迫ってみるわけです。

そのイヤフォン、鼓膜を引っ張れますか?

まず先に、PCS500IIの悪いところ。「低音が足りない」こればっかりはどうしようもないみたい。先の記事の2つに比べ、低音のドライブは完全に負けてます。一番下の方の周波数が出てない感じ。ウォークマンのイコライザでかなり補強は出来ますが、出ないものは出ないので、これはもう白旗出しちゃうしかないと思います。

逆に言えばそれ以外完璧です。このイヤフォンは「あらゆるノイズ音をきわめて正確に」再現します。スネアドラム・ギターのピック・女性ボーカルのブレスなど‥‥。ノイズ音の立ち上がりと沈み込みのスピードが半端じゃありません。普通これだけ強弱が付くと金属っぽい変な感じも同時にするんですが、PCS500IIにはそれもない。楽器の音がとてもきれいに聞こえるので、このイヤフォンで聞くと曲の評価が普通に★1つ上がります(笑)。

最初はカスタムならではの遮音の恩恵かと思ってました。しかし今回、同じカスタム耳栓に使えるPCS300IIと聴き比べることが出来、そこで気が付いたことがあるんですよね。

PCS300IIはプレイヤーの音量が同じならこっちの方が音圧が大きくでます。BAシングルドライバーの元気のある音です。ただ、その分音像が不明瞭で、上と下の音があいまいです。高音は出てない気がする。AM放送よりまし、FM放送より劣る。そんな感じの音。

どこに音の違いがあるのか。私はPCS300IIには鼓膜を「引っ張る」力が無いんだと、結論付けました。‥‥って引っ張るって何?

このカスタムイヤフォン耳栓ですが、遮音もすごいですが、その上「気密性」がすごいです。SE535当たりの遮音もすごいですが、これはあくまで「耳の入り口の音の通り道をふさいでる」ところまでで、気密にはしてないと思います。

このイヤフォン耳栓は完全に気密状態になります。耳から取り外すとき、急いで取り外したりすると鼓膜が引っ張られて痛くなったりするほどです。風船を膨らますようにして作る、このイヤフォンの独特のカスタム作業に秘密がありそう。

そして良い音を聞くコツは「耳の空気を追い出すようにして、最終的に少し耳の中を負圧にする」ように付けることです。この時PCS500IIは最高の空間を作り出します。

負圧を作ることで、イヤフォンドライバは鼓膜を叩くだけじゃなく、引っ張ることもできるようになる。ドライバに十分な力があれば、鼓膜までケーブル直結させたような正確なドライブが出来る。それがPCS500IIの秘密なんだと悟りました。

ノイズを正確に出せるという事は、波形の位相まで揃ってるという事。位相が狂うとイコライザで高音を持ち上げたときに高音のピークが間延びして耳障りになりますが、このイヤフォンにはそれがありません。デジタルイコライザであれば位相が狂わずに耳に届きます。

でも結局、ウォークマンとの組み合わせでは「クリアバス最大」で低音に元気をつけるだけで、後はノーマルが一番きれいに出るんですねー。

ほんっと、イヤフォンに5万出せる人は、1回「PCS500II」作ったほうがいい。本当にクリアな、気持ちいいサウンドですから。ノイズ音を正確に鳴らすには本当に難しいんですが、PCS500IIはそれが出来る数少ないイヤフォンの一つだと思います。

2010-10-16 良い晴れだ

Wordpress.com

あちらがなかなかよいよいなので移行しようかと検討中。

日記と通常のホームページを兼用できそうなのがよいよね。とりあえずファイルに関してはzipで置ければいいし‥‥。

ネットワーク更新がちょっと微妙だけど、2GBもあれば普通にホームページ持つにはちょうど良い感じがする。はてなよりガジェット配置の自由度があることが確認できたら移行しよう、うん。

2010-10-15 妙に長い、な

タスク並列ということ

何故かReactive Extensionsぐぐる検索で一時トップになってて慌てましたが少し下がってホッとしました。いあ、どう考えてもうちはトップじゃないでしょぐぐるさん!学習向けの紹介サイトならこことかこことかが分りやすくまとめていますので、まずはこちらを覗いてみることをお勧めします。

追い越し禁止?

さて表題の件ですが。私はRxを処理パイプのような感覚で理解しようしてたのですが、少し疑問がありました。「Rxはどんな単位で処理を並列化、あるいは直列化してるのか?」。もうちょっと具体的には「処理パイプの中で後から投入されたデータの処理が先に終わることはありうるのか?」ということ。

ということで実際にやってみた。

処理パイプの構成

今回の試験用処理パイプは、以下の構成になっています。

  • 入力は1〜9のRange
  • スケジューラはScheduler.ThreadPool
  • 実行処理はコンソールへの出力(スレッド番号など)
  • 処理内でランダムにSleepを入れる

この構成の部品を適当に組み合わせてパイプを構成し、試験することとします。

コード(超長いので折りたたむ)

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

namespace ZStudy.Rx.Parallel
{
    public static class Program
    {
        static void Main(string[] args)
        {
            // スレッドプールに対してジョブを発行するパイプ
            var pipe = Observable
                .Range(1, 9, Scheduler.ThreadPool)
                ;
            var stopwatch = new System.Diagnostics.Stopwatch();

            // ■1つのパイプを実行
            Console.WriteLine("#### TEST 1 ####");
            stopwatch.Restart();
            pipe
                .ConsoleWrite("1 ")
                .Run();
            stopwatch.Stop();
            Console.WriteLine("#### TEST 1 ==> time {0:00000}ms\n\n"
                , stopwatch.ElapsedMilliseconds);
            // ---> 処理は直列化されています。
            //      1つのタスクが終わるまで次はスケジュールされません。


            // ■2つのパイプをマージ
            Console.WriteLine("#### TEST 2 ####");
            stopwatch.Restart();
            Observable.Merge(
                pipe.ConsoleWrite("1 "),
                pipe.ConsoleWrite(" 2"))
                .Run(cnt => {
                    var text = string.Format("({0:0})[--] :                         MERGE({1:00})",
                        cnt, Thread.CurrentThread.ManagedThreadId);
                    Console.WriteLine("({1:HH:mm:ss.fff}) {0}", text, DateTimeOffset.Now);
                });
            stopwatch.Stop();
            Console.WriteLine("#### TEST 2 ==> time {0:00000}ms\n\n"
                , stopwatch.ElapsedMilliseconds);
            // ---> 2つのタスクは並列実行されます。
            //      それぞれのタスクは直列に実行されています。


            // ■2つのパイプをZip
            Console.WriteLine("#### TEST 3 ####");
            stopwatch.Restart();
            Observable.Zip(
                pipe.ConsoleWrite("1 "),
                pipe.ConsoleWrite(" 2"),
                (a, b) => Tuple.Create(a, b))
                .Run(pair => {
                    var text = string.Format("({0:0})[--] :                         ZIP({1:00}) [{2:00},{3:00}]",
                        pair.Item1, Thread.CurrentThread.ManagedThreadId,
                        pair.Item1, pair.Item2);
                    Console.WriteLine("({1:HH:mm:ss.fff}) {0}", text, DateTimeOffset.Now);
                })
                ;
            stopwatch.Stop();
            Console.WriteLine("#### TEST 2 ==> time {0:00000}ms\n\n"
                , stopwatch.ElapsedMilliseconds);
            // ---> 2つのタスクは並列実行されます。
            //      それぞれのタスクは直列に実行されています。


            // ■1つのパイプに2つの処理を続けて実行
            Console.WriteLine("#### TEST 4 ####");
            stopwatch.Restart();
            pipe
                .ConsoleWrite("1 ")
                .ConsoleWrite(" 2")
                .Run();
            stopwatch.Stop();
            Console.WriteLine("#### TEST 4 ==> time {0:00000}ms\n\n"
                , stopwatch.ElapsedMilliseconds);
            // ---> 2つの処理は直列実行されます。
            //      2つの処理が終わるまで次のタスクはスケジュールされません。


            // ■1つのパイプに2つの処理をスケジューラ切り替えをはさんで実行
            Console.WriteLine("#### TEST 5 ####");
            stopwatch.Restart();
            pipe
                .ObserveOn(Scheduler.ThreadPool)
                .ConsoleWrite("1 ")
                .ObserveOn(Scheduler.ThreadPool)
                .ConsoleWrite(" 2")
                .Run();
            stopwatch.Stop();
            Console.WriteLine("#### TEST 5 ==> time {0:00000}ms\n\n"
                , stopwatch.ElapsedMilliseconds);
            // ---> 2つの処理は並列実行されます。
            //      パイプ内で処理が追い抜かれることはありません。


            Console.WriteLine("\n#### 終了しました、何かキーを押してください。 ####");
            Console.Read();
        }

        public static IObservable<int> ConsoleWrite(this IObservable<int> pipe, string name)
        {
            var rand = new Random(name.GetHashCode());
            return pipe
                .Do(count => {
                    var sleepTime = rand.Next(999);
                    var title = string.Format("({0:0})[{1}]", count, name);
                    Console.WriteLine("({0:HH:mm:ss.fff}) {1} : START({2:00}/{3:000}ms)"
                        , DateTimeOffset.Now, title
                        , Thread.CurrentThread.ManagedThreadId, sleepTime);

                    Thread.Sleep(sleepTime);

                    Console.WriteLine("({0:HH:mm:ss.fff}) {1} :                 END({2:00})"
                        , DateTimeOffset.Now, title
                        , Thread.CurrentThread.ManagedThreadId, sleepTime);
                })
                ;
        }

    }

}

TEST1:1処理だけのパイプに10個投入

実行結果

#### TEST 1 ####
(01:38:22.493) (1)[1 ] : START(06/630ms)
(01:38:23.132) (1)[1 ] :                 END(06)
(01:38:23.135) (2)[1 ] : START(10/668ms)
(01:38:23.803) (2)[1 ] :                 END(10)
(01:38:23.803) (3)[1 ] : START(06/391ms)
(01:38:24.194) (3)[1 ] :                 END(06)
(01:38:24.194) (4)[1 ] : START(12/875ms)
(01:38:25.069) (4)[1 ] :                 END(12)
(01:38:25.069) (5)[1 ] : START(06/276ms)
(01:38:25.345) (5)[1 ] :                 END(06)
(01:38:25.345) (6)[1 ] : START(11/449ms)
(01:38:25.794) (6)[1 ] :                 END(11)
(01:38:25.794) (7)[1 ] : START(10/250ms)
(01:38:26.044) (7)[1 ] :                 END(10)
(01:38:26.044) (8)[1 ] : START(11/184ms)
(01:38:26.228) (8)[1 ] :                 END(11)
(01:38:26.228) (9)[1 ] : START(10/608ms)
(01:38:26.836) (9)[1 ] :                 END(10)
#### TEST 1 ==> time 04371ms

ここから読み取れるのは、

  • 1処理は任意のスレッドプールに割り振られる
  • スレッドプールは切り替わるが、処理の追い越しは発生しない

1パイプ内のデータは、たとえスリープが発生したとしても処理の追い越しは発生しません。全ての処理が一度にスケジュールされるのではなく、1処理が終わるたびに次の処理がスケジュールされるようです。

考えてみればこれはタスク並列、またはアクターモデルにおける重要な性格。1つの処理パイプにおいては処理が直列化されることで、並行処理にまつわるもろもろの同期処理を意識せずにロジックを組むことが出来るのです。

TEST2:2つのパイプをマージ

今度はパイプを2つ用意し、Mergeしてみました。

#### TEST 2 ####
(01:38:26.852) (1)[1 ] : START(06/630ms)
(01:38:26.853) (1)[ 2] : START(11/349ms)
(01:38:27.203) (1)[ 2] :                 END(11)
(01:38:27.204) (1)[--] :                         MERGE(11)
(01:38:27.204) (2)[ 2] : START(10/506ms)
(01:38:27.483) (1)[1 ] :                 END(06)
(01:38:27.483) (1)[--] :                         MERGE(06)
(01:38:27.484) (2)[1 ] : START(06/668ms)
(01:38:27.711) (2)[ 2] :                 END(10)
(01:38:27.711) (2)[--] :                         MERGE(10)
(01:38:27.712) (3)[ 2] : START(11/316ms)
(01:38:28.028) (3)[ 2] :                 END(11)
(01:38:28.028) (3)[--] :                         MERGE(11)
(01:38:28.029) (4)[ 2] : START(10/052ms)
(01:38:28.084) (4)[ 2] :                 END(10)
(01:38:28.084) (4)[--] :                         MERGE(10)
(01:38:28.085) (5)[ 2] : START(11/758ms)
(01:38:28.153) (2)[1 ] :                 END(06)
(01:38:28.153) (2)[--] :                         MERGE(06)
(01:38:28.154) (3)[1 ] : START(13/391ms)
(01:38:28.545) (3)[1 ] :                 END(13)
(01:38:28.545) (3)[--] :                         MERGE(13)
(01:38:28.546) (4)[1 ] : START(12/875ms)
(01:38:28.843) (5)[ 2] :                 END(11)
(01:38:28.843) (5)[--] :                         MERGE(11)
(01:38:28.844) (6)[ 2] : START(13/837ms)
(01:38:29.422) (4)[1 ] :                 END(12)
(01:38:29.422) (4)[--] :                         MERGE(12)
(01:38:29.423) (5)[1 ] : START(11/276ms)
(01:38:29.681) (6)[ 2] :                 END(13)
(01:38:29.681) (6)[--] :                         MERGE(13)
(01:38:29.682) (7)[ 2] : START(12/041ms)
(01:38:29.700) (5)[1 ] :                 END(11)
(01:38:29.700) (5)[--] :                         MERGE(11)
(01:38:29.701) (6)[1 ] : START(10/449ms)
(01:38:29.723) (7)[ 2] :                 END(12)
(01:38:29.723) (7)[--] :                         MERGE(12)
(01:38:29.724) (8)[ 2] : START(11/672ms)
(01:38:30.150) (6)[1 ] :                 END(10)
(01:38:30.150) (6)[--] :                         MERGE(10)
(01:38:30.151) (7)[1 ] : START(13/250ms)
(01:38:30.397) (8)[ 2] :                 END(11)
(01:38:30.397) (8)[--] :                         MERGE(11)
(01:38:30.398) (9)[ 2] : START(10/397ms)
(01:38:30.401) (7)[1 ] :                 END(13)
(01:38:30.401) (7)[--] :                         MERGE(13)
(01:38:30.401) (8)[1 ] : START(12/184ms)
(01:38:30.586) (8)[1 ] :                 END(12)
(01:38:30.586) (8)[--] :                         MERGE(12)
(01:38:30.587) (9)[1 ] : START(06/608ms)
(01:38:30.795) (9)[ 2] :                 END(10)
(01:38:30.795) (9)[--] :                         MERGE(10)
(01:38:31.195) (9)[1 ] :                 END(06)
(01:38:31.195) (9)[--] :                         MERGE(06)
#### TEST 2 ==> time 04356ms

予想通り2つのパイプは平行に動作し、Mergeポイントでのみデータが直列化されます。Mergeではデータが来た順に受け付けますので、2つのパイプの処理のどちらが早いかによって受け付け順が異なることになります。


TEST3:2つのパイプをZip

Mergeあらため、Zip。ここで少し予想外な挙動です。

#### TEST 3 ####
(01:41:15.755) (1)[1 ] : START(11/630ms)
(01:41:15.755) (1)[ 2] : START(10/349ms)
(01:41:16.105) (1)[ 2] :                 END(10)
(01:41:16.111) (2)[ 2] : START(14/506ms)
(01:41:16.385) (1)[1 ] :                 END(11)
(01:41:16.390) (1)[--] :                         ZIP(11) [01,01]
(01:41:16.390) (2)[1 ] : START(13/668ms)
(01:41:16.618) (2)[ 2] :                 END(14)
(01:41:16.618) (3)[ 2] : START(11/316ms)
(01:41:16.935) (3)[ 2] :                 END(11)
(01:41:16.935) (4)[ 2] : START(14/052ms)
(01:41:16.988) (4)[ 2] :                 END(14)
(01:41:16.988) (5)[ 2] : START(14/758ms)
(01:41:17.059) (2)[1 ] :                 END(13)
(01:41:17.059) (2)[--] :                         ZIP(13) [02,02]
(01:41:17.060) (3)[1 ] : START(11/391ms)
(01:41:17.454) (3)[1 ] :                 END(11)
(01:41:17.454) (3)[--] :                         ZIP(11) [03,03]
(01:41:17.455) (4)[1 ] : START(10/875ms)
(01:41:17.747) (5)[ 2] :                 END(14)
(01:41:17.747) (6)[ 2] : START(14/837ms)
(01:41:18.330) (4)[1 ] :                 END(10)
(01:41:18.330) (4)[--] :                         ZIP(10) [04,04]
(01:41:18.331) (5)[1 ] : START(13/276ms)
(01:41:18.585) (6)[ 2] :                 END(14)
(01:41:18.585) (7)[ 2] : START(15/041ms)
(01:41:18.607) (5)[1 ] :                 END(13)
(01:41:18.607) (5)[--] :                         ZIP(13) [05,05]
(01:41:18.608) (6)[1 ] : START(11/449ms)
(01:41:18.627) (7)[ 2] :                 END(15)
(01:41:18.627) (8)[ 2] : START(15/672ms)
(01:41:19.057) (6)[1 ] :                 END(11)
(01:41:19.057) (6)[--] :                         ZIP(11) [06,06]
(01:41:19.058) (7)[1 ] : START(11/250ms)
(01:41:19.300) (8)[ 2] :                 END(15)
(01:41:19.300) (9)[ 2] : START(10/397ms)
(01:41:19.308) (7)[1 ] :                 END(11)
(01:41:19.308) (7)[--] :                         ZIP(11) [07,07]
(01:41:19.309) (8)[1 ] : START(11/184ms)
(01:41:19.493) (8)[1 ] :                 END(11)
(01:41:19.493) (8)[--] :                         ZIP(11) [08,08]
(01:41:19.494) (9)[1 ] : START(14/608ms)
(01:41:19.698) (9)[ 2] :                 END(10)
(01:41:20.102) (9)[1 ] :                 END(14)
(01:41:20.102) (9)[--] :                         ZIP(14) [09,09]
#### TEST 3 ==> time 04368ms

片方が先に到着したらそちらの処理は待機するかと思いましたが、待機せずそのまま続行しています。Zip処理では値をバッファし、両方がそろった段階でバッファから取り出す実装です。考えてみればパイプへのデータ投入は待ったなしですので、受け身の処理が基本であるRxではデータの同期はZip側で面倒見る必要があるのですね。

TEST4:1つのパイプに2つの処理

パイプに、同じ処理を2回実行させてみます。

#### TEST 4 ####
(01:41:20.104) (1)[1 ] : START(11/630ms)
(01:41:20.735) (1)[1 ] :                 END(11)
(01:41:20.735) (1)[ 2] : START(11/349ms)
(01:41:21.085) (1)[ 2] :                 END(11)
(01:41:21.085) (2)[1 ] : START(14/668ms)
(01:41:21.754) (2)[1 ] :                 END(14)
(01:41:21.754) (2)[ 2] : START(14/506ms)
(01:41:22.262) (2)[ 2] :                 END(14)
(01:41:22.262) (3)[1 ] : START(15/391ms)
(01:41:22.654) (3)[1 ] :                 END(15)
(01:41:22.654) (3)[ 2] : START(15/316ms)
(01:41:22.971) (3)[ 2] :                 END(15)
(01:41:22.971) (4)[1 ] : START(11/875ms)
(01:41:23.847) (4)[1 ] :                 END(11)
(01:41:23.847) (4)[ 2] : START(11/052ms)
(01:41:23.900) (4)[ 2] :                 END(11)
(01:41:23.900) (5)[1 ] : START(12/276ms)
(01:41:24.177) (5)[1 ] :                 END(12)
(01:41:24.177) (5)[ 2] : START(12/758ms)
(01:41:24.936) (5)[ 2] :                 END(12)
(01:41:24.936) (6)[1 ] : START(15/449ms)
(01:41:25.386) (6)[1 ] :                 END(15)
(01:41:25.386) (6)[ 2] : START(15/837ms)
(01:41:26.224) (6)[ 2] :                 END(15)
(01:41:26.224) (7)[1 ] : START(13/250ms)
(01:41:26.475) (7)[1 ] :                 END(13)
(01:41:26.475) (7)[ 2] : START(13/041ms)
(01:41:26.517) (7)[ 2] :                 END(13)
(01:41:26.517) (8)[1 ] : START(11/184ms)
(01:41:26.702) (8)[1 ] :                 END(11)
(01:41:26.702) (8)[ 2] : START(11/672ms)
(01:41:27.375) (8)[ 2] :                 END(11)
(01:41:27.375) (9)[1 ] : START(12/608ms)
(01:41:27.984) (9)[1 ] :                 END(12)
(01:41:27.984) (9)[ 2] : START(12/397ms)
(01:41:28.382) (9)[ 2] :                 END(12)
#### TEST 4 ==> time 08276ms

結果は、「2つの処理が終わるまで次はスケジュールされない」です。スケジューラへの処理割振り単位は「2つの処理がまとめられて」行われています。単純に関数合成がされる範囲においては並行処理には分割されません。

TEST5:2つの処理の間にスケジューラ切り替えを挟む

今度は処理の間にObserveOnを挟んでみました。

#### TEST 5 ####
(01:41:28.390) (1)[1 ] : START(15/630ms)
(01:41:29.020) (1)[1 ] :                 END(15)
(01:41:29.020) (2)[1 ] : START(15/668ms)
(01:41:29.020) (1)[ 2] : START(12/349ms)
(01:41:29.373) (1)[ 2] :                 END(12)
(01:41:29.691) (2)[1 ] :                 END(15)
(01:41:29.691) (3)[1 ] : START(15/391ms)
(01:41:29.691) (2)[ 2] : START(14/506ms)
(01:41:30.083) (3)[1 ] :                 END(15)
(01:41:30.083) (4)[1 ] : START(12/875ms)
(01:41:30.199) (2)[ 2] :                 END(14)
(01:41:30.199) (3)[ 2] : START(14/316ms)
(01:41:30.516) (3)[ 2] :                 END(14)
(01:41:30.959) (4)[1 ] :                 END(12)
(01:41:30.959) (5)[1 ] : START(12/276ms)
(01:41:30.959) (4)[ 2] : START(10/052ms)
(01:41:31.013) (4)[ 2] :                 END(10)
(01:41:31.236) (5)[1 ] :                 END(12)
(01:41:31.236) (5)[ 2] : START(11/758ms)
(01:41:31.236) (6)[1 ] : START(13/449ms)
(01:41:31.686) (6)[1 ] :                 END(13)
(01:41:31.686) (7)[1 ] : START(10/250ms)
(01:41:31.937) (7)[1 ] :                 END(10)
(01:41:31.937) (8)[1 ] : START(14/184ms)
(01:41:31.995) (5)[ 2] :                 END(11)
(01:41:31.995) (6)[ 2] : START(12/837ms)
(01:41:32.122) (8)[1 ] :                 END(14)
(01:41:32.122) (9)[1 ] : START(10/608ms)
(01:41:32.731) (9)[1 ] :                 END(10)
(01:41:32.833) (6)[ 2] :                 END(12)
(01:41:32.833) (7)[ 2] : START(14/041ms)
(01:41:32.875) (7)[ 2] :                 END(14)
(01:41:32.875) (8)[ 2] : START(10/672ms)
(01:41:33.548) (8)[ 2] :                 END(10)
(01:41:33.548) (9)[ 2] : START(11/397ms)
(01:41:33.946) (9)[ 2] :                 END(11)
#### TEST 5 ==> time 05562ms

すると、処理1と2は同時に実行されるようになります。スケジュール単位はObserveOnで区切られるため、処理の追い抜きが発生しない範囲で並行処理が行われるようになりました。

総論:タスク並列とデータ並列の使い分け

Rxに限らずアクターモデル系技術の根幹の思想は「如何に並行処理を感じさせずに非同期処理をプログラム出来るか」です。「実行は非同期化されること」「同期処理のような直感的なコードが書けること」をフレームワークが保証するからこそ、デバッグが容易な非同期処理を実装できるようになっています。

処理の流れにいつ終わるかわからない非同期処理が挟まれる場合においても、コードを分散させずに処理の流れを記述する事が出来ます。また、パイプ内の処理は直列化されますので同期を心配する必要はありません。

そのかわり、処理順序にこだわる必要が無い場合においても1パイプ内で処理が直列化されますので、CPUをぶんぶん回すような並行処理分散が行われるわけではありません。

もう一つの並行処理技術としてPLINQの実装がありますが、こちらは逆にデータ並列モデルを採用しています。大量のデータをなるべくCPUを遊ばせることなしに処理させることが目的であるため、PLINQでは最終結果に影響しない限り、処理順序に拘りません。コード記述者は処理順序が影響しない単位でデータ処理を分割することを心がけることで最大の効果を発揮する事が出来ます。

タスク並列アプローチでも適切な関数を準備すればデータ並列的な処理分散は可能なのですが、.NETのアプローチは2つの役割を明確に分離しているようで、見たところRxには2つのパイプをまとめる関数はあっても、複数のパイプに分散したり実行順序を守らなくていい処理分散を明示的に行う関数群は用意されていないようです。負荷分散に関してはPLINQが専門家ですね。

幸いどちらもAPIの見た目は似ていますし相互乗り入れも可能です。利用者はどちらか一方だけを盲目的に使うのではなく、処理の内容により適切な技術を選ぶのが.NET流の並行処理プログラミングのスタイルじゃないかと思いました。

2010-09-21 うかんだ後

次が遠くなるとき

技術屋とコンテンツ屋の意識の違い、大きいのは「設計が古くなることへの不安」の感じ方だと思います。

設計が古くなる、というのはソフトウェアのコードが古くなること自体もありますが、むしろ「新しい技術の取り込みがしんどくなる」事を指すことが多いです。

設計思想の理想と現実

設計思想はその時々の技術トレンドによって変わりますが、基本的には今の技術を見据えつつ今後数年に出てくるであろう新技術をなるべくなら「簡単に」取り込めることを理想とするものです。

ただ、理想は理想。アプリケーションは「世に出すことが正義」である以上、あまり設計を追い求めてもいけない。伺かの根底の設計は「世に出すこと」を優先した部分がかなり色濃く残っています。それでも当初は基本設計さえぶち壊す勢いでアジャイル開発(‥‥あー、このころはそんな言葉もありませんでしたね)が進んでいたので、「最新技術」は割と近い所に居続けました。

捨てるも地獄、進むも地獄

さて一方、「今、基本設計を全部捨てて進められるか?」、となると少し停滞状況。技術屋さんはともかく、全部捨てられたら古いコンテンツをどうするのかという問題が出る。古いものをサポートしつつ新しいものを取り込むのは、非常に骨が折れる作業です。特に、プログラムコードがコンポーネント化(部品になって入れ替えることができる仕組み)されていない部分のメンテナンス負荷は顕著になります。

バラバラ殺人事件

今の伺かの設計でコンポーネント化されていない部分は「ゴースト描画系」と「プロセス空間」です。

まず、描画系ですが。AIはうまく切り離されていますが描画に関してはプラットフォームが丸抱えしているため、簡単に拡張を行うことができません。この部分がAIと同じように何らかのコマンド通信だけで繋がっているのであれば、その先を丸ごと差し替えることでどうとでもなる部分があります。また、描画系のトレンドはまさしく日進月歩の領域であり、この分野について自力で最先端に居続けることはほぼ不可能。可能であればうまくオープンソースを取り込める下地が必要な分野です。

もう一つは「プロセス空間」。割と根本的な問題ですが、伺かは1つのプラットフォームにゴーストが複数存在する設計であるため、ゴースト同士の干渉が問題になります。SHIORIは汎用スクリプト言語を使えればかなり楽になるのですが、一方多くの汎用言語はプロセス空間より小さな単位で干渉しないような設計とはなっていません。1つのプラットフォーム内で、「知らない他人同士が作ったゴースト」が干渉せずに存在できる仕組みを構築するのは実は大変なのです。これが「デスクトップアプリが単発で数多く存在する割に、プラットフォームがほとんどない理由」といってもいいかもしれません。

今どきの設計トレンドは、「内部でゴースト毎に別プロセス空間を立ち上げる」となるでしょう。あるいは.NETであるならアプリケーションドメイン(.NETの仮想プロセス空間)で環境の干渉を封じ込める、となります。

求められる技術なのか?

ぶっちゃけるとデスクトップマスコットの分野に「生粋の技術屋」が生まれる可能性が低いんじゃないか?と。技術屋が自分で実装しちゃうと、1体完成させた時点で満足しちゃって、普通プラットフォーム展開まで考えないんでしょう。伺か以降、この分野に単発が多い理由はそんなところだと思ってます。

コンテンツは爆発だ!

なら、「伺的」分野は今後悲観なのか、というと。悲観はしてませんが楽観もしていません。中か外からやってくるかはともかく、この手の分野は突然爆発的に生まれてくる、あるいは生き返るものだと思ってます。互換とかあまり考えずに出てくるでしょう。

その時「伺か」がどうなるのか。たぶんコンバートなりなんなり、あっさり移っちゃうんじゃないかなー。技術屋もコンテンツ屋も楽しければいいんです、魂は消えないです、きっと。

少しずつ変わる道、改革を求める道、黒船がいきなり作る道、色々とあると思いますが、「窓のないゴーストの世界」はあり続ける。きっと大丈夫。

‥‥不安の話はどうなった?

「設計が古い」のは、すぐ解決する問題じゃない。伺かクラスのアプリを本気で再設計するなら、1年はフルタイムで仕事をできる位の人が中心にいないと難しい。普通に仕事で予算切るなら3〜4千万円以下にはならない。絵を含めた総予算でいえば一億の仕事です。だからこそ無責任に楽観でやってみよーなんて言うつもりはないですが、一方で技術力のある学生(またはニート‥‥はっ)が一旗揚げるにはちょうどいいコンテンツです。

なので、技術屋の不安はどーにもならないが、いつかいきなりぶっ飛んだ解決策が提示される、という結論。最新技術が遠い今の状況は、本質的には「伺か」がぶっ飛ばされるまで改善されない。ただしそれは悲観でも誰かが悪いわけでもなく、デスクトップマスコットという技術が持つ本質だと思ってます。表現の世界はいつでも大きな淘汰に晒されている。命が晒されるわけじゃなし、ネガティブにならずに、やりたいことができる船に乗っかればいいと思ってます。

2010-09-11 うかぶまえ

そろそろ懇親会の締切り

うかべん大阪#6 9/18です。懇親会に参加予定の方は週末締切りとなりますので是非ご登録お願いします。正確には月曜日の夜に電話入れる感じです‥‥。

懇親会の方、今のところ27人の模様。50人まで行けるよ!(ってうかべん本会場のほうが40人でいっぱいの罠)

あ、それと、今回は私は完全な裏方です‥‥。よろしくっす。

2010-08-28 このまま放置してたらなんか変な人だな

だんだん電波に

「今」を過去・未来と比較しようとするとなぜか変になるのです。今って止まらないですからねぇ。「今」を意識している「自分」は何だろう、と考え出すと割と楽しいですが応用理系思考としてはどっかで割りきって実用案を模索するのです。

「自我」とは脳が投影する1本の映像だと認識しています。基本的には映像が出た時点で確定してるので、自我が考えてるというより、出てきた段階で結論が出てるようなもの。「分かっちゃいるけどやめられない」という理不尽は、だから実は理不尽でも何でも無く、未来を先読みする行動決定に対して、今を再生している「自我」の方が後追いだからそうなってしまう、という流れなのでしょう。

‥‥はっ、電波さんになってきたので一旦置くぞ。次はもはや訳のわからない話。

0と1の世界

疑問がある。「光には周波数は存在するのか?」

突き詰めれば、光そのものには「時間」がない。普通の物質は、光速に至るにつれて時間の経過が遅くなり、理論上光速で時間が止まる。なら、そもそも光速で行動する光は、それ自身の時間は止まっていると仮定すべきだと、自分は思う。

時間がないなら距離も関係ない。光のエネルギーは、放たれた「刹那」にもう一方に届くのだろう。光は刹那刹那で、2点の空間座標を確率によって情報をつなぐもの、そういう処理をしているんだと考え出すと、割と面白くなってくる。

光の視点で考えるに、「光の周波数」は観測者の都合でしかないのではと、そう思えてきた。時間がない存在に周波数は存在しえない。光はいつも「1」というエネルギーを伝達してるが、受け取る側の相対速度の差で値が決まってるのでは?

楽しー

こういう事を考えるのは楽しい。うん。無駄に宇宙につながってる気がするのはおらだけだろうか。実は割とこういう事を考える人は多くて、そ〜いう人が見つかるたびに密かに抹殺されてるんじゃなかろうかとか。‥‥おや来客のようだ

2010-08-24

意識が生まれる時空

哲学っぽいけど言いたいことはあくまで理系の話。

「心」が「どの時間に」存在しているか意識したことはありますか?「コギト・エルゴ・スム」、我思う故に我あり。自我は「今の時間」存在しているはずです。この「心の時間」と、私たちを包む「リアルな時間」、どのくらいずれているでしょうか?

答えだけ言えば「ズレはない」です。相当かなり正確に一致している。そうじゃないと「歌」は歌えないはずです。正確に世界のリズムに一致しているからこそシンフォニーもダンスも生まれるのです。

しかし、これはよく考えればおかしな話。思考装置「脳」は実際に行動する「前」に行動内容を決定しているはず。そうじゃないと実時間に先回りして行動を起こすことは出来ません。でも、歌う声を出すとき、心の「声」は実際の時間より先にだしているでしょうか?声を出した瞬間と「心の声の瞬間」は一致しているはずです。でも、歌はズレない(‥‥一部のかわいそうな人を除く)

昔「脳は未来予測装置だ」という趣旨のことを書いたことがありますが、しかし「意識」は「心に思った瞬間に」生まれているように思えます。「自我」というものは時間と切り離せない概念ですが、だからこそ「自我」が生まれる瞬間には、脳はすでに別のことを考えているといえます。

意識とは脳の活動そのものではなく、脳やその他の思考装置たちが未来に放った「ミーム」達が今の時間に結像したゴーストのようなもの。そう考えると「意識」は思考装置にまとわりついては居ますが、同次元に居る存在ではありません。思考装置と「意識」は一対一の関係ではない。

今より先回りした未来を思考する「何か」さえ存在すれば、恐らくその集合体は何らかの自我を生み出す可能性がある、と考えています。

「この入力があったらこう出力する」というのでは遅いのですよね。嘘でもいいから未来を予測できて初めて、今の時間を生きる意識が生まれるんだろうと、思っています。

借りぐらしのアリエッティとか

1ヶ月も前に見たんだけど、ソロソロ感想位いいだろう。まあネタバレというわけではないのだけど。というか一言なんだが。

「続編が出たら評価する」としか言いようが無い(^^;。キャラクター・世界観とかはジブリのここ数作で圧倒的に良いと思った。特にヒロイン、アリエッティーの存在感は抜群。

ただ‥‥、「俺たちの明日はどっちだ!」な終わり方なんだ!なんだこの続編臭アフレル終わり方は‥‥。起承転結のうち、「起承」くらいで終わってるなぁ。ああん欲求不満だ!次が出てたみたいのは確かなんだ!

‥‥と、言うくらいには評価してるのかもしれないが、やっぱ出ないよなぁ、むうん。

2010-08-06

Reactive Extensionsとか

.NET4.0sp1搭載予定(と激しく思われる)Reactive Extensions。さらにもう少し調べてみた結果、ものすごく誤解を招きかねない要約として、「WFの競合技術」だと理解した!

実現方法はかなりC#風ですが、結局のところワークフローを実現するための基礎技術であるために、WFがこのままであれば、プログラマ的に使いやすいRxに追いやられてWFは忘れられてしまいそうだなぁと思ったりします。

で、何が出来るのかを自分で理解するためにReactive Extensions早見表、作りかけですが置いときます。拡張メソッド別に、何が出来るのかを一言、代表的なコマンド引数を1つ、無理やり1行にまとめています。編集可能なので気が向いたら補足してください‥‥。

2010-07-19 晴れ時々晴れ^100

英語ができないとだよねぇ

Celectiaは英語のソフトなので開発のやり取りとか全部英語でやってます。はやぶさ関係はこのあたりで議論中。

無理なら無理なりにサイトに登録しようかと思ったんですが、なんかID名称の不具合でログインができないのです。IDに英数字以外を使ってるとかで。でも、登録はできたんですがーーー!登録はあるから登録の時に使ったメールアドレスはもう使えないし、不具合報告しようにもそもそも英語で文章作ることすらままならないし!

おらは英語の偏差値が30台だった学生時代なので、勉強しろとかいう以前に精神的にストレスがこみ上げてくる英語恐怖症‥‥。あーなんていうか悲しいのお、悲しいのお。