最近の更新

関連


その他いろいろ

多機能フィードリーダSynapse製作中

2月までは目が回るほど忙しい

MODxでつくる! 最強のCMSサイト カバー
MODxでつくる! 最強のCMSサイト

2008 10 13

Synapse 0.0.17α

一年と数ヶ月ぶりにフィードリーダSynapse 0.0.17αをリリースしました。

  • フィード巡回時の重さを軽減
  • idがintだったためしばらく使うとオーバーフローする問題を解消(ulongに変更)
  • 「ブラウザで開く」(O)と対にして使うグローバルホットキー「Synapseをアクティブ化する」(Ctrl+Alt+O)
  • 未読件数が時々狂うのは未だ直ってない

これだけ時間を空けると自分の書いたソースでも訳が分からない。一度リファクタリングしないとこのまま開発するのは難しそう。

引き続きSIMDの練習。

モノクロやネガのようにリニアに読み書きすればいい処理と違って、y方向近傍を読まざるを得ない座標変換が入ると、キャッシュミスによる遅延が効いてきそうですが、避けて通るわけにも行かないので挑戦してみます。
まずは画像のスケール変換を書いてみました。

static wxImage ResizeImage(wxImage &src_image, double size) {
 int src_real_w = src_image.GetWidth();
 int src_real_h = src_image.GetHeight();
 int dst_real_w = src_real_w*size;
 int dst_real_h = src_real_h*size;
 // 実際のビットマップの大きさ
 int dst_w = std::min<int>(src_real_w, src_real_w*size);
 int dst_h = std::min<int>(src_real_h, src_real_h*size);
 int src_w = dst_w*src_real_w/dst_real_w;
 int src_h = dst_h*src_real_h/dst_real_h;
 
 if (dst_w*dst_h<=4)
  return wxImage();

 unsigned char* src = src_image.GetData();
 unsigned char* src4 = (unsigned char*)malloc(src_w*src_h*3*4);

 int aligned_dst_size = dst_w*dst_h*3;
 aligned_dst_size += 3-(aligned_dst_size+3)%4;
 unsigned char* dst = (unsigned char*)malloc(aligned_dst_size);
 unsigned char* dst4 = (unsigned char*)malloc(dst_w*dst_h*3*4);
 // byte => DWORD
 for (int v=0; v<src_h; v++)
  for (int u=0; u<src_w; u++) {
   int p_s = (v*src_real_w + u)*3;
   int p_s4 = (v*src_w + u)*3*4;
   src4[p_s4 + 0] = src[p_s + 0];
   src4[p_s4 + 4] = src[p_s + 1];
   src4[p_s4 + 8] = src[p_s + 2];
  }
 switch (0) {
  case 0: {
   // bilinear
   // 参照先srcを越えない範囲
   int v_to = std::min<int>(dst_h-1, (src_h-1)*size);
   __m128 xmm_dummy;
   float dummy = 0;
   /* 水平用 */
   xmm_dummy = _mm_load1_ps(&dummy);
   // RGB一括ロードのマスク
   __m128i xmm_mask;
   xmm_mask.m128i_i32[0] = 0x000000ff;
   xmm_mask = _mm_shuffle_epi32(xmm_mask, 0);
   xmm_mask.m128i_i32[3] = 0;
   
   char *buf = (char*)_mm_malloc(16, sizeof(__m128i));
   for (int v=1; v<v_to; v++) {
     long l = v/size;
     long l1 = l+1;
     double wv = v/size - l;
     double wv1 = 1 - wv;
     // プリフェッチ
     _mm_prefetch((const char*)(src4 + l*src_w*3*4), _MM_HINT_T2);
     _mm_prefetch((const char*)(src4 + l1*src_w*3*4), _MM_HINT_T2);

     // 参照先srcを越えない範囲
     int u_to = std::min<int>(dst_w-1, (src_w-1)*size);
     for (int u=1; u<u_to; u++) {
      long k = u/size;
      long k1 = k+1;

      double wu = u/size - k;
      double wu1 = 1 - wu;
      long p_s_0_0 = (l*src_w + k)*3*4; /* (k, l) */
      long p_s_0_1 = (l*src_w + k1)*3*4; /* (k+1, l) */
      long p_s_1_0 = (l1*src_w + k)*3*4; /* (k, l+1) */
      long p_s_1_1 = (l1*src_w + k1)*3*4; /* (k+1, l+1) */
      long p_d = (v*dst_w+u)*3*4;
      /*
      dst4[p_d + 0] = (src4[p_s_0_0 + 0]*wu1 + src4[p_s_0_1 + 0]*wu)*wv1
       + (src4[p_s_1_0 + 0]*wu1 + src4[p_s_1_1 + 0]*wu)*wv;

      dst4[p_d + 4] = (src4[p_s_0_0 + 4]*wu1 + src4[p_s_0_1 + 4]*wu)*wv1
       + (src4[p_s_1_0 + 4]*wu1 + src4[p_s_1_1 + 4]*wu)*wv;

      dst4[p_d + 8] = (src4[p_s_0_0 + 8]*wu1 + src4[p_s_0_1 + 8]*wu)*wv1
       + (src4[p_s_1_0 + 8]*wu1 + src4[p_s_1_1 + 8]*wu)*wv;
      */
      __m128 xmm_dst, xmm_wu, xmm_wv;

      // DWORD転送 xmm_src_all_d: 0 [b] [g] [r]
      __m128i xmm_all_tmp;
      __m128 xmm_src_all_0, xmm_src_all_1, xmm_src_all_2, xmm_src_all_3;
      xmm_all_tmp = _mm_loadu_si128((__m128i *)(src4 + p_s_0_0));
      xmm_all_tmp = _mm_and_si128(xmm_mask, xmm_all_tmp);
      xmm_src_all_0 = _mm_cvtepi32_ps(xmm_all_tmp);
      xmm_all_tmp = _mm_loadu_si128((__m128i *)(src4 + p_s_0_1));
      xmm_all_tmp = _mm_and_si128(xmm_mask, xmm_all_tmp);
      xmm_src_all_1 = _mm_cvtepi32_ps(xmm_all_tmp);
      xmm_all_tmp = _mm_loadu_si128((__m128i *)(src4 + p_s_1_0));
      xmm_all_tmp = _mm_and_si128(xmm_mask, xmm_all_tmp);
      xmm_src_all_2 = _mm_cvtepi32_ps(xmm_all_tmp);
      xmm_all_tmp = _mm_loadu_si128((__m128i *)(src4 + p_s_1_1));
      xmm_all_tmp = _mm_and_si128(xmm_mask, xmm_all_tmp);
      xmm_src_all_3 = _mm_cvtepi32_ps(xmm_all_tmp);
      
      // 色ごとに集める xmm_rtmp: r3 r2 r1 r0
      __m128 xmm_colors_tmp[3]; // r, g, b
      xmm_colors_tmp[0] = _mm_shuffle_ps(xmm_src_all_0, xmm_src_all_0, SHUFFLEBITS(3, 3, 3, 0)); // 0 0 0 r0
      xmm_colors_tmp[0] = _mm_or_ps(xmm_colors_tmp[0], _mm_shuffle_ps(xmm_src_all_1, xmm_src_all_1, SHUFFLEBITS(3, 3, 0, 3))); // 0 0 r1 0
      xmm_colors_tmp[0] = _mm_or_ps(xmm_colors_tmp[0], _mm_shuffle_ps(xmm_src_all_2, xmm_src_all_2, SHUFFLEBITS(3, 0, 3, 3))); // 0 r2 0 0
      xmm_colors_tmp[0] = _mm_or_ps(xmm_colors_tmp[0], _mm_shuffle_ps(xmm_src_all_3, xmm_src_all_3, SHUFFLEBITS(0, 3, 3, 3))); // r3 0 0 0
      xmm_colors_tmp[1] = _mm_shuffle_ps(xmm_src_all_0, xmm_src_all_0, SHUFFLEBITS(3, 3, 3, 1));
      xmm_colors_tmp[1] = _mm_or_ps(xmm_colors_tmp[1], _mm_shuffle_ps(xmm_src_all_1, xmm_src_all_1, SHUFFLEBITS(3, 3, 1, 3)));
      xmm_colors_tmp[1] = _mm_or_ps(xmm_colors_tmp[1], _mm_shuffle_ps(xmm_src_all_2, xmm_src_all_2, SHUFFLEBITS(3, 1, 3, 3)));
      xmm_colors_tmp[1] = _mm_or_ps(xmm_colors_tmp[1], _mm_shuffle_ps(xmm_src_all_3, xmm_src_all_3, SHUFFLEBITS(1, 3, 3, 3)));
      xmm_colors_tmp[2] = _mm_shuffle_ps(xmm_src_all_0, xmm_src_all_0, SHUFFLEBITS(3, 3, 3, 2));
      xmm_colors_tmp[2] = _mm_or_ps(xmm_colors_tmp[2], _mm_shuffle_ps(xmm_src_all_1, xmm_src_all_1, SHUFFLEBITS(3, 3, 2, 3)));
      xmm_colors_tmp[2] = _mm_or_ps(xmm_colors_tmp[2], _mm_shuffle_ps(xmm_src_all_2, xmm_src_all_2, SHUFFLEBITS(3, 2, 3, 3)));
      xmm_colors_tmp[2] = _mm_or_ps(xmm_colors_tmp[2], _mm_shuffle_ps(xmm_src_all_3, xmm_src_all_3, SHUFFLEBITS(2, 3, 3, 3)));

      /* wu1 wu wu1 wu */
      xmm_wu.m128_f32[0] = wu1;
      xmm_wu.m128_f32[1] = wu;
      xmm_wu = _mm_shuffle_ps(xmm_wu, xmm_wu, SHUFFLEBITS(1, 0, 1, 0));
      /* wv1 wv1 wv wv */
      xmm_wv.m128_f32[0] = wv1;
      xmm_wv.m128_f32[2] = wv;
      xmm_wv = _mm_shuffle_ps(xmm_wv, xmm_wv, SHUFFLEBITS(2, 2, 0, 0));

#define BILINEAR_LINE_N(COLORINDEX) { \
      /* 積 */ \
      xmm_dst = _mm_mul_ps(xmm_colors_tmp[COLORINDEX], xmm_wu); \
      xmm_dst = _mm_mul_ps(xmm_dst, xmm_wv); \
      /* 水平 */  \
      xmm_dst = _mm_hadd_ps(xmm_dst, xmm_dummy); \
      xmm_dst = _mm_hadd_ps(xmm_dst, xmm_dummy); \
      /* 結果を非テンポラルに書き込む */ \
      xmm_all_tmp = _mm_cvtps_epi32(xmm_dst); \
      _mm_stream_si128((__m128i*)(buf), xmm_all_tmp); \
      dst4[p_d + COLORINDEX*4] = buf[0]; \
      }
      BILINEAR_LINE_N(0);
      BILINEAR_LINE_N(1);
      BILINEAR_LINE_N(2);
#undef BILINEAR_LINE_N
     }
   }
   _mm_free(buf);
  } break;
 }

 // DWORD => byte
 for (int v=0; v<dst_h; v++)
  for (int u=0; u<dst_w; u++) {
   int p_d = (v*dst_w + u)*3;
   int p_d4 = p_d*4;
   dst[p_d + 0] = dst4[p_d4 + 0];
   dst[p_d + 1] = dst4[p_d4 + 4];
   dst[p_d + 2] = dst4[p_d4 + 8];
  }
 
 free((void*)src4);
 free((void*)dst4);

 wxImage result = wxImage(dst_w, dst_h);
 result.SetData(dst);
 return result;
}

