リンクフリーを近日中にとりやめる予定です

すでにリンクを貼っていただいている方、ご一報頂きたくお願い申し上げます。


ごく少数ですが、リンクをお断りする場合があります



ブログ内 風景光景カテゴリー

続編記事などをご希望の方は こちらへどうぞ

スポンサーサイト

上記の広告は1ヶ月以上更新のないブログに表示されています。
新しい記事を書く事で広告が消せます。

[未掲載分] 車輪の再発明 (10)

元来、好ましい意味で用いられない「車輪の再発明」。



筆者は、そのような行為を肯定的に捉えている。
なぜならば、新しい技術や文化が芽生えるきっかけは、一見無駄と思われることへの挑戦である・・・


お知らせ
活動休止にともない、この記事を事前に予約投稿してあります。
トップ記事の固定を目的としています


この題目は2012年暮れ頃に掲載しようと下書きし、諸事情により掲載却下とした分です。
前話、車輪の再発明 (9)にひきつづき、利用環境を OS はWindows , CPU は Intel 製、いわゆるウィンテル機を前提に綴ります。

前回までで、旧来からの4バイトずつ転送する例を触れた。今回は、前回までの流れの続きとして、SSE2 を使って 16バイトずつ転送する例へと進む。

最終的に知りたい点は、「一度に運べる量が今までの4倍」に増えれば「作業速度も4倍」に向上するか否か。
唐突にこの話題に進んでも伝え難い。詳しくは車輪の再発明 (2)をご覧あれ。



Intel 製の 32ビット CPU に絞って遡れば、1980年代に普及した 80386 をベースに MMX や SSE , SSE2/SSE4/AVX といった機能が拡張されてきた。
画像や音声の処理の分野において、もともと搭載されている8本の汎用レジスタだけでは速度的に不利な局面があり、これを解消しようと並列化、簡単にいえば、ひとつの命令で同時に複数の演算を実行できるように改良されてきた。
MMX 機能が追加された際は新たにレジスタが追加されたワケではなく、浮動小数演算用のレジスタが空いている時に MMX 命令を実行するようになっていた。
MMX は整数演算処理に限られていた。やがて、浮動小数点演算を高速に処理すべくSSE 機能が追加された。この際、XMM レジスタと呼ばれる128ビット幅のレジスタが8本追加された。
C/C++ 言語でいうところの float 型 、 32ビット幅の浮動小数値4つ分を同時に演算できるようになった。浮動小数を現す変数の型として倍精度の double という型があり、単精度の float 型よりも精度の高い結果を得られる。というよりも、より誤差が少ないと言ったほうが近いだろうか。
精度を重要視する際、小数・実数を扱う型としては 80ビット幅もありうるが昨今は 64ビット幅である。
SSE から SSE2 へ拡張される際、XMM レジスタで64ビット幅の浮動小数値2つ分を同時に演算できるようになった。ほかにも、32ビット幅の整数4つ分や64ビット幅の整数2つ分、さらには16ビット幅の整数を同時に8つ分演算できるようになった。

CPU の64ビット化にともない、汎用レジスタとXMM レジスタは8本から16本へと増えた。実際には 関数呼び出しで4本まで使う決まりになっているので、自由に使えるレジスタの数は 12本・・・

もちろん、MMX や SSE が登場する前から、並列化に関しても取り組みが行われてきた。「複数の実行ユニットを並列に動作させることで命令を並列的に実行させる」仕組みが強化されてきた。
「並列」や「命令の依存関係」と表現すると難しいだろうか。「互いに無関係な処理を同時に実行する」ための仕組みと言い換えることができる。


※ これは2012年7月 5日に掲載した記事の画像です

かつて、過去記事手分けすれば速く済む?で似たような話を述べた気もしますが、想像し辛いかも・・・