とりあえず動くことは動く。絶望的に遅いので特に評価はしていませんが…
プリフェッチ命令とか非テンポラルストアとか、意味があるのか分からないものの使ってみました。
命令とコストの対応が分かってないので、何をやるにしても方針が立ちません。やはり一度真面目に勉強するべきか。

追記: float(ps)は遅そうなので、ふつうのCでSSE使わずに、256倍して整数で書いてみました。結果は2倍以上高速に…
やはり命令の使い方の前にコストを知る必要がありそうです。

cygwinやMinGWにpdh.hが含まれていないらしいので、Windows SDKを入れてみました。
WinSDK-x86.msi
WinSDKBuild-x86.msi
WinSDKDocWin32-x86.msi
WinSDKWin32Tools-x86.msi

なぜpdh.hが必要かといえば、Windowsタスクマネージャの代わりにコンソールでCPU使用率順のプロセス一覧を表示させたいから。試しにC#で書いてみたら、それ自体が2%もCPUを使うので本末転倒。仕方なくネイティブで書くことになりました。

…と思ったら、なぜかv6.0Aがすでにインストールされていました。(v6.0が今回入れた物)

しかもcygwinでmakeしてもエラーばかりで全然通らない。仕方ないのでVisual C++ 2008 Express Editionに切り替えてみた。普通にWin32コンソールアプリケーションがある…
pdh.hもpdh.libもある…

ということでcygwinを捨ててVC++になりました。前は.NETじゃないとセットアップが面倒だった気がしますが、最近のMSは粋なことをしてくれます。

IDEが重いしviキーバインドにならないのと、非マネージドのインテリセンスがあまり便利じゃないのが玉に瑕ですが。

遊んでいる暇は無いのに…最近コーディングしてないので、雨模様ということもあって土日を使ってしまいそう。

とりあえず400プロセス以内、2コアに対応し、PIDとCPU使用率、イメージ名を1秒おきに書き続けるtopもどきが出来ました。
LLになれるとC++でソートとか面倒すぎる…

一応zip置いておきます。wintop.zip
Cygwin等の\fで画面クリアが効く端末でないと使えません。

PuTTY for Win32で背景画像表示するパッチのPuTTYjp適用済みバイナリを更新しました。

カーソル上のバックスラッシュや、ワイド文字の背景画像が描画されずデフォルト背景のままになっていた問題が修正されています。併せてPuTTY→PuTTYjp+背景画像のパッチをリリースしました。

vimで<C-W><C-W>でウィンドウ移動するのはよく使いますが、[count]<C-W><C-W>で[count]番目のウィンドウに移れることはあまり知られていないようです。というか自分が知らなかっただけか…

しかし縦だけ、横だけの分割なら左上から1,2,..とウィンドウ番号が振られますが、縦横に分割した場合は一目には番号との対応が分かりづらい。