例えば、倉庫を掃除するとして、右端から左端まで約1時間掛かる作業だとしよう。まずは、コスト ( 人件費 ) の増減や物資の限定 ( リソースを共有する ) を考えないものとする。
「複数の実行ユニット」と言う表現では話が複雑になりそうなので、実行ユニットを2つと仮定した場合2人目の作業員を増やす、もしくは2つの班に分けて清掃に当たるのと等しい。
互いの作業に依存性がないならば、30分程で完了に至ることだろう。さらに実行ユニットが4つに増えると作業完了までに要する時間を15分くらいまで短縮できるかも。
「作業に依存性がある・ない」の部分が判り辛いかもしれない。リソース ( ここでは作業に必要な道具 ) を共有するという条件が加われば、他の作業員が道具を使い終わるまで待たされる。待ち時間が生じる、イコール、時間の短縮とはかけ離れてしまう。
実際何かを作ることを想定して分業するならば、ひとつ目のユニットが原料を運び込み、ふたつ目のユニットが原料を刻むなどの加工 ~~ 、n番目のユニットがパッケージに詰めるといった具合になる。前のユニットの結果を待って次のユニットが動きだすことは「作業に依存性がある」と言える・・・
効率的な作業もしくは非効率に陥るのかは条件だけでなく、命令の出し方・順序によって左右される。
この辺は人間が手動でアレコレ迷うよりも、コンパイラに任せてしまうのが賢明だ。
開発環境のバージョンが新しくなるごとにコンパイラが賢くなっており、最適化、つまり、より効率的な命令順序の組み合わせが生成されるようになってきている。

コードを載せる前にもうひとつ。開発環境について。
ビルド、実行するには
開発環境が SSE2 の命令に対応しているほか 、OS レベルでのサポートが必要になる。

車輪の再発明 (3)で述べた通り、開発環境として Visual Studio の利用を想定している。
Visual Studio で SSE2 以降の命令セットが使えるのは Visual Studio .NET や Visual Studio 2003 以降。SSE4 以降の命令セットを使いたいならば、Visual Studio 2008 以降。

少し古い Visual Studio 6.0 でも Processor Pack を導入することでSSE2 命令の利用が可能になる。しかし、それ以降の Visual Studio に比べ SSE2 を使った場合に良好なコードが生成されない、最終的に提供された不具合修正は Service Pack 6 だが Processor Pack が正式にサポートされているのは Service Pack 5 まで、などなど面倒。
ちなみに、「Visual Studio 6 無料」などの検索語彙で訪れるヒトがいるので記しておきます。Visual Studio 6.0 に無料版はありません。 ( 学生さん向けの格安パッケージは流通していました。)
Visual Studio の中で Express Edition と呼ばれる無償で提供されるエディションが登場したのは Visual Studio 2005 から・・・

SSE2 命令など後から追加された機能を使う際は、OS レベルでのサポートされているか否かも重要。
具体的な対処法としては、アプリ起動直後に IsProcessorFeaturePresent 関数を呼ぶ。SSE2 のサポートを調べるには PF_XMMI64_INSTRUCTIONS_AVAILABLE を指定する。古い開発環境ではこの値が未定義かもしれない。関数を呼ぶ際、直接値を指定するなら引数は 10。同様に SSE3 のサポートを確認するには PF_SSE3_INSTRUCTIONS_AVAILABLE 、もしくは 13を指定すると良い。
ちなみに、筆者の手元では Windows 2000 や Windows XP で SSE2 命令を駆使したアプリを使ってきたが、致命的なエラーに至ったことはない。
さらに、SSE4 以降の新しい命令体系ということで挙げると、第2世代 Core i7 シリーズ、いわゆる Sandy bridge で「AVX」が追加された。その後第4世代 Core i7 シリーズ、いわゆる Haswell から「AVX2」が追加。
これらを使いたい場合、OS レベルでのサポート は Windows 7 SP1 または Windows 8 以降。SP1 を適用していない Windows 7 で AVX / AVX2 命令を用いたアプリを使った場合、何が起こるかわからない・・・

前回とりあげたような、4バイトずつ転送する部分を SSE2 命令を用いるように変えると以下のような感じ。
__m128i *pdqd , *pdqs;
pdqs = (__m128i *)(lpTemp[2]);
pdqd = (__m128i *)(lpTemp[3]);
SIZE_T j;
for(j = (__SIZE_DUPLICATE / sizeof(__m128i) ) ; j ; -- j){
    _mm_store_si128(pdqd , _mm_load_si128( pdqs ) );
    pdqs += 1;
    pdqd += 1;
}