そこでステータスラインにウィンドウ番号を表示すると少し便利になります。

set statusline=%{winnr('$')>1?':'.winnr().'/'.winnr('$'):''}

勿論これではファイル名も何も表示されなくなってしまうので、すでに書かれているものに適宜加筆します。

ついでにバッファ番号以下よくあるステータスラインとマージした例。

set statusline=[%n%{bufnr('$')>1?'/'.bufnr('$'):''}%{winnr('$')>1?':'.winnr().'/'.winnr('$'):''}] %< %f %m%r%h%w%{'['.(&fenc!=''?&fenc:&enc).']['.&ff.']'}%=%l/%L,%c%V%8P

同SS
vimステータスライン

ただ頻繁に使う1~3の数字がQWERTYではCtrlやWと近く、入力しにくいので<C-W>以外にもマップしておくといいかもしれません。

Linuxなら端末でF11すればタスクバー等も全部表示されない本物の全画面になりますが、Windows等でgVimを使っていると:FullScreenではタスクバーもタイトルバーも残って画面領域が無駄になります。

Vimのヘルプを眺めていたらguioptionsにキャプションバーを隠すCオプションを見つけたので、全画面最大化トグルスクリプトを書いてみました。

" fullscreen
"-----------------------------------------------------------
nnoremap <F11> :call ToggleFullScreen()<CR>
function! ToggleFullScreen()
  if &guioptions =~# 'C'
    set guioptions-=C
    if exists('s:go_temp')
      if s:go_temp =~# 'm'
        set guioptions+=m
      endif
      if s:go_temp =~# 'T'
        set guioptions+=T
      endif
    endif
    simalt ~r
  else
    let s:go_temp = &guioptions
    set guioptions+=C
    set guioptions-=m
    set guioptions-=T
    simalt ~x
  endif
endfunction

上のスクリプトをvimrcに加えればF11でメニュー・ツールバーは勿論、キャプションもタスクバーも隠した最大化が出来ます。
もう一度F11を押せばメニュー・ツールバーを(元々存在していれば)復元し、元に戻ります。

加えて


set guioptions-=T "ツールバーなし
set guioptions-=m "メニューバーなし
set guioptions-=r "右スクロールバーなし
set guioptions-=R
set guioptions-=l "左スクロールバーなし
set guioptions-=L
set guioptions-=b "下スクロールバーなし

も設定しておくと普段から広くて少し嬉しいかもしれません。

ちなみに自分はAutoHotKeyで普通のアプリケーションのF11最大化トグルを設定していますが、そのままでは今回のスクリプトと衝突するので回避するよう変更しました。

; F11で最大化トグル
F11::
  WinGetClass, className, A
  if(className="Vim") {
    Send,{F11}
    return
  }
  WinGet, State, MinMax, A
  if State != 0
    WinRestore, A
  else
    WinMaximize, A
  return

Synapseも完成していないうちに、(飽きた訳ではないけれど)別のアプリケーション案が沸いてきつつあります。

僕が普段使っているFirefoxのブックマークは、エクスポートすると3MB近くあり、二度と見ないようなページがかなり含まれている。それに、タグ管理できないために同じURLを別のフォルダに放り込んでいることも多いし、リンク切れのも探せばかなりあるだろう。
勿論del.icio.usとかはてブとかlivedoor clipとか、ソーシャル(オンライン)ブックマークを使えば、このような問題の多く(特に後半)は解決可能かもしれない。しかしブックマークするために「bookmark this」をクリックし、タグを決め…等の操作をわざわざ別タブ開いてやりたくない。(基本的に熟読する前にブックマークするので、タブ移動されると戻るのが面倒なのだ)
特定のトピックに絞って検索し、有用そうなページを一瞥してたとえば「Yahoo形態素解析資料」のフォルダに放り込む…といった標準ブックマーク管理で簡単にできることが、オンラインブックマークでは面倒になる点も許せない。

ということで、これらを一挙に解決するかもしれないブックマーク管理アプリケーションを作ろうと考えています。ブラウジングとブックマーキングは別の作業だから、UIはウェブベースである必要はない。プラットフォームは迷わずC# .NET。
標準ブックマーク管理の問題点を解決するため、

  • タグ管理
  • 時系列管理
  • リンク切れチェック

は外せない。オンラインブックマークの恩恵にも与れるなら与り、場合によっては模倣。

  • 各種オンラインブックマークにデータ保存(あるいは同期)
  • サムネイル保存

これは幸い.NETの外部アセンブリで様々なものに対応できる。

ついでに若干独自性(多分)を出す以下の機能。

  • 内容の形態素解析でタグ・キーワード提案
  • タグを関連性で結んだグラフ(2次元)で少ない操作でタグ選択
  • タグの組み合わせをロックし、同一ジャンルのURLを次々に登録
  • マウスでもキーボードでも極力操作を減らす
  • 絞込条件(タグ・時系列・手動選択)に対して一括処理(aタグ生成等) これも外部の拡張機能で
  • ブックマークの「内容」に対して全文検索

ここまではまあブックマークとして妥当なところでしょう。次の段階まで進むと、Synapseと統合してしまった方がいいかもしれない。

  • 登録時に内容取得、巡回してdiff管理

どのみち今は書く時間がぜんぜん取れないので、7月第4週くらいから本格的に考えるつもりです。といっても夏は夏で夏らしくもない雑多な用事がいろいろ有りますが…

Python Workshop the Edge 2007が6/30にあったようです。東京大学駒場キャンパス5号館で。

行けばよかった!近所なのに、すっかり忘れていました。情報収集用のフィードリーダを書いていてこんな大きな情報を逃していては間抜けもいいところです。
来年も開催されるなら参加したいなあ。

ちなみにdlnicoの人がDjango陣に居たことになっている?ようですが、僕ではありません…

WILLCOMの新端末、x-w.jpが6/7に発表されるようです。数日前からティーザーサイトでカウントダウンが始まっていますが、7日にはどうなるのか…当然気になります。