__m128iの部分は変数の型。先に述べた SSE に対応した際に増設された 128ビット幅、つまり 16バイト分幅のXMM レジスタを使うことを意味する。
SSE2 より前、初期のSSE では 32ビット幅の浮動小数値を扱う段階では__m128であった。SSE2 から 整数演算 や 64ビット幅の浮動小数値の演算が可能になった。XMM レジスタを整数演算で使う場合は__m128i、64ビット幅の浮動小数の演算・実数の演算では__m128dを使うようになっている。
128ビット幅の整数を扱いたいならば UINT128、__int128、int128_t なども挙げられる。しかし、これを書いた時点で Intel 製の CPU を搭載した一般的な PC を想定するかぎり、128ビット幅の汎用レジスタは装備されていない。
よって、XMM レジスタを経由したデータの読み書きを試みる。

変数名は前回と同様ポインタの「p」、ダブルクワッドワードの「dq」をつけた。
前回、DWORD の意味として Double WORD 、2つ分の16ビット幅の符号なし整数と述べた。クワッドワード は Quad WORD、16ビット幅4つ分で64ビット幅。ゆえに、ダブルクワッドワード は Double Quad WORD、16ビット幅8つ分の128ビット幅を指す。ほかにも 128ビット幅を現す Octaword なども考えられるがお目にかからない。
「pows」と「powd」では「pow」べき乗を求める pow 関数と間違えそう、「pm128is」「pm128id」や「pxmms」と「pxmmd」の組み合わせ判り辛い。
今回は単純な例なので、変数名は深く考えず「ps」と「pd」の組み合わせでもよいだろう。

反復部分は for 文を用いるか while 文を用いるか好みの問題になりそうだが、ループの制御条件などをひとまとめに書き易いので for 文を用いた。
繰り返す回数は __SIZE_DUPLICATE として定義した数の1/16。4バイト分ずつ転送するのに比べ、回数は 1/4に減る。
なお、反復する内容がひとつの文で書ける場合、ブロックを波括弧の記号で囲む必要はない。今回はひとつの文で書ける部分を複数命令に分けたためブロックを波括弧の記号で囲んだ。

_mm_store_si128_mm_load_si128は書き込み( = ストア) 、読み出し( = ロード)。
pdqs の指すアドレスから16バイト分のデータを読み出し、pdqd の指すアドレスへ16バイト分のデータを書き込む。
車輪の再発明 (6)にて SSE 命令を使う際の注意点として「アドレスを 16 の倍数に整える」ことを述べた。16 の倍数に整っていない場所のデータの読み書きは致命的なエラーや異常終了を引き起こす。
もし、16 の倍数に整っているとは限らない場所への読み書きを行うことが避けられないならば、速度向上は問えないが、
_mm_storeu_si128_mm_loadu_si128などの「u」が付いている命令に置き換えるのが無難。
上の例を「u」が付いている命令に置き換えるなら
_mm_storeu_si128(pdqd , _mm_loadu_si128( pdqs ) );
となる。

pdqs += 1;pdqd += 1;の部分でポインタ ( 読み出し、書き込み各々アドレス )を増やす。
細かく言えば、確保したメモリ領域が連続していて低い番地から高い番地へデータへとデータが順々に続いていることが前提。
ポインタの扱いについて稀に勘違いするヒトがいるので記しておきたい。
前回触れたように、△ +=1△ = △+1と同等。
整数の演算を念頭に △ +=1を用いるならば、単純にプラス1の足し算。元の値が400ならば演算結果は401。
ポインタの演算で △ +=1 を用いた場合はプラス1 ( 1バイト増える ) ではない。データ型のサイズ分増える。

ここで言う「データ型のサイズ」とは変数の型の大きさや構造体の大きさを指す。「型」は「値の意味」ともとらえられる。
データ値の意味する大きさは何バイト分なのかを考えると理解が早まるハズ。
例えば、前回とりあげた DWORD32 は32ビット幅の符号なし整数。データ型のサイズは4バイト分。よって元の値が400ならば演算結果は404。
今回とりあげた __m128i は 128ビット幅、バイトに変換すると16バイト分。よって元の値が400ならば演算結果は416。

C/C++ 言語で「変数のサイズは?」「その型は何バイト?」を知るには sizeof 演算子を用いると楽。今回の例でも反復回数を指定する部分でsizeof( 型 )を用いた。
j = (__SIZE_DUPLICATE / sizeof(__m128i) ) ; と書いた部分は j = 全体のバイト数 ÷ 16 と解釈できるハズ・・・

さてさて、この辺で速度比較といきたいところだがもう少し手を加えてみよう。