ということで、ちょっと覗いてみました。

  1. http://x-w.jp/swf/top.swfをダウンロード
  2. Wiresharkで覗くと/cgi-bin/svrtime/svrtime.cgiにアクセスし、’st=06′という文字列を受取っているので、これを07にすればよさそう。
  3. /windows/system32/drivers/etc/hostsに
    127.0.0.1       x-w.jp

    を追加。

  4. Pythonで適当にスクリプトを書いて
    import sys, thread, BaseHTTPServer
     
    class XWRequestHandler(BaseHTTPServer.BaseHTTPRequestHandler):
        def do_GET(self):
            if self.path == '/cgi-bin/svrtime/svrtime.cgi':
                self.wfile.write('st=07')
     
    httpd = BaseHTTPServer.HTTPServer(('', 80), XWRequestHandler)
    thread.start_new_thread(lambda x: x.serve_forever(), (httpd,))
    sys.stdin.readline()

    コマンドプロンプトで実行。

  5. あとはローカルに保存したtop.swfを再生。

x-w
Today coming soonということは、7日になった瞬間に発表というわけでもなさそうです。

まあこんなちょろい方法で日付変えてだますなんて当然想定済みで、これはフェイクだったり…するかもしれませんが。

2007 05 18

C#.NET + IronPython

C#.NET + IronPythonの連携が便利すぎます。
記憶は削除の方向で - IronPython を中継して、C#からPythonライブラリを使ってみる
に書いてある通り、

  1. C#でインターフェイス宣言(PythonのListはC#のIList)
  2. IronPython.Hostingをusingし、PythonEngineのインスタンス作成
  3. 自分のアセンブリ情報を渡し、必要ならimportパスを指定
  4. Python側でC#のnamespace.interfaceをimport
  5. 普通に継承してクラス作成
  6. EvaluateAs<>("NewClass()")でインターフェイスのサブクラスが返ってくる

以降は普通のC#のオブジェクトのように扱えます。なんて便利なんだろう…
折角.NETが使えるので、Pythonでもimport Systemしてもいいのですが、標準ライブラリを使う場合は、IronPython と Python 標準ライブラリについての注意が参考になります。(まだ試していませんが)
PythonからC#や.NETのライブラリを扱う際、プロパティはInstance.propertyでアクセス出来るようです。StringやInt??周りで若干エラーが出やすい感じですが、PythonEngineで呼んだスクリプトからConsole.WriteLineすればコンソール出力が表示されるのでなんとかなります。

こんなに連日(引きこもって)何のためにC#をやっているかというと、かねがね作ろうと思っていた高機能省力フィードリーダに着手しています。
1日でC#に触れ2日でIronPython連携し、と順調です。Geckoを.NETで扱う巧い方法があれば6日とたたず世界は構築されるんですが、現時点では妥協してプレーンテキストとIEコンポーネント、Flash埋め込みの切り替えになりそうです。
当初は文字情報Pull/Push・加工・保管・検索・表示・投稿に関する全てを取り込もうとしていましたが、現在表示面ではやや遅れています。
更に取得・加工部分をPlaggerに委譲しようかと思っていますが、PerlはPythonほどC#と親和性が高くない気がするので、取り敢えずはIronPythonスクリプトによるSmartFeed(RSSやAtomと同列の、最新エントリ一覧を単位とするクラス)を実装しています。

以下単に愚痴ですが、何故今更フィードリーダなんぞを書いているかと言う話。
自分はFirefoxを使っているのもあって、Sageに100フィード程度登録して使っているのですが、

  • 既読扱いにするのが面倒
  • フィードリストのスクロールが面倒
  • 全文を取得して表示出来ない
  • 3ペインビューがない
  • 過去エントリの保管・検索ができない

また、既存のリーダ全般に言えると思いますが、巡回という情報の内容以外は全く同じ操作をするために異常な労力が必要となるのが最大の問題。もちろん製作中のものはオートスクロールその他省力機能満載です。
加えて、投げやりにRSSのDescriptionやらLink先をブラウザコンポーネントに突っ込むその態度。あんな狭い画面で個別サイトのヘッダやらサイドバーなんぞ見たくありません。
とまあ、語り始めると止まらない感じで、放っておけば勝手に膨張していく夢を抑えるのが大変です。
完成しても「ただの気持ち悪いインターフェイスのフィードリーダ」になってしまわないよう、珍しくユーザビリティに気を遣って作ろうと思っています。
(早くも設定画面のコーディングには嫌気がさしていますが)

今までの対数関数的製作曲線からいって、実際にリリースされるのがいつになるかは分かりませんが、βくらいで仕様固めのために公開する予定なので、使ってみてください。

次のページ »