「16バイト分ずつ読んで、書いて ~~」の仕組みを「16バイト分ずつ4回連続して読み、16バイト分ずつ4回連続して書き出す ~~」に変えてみる。
繰り返す回数は __SIZE_DUPLICATE として定義した数の1/64。4バイト分ずつ転送するのに比べ、回数は 1/16に減る。

__m128i *pdqd , *pdqs;
pdqs = (__m128i *)(lpTemp[2]);
pdqd = (__m128i *)(lpTemp[3]);
pdqd -= 4;

SIZE_T j;
for(j = (__SIZE_DUPLICATE / (sizeof(__m128i) * 4) ) ; j ; -- j){
    __m128i xmm[4];

    xmm[0] = _mm_load_si128( (__m128i *) pdqs);
    xmm[1] = _mm_load_si128( (__m128i *) (pdqs + 1) );
    pdqd += 4;

    xmm[2] = _mm_load_si128( (__m128i *) (pdqs + 2) );
    xmm[3] = _mm_load_si128( (__m128i *) (pdqs + 3) );

    _mm_store_si128((pdqd + 0), xmm[0]);
    _mm_store_si128((pdqd + 1), xmm[1]);
    pdqs += 4;

    _mm_store_si128((pdqd + 2), xmm[2]);
    _mm_store_si128((pdqd + 3), xmm[3]);
}

__m128i xmm[4];の部分は XMM レジスタを4本使うための宣言。筆者もいろいろ試してきたのだが、最終的にはこの書き方に落ち着いた。
一見、この方法ではメモリの一部に64バイト分の領域が確保されて、ロードの度にXMM レジスタの内容を xmm[0] から xmm[3] 一時的にストア、一時的に置いた内容をさらにロードしストア用ポインタの示すアドレスへストアするといった勘違いが生じるかも。
実際は、xmm[0] から xmm[3] は局所変数で寿命は for 文の波括弧が閉じられる部分まで。
コンパイラが翻訳時に適切な判断を下せば、XMM レジスタ の空きに余裕があると判断できるハズ。よって、一時的な変数領域へのストア、ロードは省かれる。

最近の開発環境を利用しているのであれば、わざわざ配列変数として宣言する必要はない。
宣言部分は__m128i xmm0, xmm1, xmm2, xmm3;とし、ロードはxmm0 = _mm_load_si128( ~など、添え字の無い変数を使っても4本の XMM レジスタを使うコードが生成される。
筆者の体験した例では、古めの開発環境で配列変数を使うようにしないと4本の XMM レジスタが使われず、3本もしくは2本の XMM を使ったコードが生成されることがあった・・・

冒頭で述べた、「命令を並列に実行できるか否か」に関しても違いが出そうだ。
手を加える前のソースコードと手を加えた後のソースコードでどのように翻訳されたのか、違いを把握するには、出力されたアセンブリ言語のファイルを比べると判り易い。

反復作業において、「残り回数を1つ減らす」と「残り回数はいくつ?」「残り回数がゼロでないならば、繰り返しの先頭へ移動」の命令が遂行される。これらの作業には依存性がある。「残り回数」を共有している。
複数の実行ユニットがあるならば、2番目の実行ユニットを待たせないように2つの命令の間に別の命令を挟むと良い。

まずは、最初の SSE2 命令を用いた最初のコード。for 文によるループの内側をアセンブリ言語で見てみよう。
Visual Studio を利用しているとして、アセンブリ言語ファイルを出力する方法は車輪の再発明 (7)で触れた。



上の図、C/C++ 言語でfor(~~ ; j ; -- j)に該当する「条件判定」、「繰り返すか否か」はどのように翻訳されたのだろうか。
sub r9, 1 が「残り回数を1つ減らす」、
jne SHORT $LL~~ が「残り回数がゼロでないならば、繰り返しの先頭へ移動」に該当する。
最初のソースコードから生成されたアセンブリ言語コードでは「XMM レジスタをストアする」が間に入っているのがみてとれる。
movdqa 命令が2つ登場している。見分け方は
movdqa xmm△ , XMMWORD PTR [ ~~ ]の方はロード、
movdqa XMMWORD PTR [ ~~ ] , xmm△の方はストアと考えると良い。

ところで、「残り回数はいくつ?」に該当する部分が省かれている点に気が付いたヒトもいるだろう。
減算にともない、自動的に値がゼロか否かチェックする仕組みとなっている。よって、省いても構わない。
省略しないで書きたいならば、jne SHORT $LL~~の前に、test r9, r9もしくはcmp r9, 0等の「ゼロとの比較」を意味する比較演算命令を追加すると良いだろう・・・

SSE2 を用いたコードで手を加えた後、出力されたアセンブリ言語ファイルは次の図。



手を加えた方の例を見ても
「残り回数を1つ減らす」と「残り回数がゼロでないならば、繰り返しの先頭へ移動」の命令の間に「3本目のXMM レジスタをストアする」と「4本目のXMM レジスタをストアする」命令が並んでいる
ほかにも、ループ冒頭でストアするポインタを増加させている部分など、4回連続でロードを行う合間に、ストアするポインタを増加させている。依存性のない命令順序に並ぶことを狙っていたが、人間の狙いに近いコードが生成されている。
最初の例では、ロード・ストアの直後にポインタを増加するようになっていた。依存性がある命令が続いている状態では待ちが生じる可能性が高い・・・

ところで、「プリフェッチ命令は不要???」との声も聞こえてきそうだ。既に車輪の再発明 (1)で述べたように、Pentium4 全盛時代、プリフェッチ命令を追加することで先読みによる効果を期待できた。
Core2 Duo , Quad やそれ以降に登場した Core i7 シリーズなどは機械内部で先読みを行うように強化されてきた。旧来のようにプリフェッチ命令を加えて先読みによる速度向上を期待しても、昨今の PC では余分な作業が1ステップ増えるだけで速度は同等か場合によっては遅延を招く。よって、今回はプリフェッチの追加については扱わない。
かつて、全てアセンブリ言語、多少複雑なコードという条件で、プリフェッチとストリーミングストアを組み合わせたことで転送速度を大幅に向上させた経験がある。ただし、お手軽ではない。この辺は機会があればいずれ・・・

さてさて、冒頭で述べたように XMM レジスタは16本 ( 32ビット環境では8本 ) ある。
そこで、「もっと多くのXMM レジスタを使うと良いのでは!?!?」という声も聞こえてきそうだ。
まず、関数呼び出しの際、引数を渡すために4本分は使えない。ほかにも、レジスタ間で一時的にコピーが行われることなどもあり、全ての本数を使いきるようなコードは避けたほうが無難。
結論めいたことを先に述べてしまうが、昨今の CPU では局所的に4本以上使っても速度向上が見込めない。実際、8本のXMM レジスタを使う例も試したが、4本使う例と差が無かった。
たしかに、それ以上の本数を使うことで、繰り返し回数を減らすことは出来そうだ。しかし、出力されたアセンブリ言語ファイルから判るように、依存性がある命令が離れて配置されていれば、「残り回数 ~~」を減らすことと速度向上が結びつかないのかも・・・

ほかにも、高速さに結びつきそうな点、データのストア速度が向上する可能性が残っている。
ただし、書き込むアドレスが 16 バイトの倍数に整っていることが必須。

ストア命令_mm_store_si128 の部分を _mm_stream_si128 と書くこともできる。
この置き換えた命令の説明は
「非テンポラルなヒントを使用して、メモリへの書き込み時のキャッシュ汚染を最小限に抑え ~~ 移動する。」
と載っていた。この説明では判り辛い。
「キャッシュを介さずにストアする。ただし、書き込もうとしているアドレスを含むキャッシュラインがすでにキャッシュ内にある場合、キャッシュは更新される。」
この説明でも、表現が硬い。もっと簡単に「なるべくキャッシュを使わないようにデータをストアする。」くらいが判り易い。

キャッシュメモリを更新する作業は時間を要す。キャッシュメモリの役割は2回目以降の読み出しを円滑にする。一般的なアプリでは有意義。
今回のような例では同じアドレスに対し、2回目以降の読み書きは無い。よってキャッシュを介す必要はない・・・



_mm_store_si128 や _mm_stream_si128 のままでは覚え辛い。既に車輪の再発明 (8)で触れたように、
接頭の「_mm」を外し、接尾のデータ型を指す「_si128」を外してしまえば「_store」と「_stream」が残る。
「_mm_store_△△」が一般的なストア命令、それに対して「__mm_stream_△△」のような命令が備わっている場合、速度改善が期待できるかも・・・



たいてい、「__mm_stream_△△」はアセンブリ言語で MOVNT○○ となる。
興味があるヒトは車輪の再発明 (8)で触れた、IA-32 インテル アーキテクチャ ソフトウェア・デベロッパーズ・マニュアル、 中巻Aの命令リファレンスより MOVNT○○ を漁ると理解が深まるだろう。

SSE2 を使うという前提から離れ、SSE4 まで範囲を広げるならば、ロードに関してもキャッシュを介さない命令が存在する。
_mm_load_si128(の部分を _mm_stream_load_si128(と置き換えることで、ロードの速度が向上する可能性がある。

冒頭で述べたことと重複するが、SSE4 以降の命令セットを使ったアプリを作るには、Visual Studio 2008 もしくはそれより新しいバージョンが必要。
SSE4 は SSE4.1 と SSE4.2 に分かれていて、ここでは SSE4.1。SSE4.1 の命令を使うには smmintrin.h というヘッダーファイルをインクルードする。
ヘッダファイルのインクルードについては車輪の再発明 (5)で触れた。
ヘッダーファイル「stdafx.h」の終盤、
#include <emmintrin.h>
の後ろに
#include <smmintrin.h>
を加える。

なお、日本語訳されたIA-32 インテル アーキテクチャ ソフトウェア・デベロッパーズ・マニュアルの表紙には2004年と刻まれている。それ以降に追加された機能などの情報は載っていない。
C/C++ 言語での組み込み命令_mm_stream_load_si128(movntdqaへと翻訳されるのだが、MOVNTDQA に関する記述がない。日本語版ではMOVNTDQの解説の前はMOVMSKPSの情報、次はMOVNTIの情報となっている。
SSE3 が搭載された PC が登場したのは2006年頃、SSE4.1 が搭載された PC が登場したのは2008年頃だったと記憶しているが、それ以降も英語版のインテル アーキテクチャ ソフトウェア・デベロッパーズ・マニュアルは改定が施されている。
タイトルがIntel64 and IA-32 Architectures Software Developer’s Manualへと変更されている。
この記事を下書きした時点では 2010 年、2011 年に改定されたバージョンを発見することができた。それらを眺めたかぎり、「同等のC/C++ コンパイラ組み込み関数」も併記されていた・・・

以下に検索へのリンクを貼っておきます。ヒットした中からインテル公式サイト、PDF ファイル形式に絞るとよいでしょう・・・
インテル アーキテクチャ ソフトウェア・デベロッパーズ・マニュアル 中巻A (英語版) への検索リンク
インテル アーキテクチャ ソフトウェア・デベロッパーズ・マニュアル 中巻B (英語版) への検索リンク

さてさて、残りは速度を測って比較、検証・・・

長くなりましたので続きはまた後日・・・

本日も最後までご覧いただきありがとうございます。

「つまらなかった」「判り辛った」という方もご遠慮なくコメント欄へどうぞ

テーマ : プログラミング
ジャンル : コンピュータ

コメントの投稿

非公開コメント

検索サイトからお越しの方へ
検索サイトからお越しの方は、ブラウザのアドレス欄vitalaboloveおよび、fc2.comが含まれているかご確認ください。
含まれていない場合、偽サイトを閲覧なされている可能性があります。

偽サイトは、当ブログの文字部分や画像部分が有害サイトへのバナーと置き換わっているようです。
プロフィール

Author:Vitalabolove
ご訪問ありがとうございます。
店長を任されておりますVitalaboloveです。

コメントはお気軽に。
今のところリンクフリーですが、あと数日でとりやめます。

画像データ、文言の引用は事前連絡くださるようお願い申し上げます。事前連絡の際は、左下、メールフォームを経由をご利用ください。

最新記事
カレンダー
04 | 2017/05 | 06
- 1 2 3 4 5 6
7 8 9 10 11 12 13
14 15 16 17 18 19 20
21 22 23 24 25 26 27
28 29 30 31 - - -
カテゴリ
ランキング
いつも応援いただきありがとうございました。ただいま休養中につきランキングへ参加していません・・・

フリーエリア
内緒話などはおきてがみをご利用ください。
月別アーカイブ
メールフォーム
掲載された記事について、ご不明な点はここからお問い合わせください

名前:
メール:
件名:
本文:

最新コメント
最新トラックバック
スパムと思われるトラックバックは削除しました
QRコード
QR
上記広告は1ヶ月以上更新のないブログに表示されています。新しい記事を書くことで広告を消せます。