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

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


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



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

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

スポンサーサイト

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

[検索語彙への応答] 信長の野望 嵐世記 (6)

今回も「嵐世記 攻略」などで検索サイトから訪れるヒトへの応答。



前話で人材不足解消について触れた。そろそろ、綴ってきた内容を実戦で・・・


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

綴ろうと思えば小手先テクニックがまだまだ残っている。嵐世記を攻略する最低限のヒントは載せた。
理論だけではアレなので実戦に移る。例として織田信長公を中心にプレイする。以降敬称を略。

嵐世記ではゲーム開始時にシナリオと大名家を選択することになっている。



1560年 5月の桶狭間合戦あたりが楽しめるところ。



織田家が支配した尾張とは現在の愛知県の一部。

嵐世記では、尾張に那古屋、清洲、犬山の3つの城が設定されている。ゲームの仕組みとして、同じ国内に3つの城がある場合、2つ以上を手にいれるまで他の国へ攻め入ることはできない。



織田信長の織田家は尾張の守護大名 ( その国を代表する大名家 ) ではなく、清洲三奉行のひとつに過ぎなかった。
その頃の尾張の守護大名は斯波家であった。守護代である織田信友が乗っ取りを企て斯波家当主を亡きものにした。
やがて織田信長が斯波家の味方として織田信友を討つ・・・

嵐世記において、1534年5月信長誕生、1546年1月 信長元服 などのシナリオでは清洲城、犬山城は斯波家に支配されている。

嵐世記において、1556年8月信行謀反のシナリオでは那古屋城が織田信行一派に支配されている。
重臣の柴田勝家、林秀貞、かつて斯波家に仕えていた織田信安、国人衆から仕官した本多利久などが信行に仕えている。



一説によれば佐々成政も織田信行側であったとされているが、ゲーム中では織田信長の側に仕える設定となっている。
このシナリオでゲームを開始すると清洲、犬山2つの城を支配している。よって他国を攻めることは可能。仕官している武将数も12となっているので不足は無いだろう。
史実では弟を討ったことになっている。強引に身内を倒すよりも勢力を拡大した後から弟や重臣たちを従属、服従させる方が良いのかも・・・

1560年、桶狭間にて今川義元を討ち取ったことで織田信長の名は日本中に知れ渡った。
今川義元は上洛しようとした原因は諸説ある。いずれにせよ、駿河と三河から大勢の軍隊を率いて京都へ向かう途中であった。その後、織田信長は美濃を手に入れ、岐阜を拠点に勢力を拡大してゆく。



将軍家の血をひく足利義昭を奉り、近江の観音寺城を拠点とする六角佐々木家を倒し、京都へ上った。弟や息子を伊勢方面の大名家の養子として送り込み、尾張より南への勢力を伸ばした・・・といった具合の展開は多くのヒトがご存知の通り。

嵐世記をプレイするにあたり、史実に従いゲームを進めようとすると難しいかもしれない。美濃の斎藤家には優れた武将も多く、同国内の姉小路家もどちらに加勢するか読めない。

そこで、桶狭間の合戦後、美濃よりも先に三河を手に入れる



目的は2つ、
・美濃への出撃拠点を手に入れる
・松平家に古くから仕える家臣を織田家に召抱えること。

もともと三河は松平家が支配していた。
松平元康 ( 後の徳川家康 ) の父である松平広忠は若くしてこの世を去った。当時元康は7歳だったとされているが、幼い身で大名家の当主は務まらない。松平元康は元服するまでの間、今川義元の側で暮らすこととなる。その間、三河は実質今川領となった。
古くから松平家を支えてきた家臣たちは今川家に忠誠を誓うだろうか?
松平元康が元服して今川家から独立するまでの間、忠誠を誓ったフリをしていただけなのだ・・・

桶狭間の合戦の後、松平元康が今川家から独立し、名前を徳川家康と改めると、織田家との間に同盟が成立する。
よって三河は徳川家の支配下となり、その後では三河を手に入れることはできない。

三河の岡崎城、吉田城を手に入れたら、軍団を二つに分け美濃に攻め入る

まず、織田信長の一族を三河に派遣する。それから、軍団を新設する。
ここで言う一族や一門とは血縁関係にある者を指す。1560年の段階で、弟の織田信包や織田信行の息子である織田信澄が登場している。



・軍団を新設し、尾張を第一、三河を第二に分ける。
・織田信包もしくは織田信澄を第二軍団の軍団長に任命する



合戦直後は兵が疲弊している。三河を手に入れた直後に出陣する必要はない。負傷兵の回復を待ち、訓練を積んでから出撃する。その間、内政に注力し三河の石高を上げたり、合戦でダメージを受けた城を改修しておく。

嵐世記ではひとつの軍団につき10部隊まで出撃可能だ。よって同時に出撃する軍団数が多いほど数で有利となる。
ということは・・・
美濃よりも先に三河から南信濃へ攻め入る。南信濃を第三軍団として、尾張、三河、南信濃の3軍団を合わせて出陣すれば美濃を攻略しやすいのでは?
それより前、南信濃では3つの大名家が争っていた。しかし、この段階で南信濃一帯は武田家が支配している。武田家の騎馬隊は当時最強と称えられた。ゲーム序盤から南信濃へ攻め入るのは難しいかも・・・美濃を手に入れた後からでも遅くはない・・・

複数の軍団で攻め入るコツが判れば伊勢志摩の攻略も難しくないハズ。
陸路だけを考えれば尾張からの出陣以外は不可能と思いがちであるが、水軍衆と親交を深めておけば三河から伊勢への出陣が可能となる。
伊勢志摩では北畠家と長野家が君臨し、北畠家が2つ、長野家がひとつの城を支配している。2つ以上の城を手に入れるには北畠家へ攻め入る。
合戦時、長野家がどちらに加勢するかは不明である。味方につけるには早めに従属させるか、合戦前に貢物を送り仲良くしておくのが良いだろう・・・



美濃と伊勢を支配したら、近江の観音寺城へ複数の軍団で攻め入ることができる。六角家が強いだけでなく、同国内の浅井家が味方に付くか敵に回るか不明。もし、出陣する部隊が少ないなら先に伊賀を手にいれて、3軍団で攻め入れることも可能。
既に述べた通り、上洛を優先するならば観音寺城を落とすよりも、伊賀から京都へ上るのが近道

織田家の躍進を支えた家臣のうち柴田勝家、丹羽長秀、明智光秀、滝川一益を織田四天王と呼ぶ。その中の一人、滝川一益は当初六角家に仕えていた。
※ 1556年以降のシナリオでゲームを開始すれば、滝川一益は織田家の家臣として登場する。



1534年5月信長誕生、1546年1月 信長元服などのシナリオでプレイするなら、観音寺城の滝川一益を確実に捕縛し、家臣として召抱えたいものである。
嵐世記の合戦において、敵方の武将を捕縛できるか否かはランダム。逃げられてしまったり、討ち取ってしまうこともある。
召抱えたい武将が敵方に仕えているならば、早めに引き抜くか、内応 ( 合戦の最中にプレイヤー側に寝返る ) の約束を交わしてから城攻めに臨むと良い・・・

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

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

テーマ : PCゲーム
ジャンル : ゲーム

[検索語彙への応答] 信長の野望 嵐世記 (5)

今回も「嵐世記 攻略」などで検索サイトから訪れるヒトへの応答。



前話で大砲部隊を混ぜた城攻めについて触れた。今回は、人材不足解消について・・・


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


嵐世記ではゲームを始める際、年代と大名家を選べる。有能な家臣を多くかかえる大名家を選択すればスムーズに進行できる。
そうでない場合、いわゆる人材不足の場合はどうしたら良いだろうか?

製品付属の解説書の余白を使って「二階堂戦記」なる例が載っている。これを手本に進めてみよう。

まず、主役となる大名家は岩代の二階堂家。当時「岩代」もしくは「岩州」と呼ばれていたのは、現在の福島県の西半分。海に面していない分、敵方の水軍 ( 海軍 ) に悩まされることはない。



嵐世記において二階堂家の当主は「二階堂輝行 」。歴史的な文献をたどると「二階堂照行」が正しいようだ。
しばし、武将は名前が変わることもある。室町幕府第15代将軍であった足利義昭も義秋と名乗っていた時期がある。
幼少の頃の名前と元服後でも名前が変わる。仕える主が変わると、主大名の名前を一文字含んだ名前に変えるなどなど・・・

二階堂輝行の子に二階堂盛義、その孫は盛隆、二階堂盛行、行栄などがいる。長兄の盛隆は蘆名盛氏の下、人質として過ごした。蘆名盛氏の後継者 ( 蘆名継嗣 ) が若くして亡くなったため、蘆名盛氏の養子となり蘆名家を継いだ。



二階堂家は岩代の中で、東西を二本松家と蘆名家に囲まれている。
二階堂輝行は伊達晴宗の妹と婚姻関係。伊達といえば誰もが思いつくのは伊達政宗だろう。伊達政宗は伊達家の支配地域を大幅に広げた。伊達晴宗の孫が伊達政宗である。

伊達家に援軍を頼めば二本松家、蘆名家を倒せる???



嵐世記の仕組み上、国 ( 現在の都道府県に相当 ) に3つの城が存在する状況ならば、2つ以上の城を支配しないと他国へ進軍できない。
ゲーム開始当初、伊達家は羽前、陸前の各ひとつの城を支配している状況。よって援軍を依頼できない。
同様に、隣接している下野、磐城、羽前、陸前では同じ国内に2つ以上の城を支配する大名がいない。よって、序盤から岩代に攻め込まれることはない・・・

二階堂輝行でプレイするならば西暦1534年もしくは1556年が選択可能である。
前者は火薬伝来前なので鉄砲や大砲は登場しない。後者の場合、息子の二階堂盛義が加わる。とはいえ、他大名家に比べ明らかに人員不足である。

有能な人材を確保するにはいくつか考えられる
・浪人武将を仕官させる
・国人衆へ仕官を促す
・子孫の元服を待つ

浪人を仕官させるには、奉行コマンドから「登用」の指示を出す。
知略パラメータの高い武将や「登用の特技を持つ武将なら成功の確率が高い。ほかにも、大名家の知名度の高低などに左右される。
幸いなことに二階堂輝行の特技に「登用」が備わっている。



1534年の段階で登用を実行したところ、大田原資清なる浪人が岩代を放浪中。文武に長けた人物のようで、活躍を期待できそうだ。実子には大関高増、福原資孝などがおり、元服した暁には・・・

登用が毎回成功するとは限らない。登用したい浪人が自国領に訪れている場合は次のターンへ移る前にデータをセーブしておこう。登用を失敗した場合は、ロードしなおして、プレイ再開を繰り返すと良いだろう。
浪人が自国領に訪れているか否かは、城下画面で歩いている人に話しかけてみれば ( 歩行している人物をクリック ) ヒントを得られるハズ。

ところで、製品付属の解説書に載っている「二階堂戦記」では、伊達家が滅びてしまい、当主であった伊達晴宗が登用に応じたことになっている。それに伴い、伊達家の旧臣たちが続々と仕官を申し出たとなっている。
実際にプレイするかぎり、伊達家が滅びるのは稀である・・・

国人衆を武将に取り立てるには、受動的と能動的の2つ。前提としては常日頃から物資の援助などを行い親密な関係にあること。
ひとつめは、国人衆が自ら仕官を申し出ることがある。それを受動的に待つ。ただし、申し出るのはいつになるか判らない。
ふたつめは能動的に、つまり、こちらから仕官を誘ってみる。具体的には、勢力コマンドで国人衆と交渉するのだが、仕官を誘える武将は限られていて、国人衆から武将に転じた武将だけが可能である。

同様に、交渉担当者が忍者出身ならば百地三太夫や風魔小太郎などの忍者、水軍出身ならば村上武吉 ( 村上水軍 ) などを仕官するよう誘うことができる。



幸いなことに、さきほど登用した大田原資清はかつて国人衆であった。これで国人衆を仕官させることが可能だ。
岩代の近辺を見渡すと 仁賀保挙晴 ( 羽前 )、黒川晴氏 ( 陸前 )、皆川政勝 ( 下野 )、皆川俊宗 ( 下野 ) など有能そうな国人衆がいる。きっと人材不足解消のカギになるだろう・・・

最後は子孫の元服について触れておきたい。あえて「息子」ではなく「子孫」と記したの理由は、嵐世記では武将や家臣の孫であったり女性の武将が登場するから。
大名家や家臣の息子が元服の年齢に達した段階で家臣に加わる。この辺は難しくない。
そのほか、大名の娘を武将として加えることができる。信長の野望では「女性武将」と呼ぶよりも「姫武将」といったほうが通じ易いだろうか。

季節の変わり目、2、5、8、11月、ある一定の確率で姫が登場する。実は、ゲーム開始時に設定画面の2枚目があり、「姫の誕生確率」を選択できるようになっている。案外、これを忘れがち・・・
もうひとつ、見落としがちなのは大名の年齢。史実でも若くして大名家を継いだ事例も多々ある。おおよそ、30歳後半までは姫の登場は期待できない。

姫が登場した場合、
・姫を武将として育てる
他大名と婚姻関係による同盟を結ぶ
などを選択できる。今回は人材不足が前提なので武将として育てるを選択する。



人材不足など、どうしても姫武将が必要ならばさきほどの、浪人の登用と同じ手法が活きてくる。2、5、8、11月の直前でセーブしておき、姫が登場するまで何度でもロードしてプレイしなおす
登場した姫武将は大名の一門となるので、複数の国を支配する際の軍団を任せるのも良いだろう。

製品を購入する際、パワーアップキット付きを選択すればある程度のデータ変更が可能である。大名家と国人衆を親密に変更したり、各武将の能力、特技、相性、寿命、国人や水軍出身といった点は修正できる。
一方、各武将の健康状態、浪人の滞在地や姫の誕生、血縁関係の変更や、養子縁組、各地の石高などは変更できない。
実は、この辺を変更したくて、過去記事嵐世記 石高を増やすではアプリを自作するに至ったのです・・・が、誰もが出来ることではありません。そのためには、PC の仕組みやプログラミングを覚える必要があります。自身で目標にたどり着きたいならば、学習を重ねることでしょう。
学校で歴史の勉強が嫌いでも、ゲームを楽しんでいるうちに戦国時代に詳しくなったヒトもたくさんいます。
何かに夢中になっているうちに、詳しくなってしまった」という方が、無理に覚えようとするよりも良い結果をもたらすことでしょう。短期間で集中的に覚えたことは短時間で忘れやすいそうです。

それが何であれ、身に付くきっかけと出会えますように・・・

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

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

テーマ : PCゲーム
ジャンル : ゲーム

[検索語彙への応答] 信長の野望 嵐世記 (4)

[検索語彙への応答] 信長の野望 嵐世記 (4)

今回も「嵐世記 攻略」などで検索サイトから訪れるヒトへの応答。



前話で個々の人物特性について触れた。今回は、大砲部隊を混ぜた城攻めについて・・・


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


すでに信長の野望 嵐世記 (1)で触れた通り、敵の大将を野戦で倒してしまえば、敵の城を落としやすくなる。
一方、篭城 ( 敵の大将が城に篭ってしまう ) となると厄介。なぜならば、敵の大将が健在の間、攻撃の間を空けてしまうと城の士気・防御が回復する。

テーマは
敵の大将を城の外におびきだすにはどうすれば良い?
答えは、敵の城に刺激を与えれば良いのだが、槍部隊、馬部隊などで猛進すれば反撃を喰らい、味方のダメージは避けられない。
そこで、大砲を用いて刺激を与えてみよう。



大砲を使える条件は
・火薬が伝来していること
・武将の特性で兵種「大砲」が備わっていること
の2点。

ゲーム中、火薬伝来のイベントが発生する ( 1543年8月 ) までは鉄砲や大砲が利用できない

火薬や鉄砲は1543年9月、ポルトガル人を乗せた貿易船が種子島に流れ着いたことにより伝来したと言われている。火薬伝来以前でも投石兵器が存在していたようで、甲斐 ( 現在の山梨県 ) の小山田一族などが有名。
火薬を用いた大砲という点では、豊後( 現在の大分県あたり )を支配していた大友宗麟公 ( 大友 義鎮 ) が1576年頃に輸入したと残されている。

嵐世記で大砲を入手するには、商人との取引や国人衆から献上、ほか敵方の大砲部隊の武将を引き抜いてしまうなどなど。引き抜きについては前回触れた。

火薬や鉄砲が伝来する前、商談の席で大砲を訪ねてみたところ商人たちの反応は・・・



2番目の条件となる、「大砲」を扱える武将のは、およそ 15人にひとり。全ての武将が扱えるわけではない。参考までに1582 年の段階で登場する武将が約610名、そのうち 43名が大砲を扱える。
また、特技に「雨撃」が備わっていれば天候に関係なく撃つことができる。特技に「攻城」が備わっていれば砲撃とともに、城に多大なダメージを与えられる。
以下、敬称略。兵種「大砲」と特技「攻城」を兼ね備えている武将は織田信長、伊達政宗、片倉景綱、佐竹義宣、大友宗麟、伊東祐兵など限られている。カルバリン砲や刀剣類の家宝を所有することで兵種や特技を追加できる・・・

大砲部隊を混ぜた戦を簡単な図で現すと以下のような感じ。



出撃する部隊に大砲部隊、荷駄隊 ( 補給部隊 )を加える。
大砲部隊も荷駄部隊と同様守備に劣り、重たいゆえに機動性を欠く。また、砲撃を続けると疲労が重なる。

城に近づきすぎない距離まで大砲部隊を移動。適度に休息できるように近くに陣を張り、不意打ちを防ぐため陣の近くに護衛部隊を配置。
城の近辺には弓、鉄砲部隊などを遊撃体制で待機させる。
駿足の馬部隊を先行させるよりも、大砲部隊を護衛しながら移動するのが良い。

大砲で城に一撃を加えると、それまで篭城していた敵部隊が城外へ出てくる。城の近辺に配置した弓、鉄砲部隊で敵の大将を狙い撃ちにする。
敵の大将を倒してしまえば、残り部隊も士気が下がり逃走。もし、味方に引き入れたい武将がいるならば深追いしても良いだろう。あとは馬、槍部隊で城攻めすれば落とせるハズ・・・

どのような兵器で攻めるかだけではなく、戦の前に敵の城を弱体化させておきたいものだ。
具体的には攻めたい城、国へ忍者を派遣する。忍者の役割は、敵の城下を焼き払う、敵の食糧を奪う、敵軍が侵攻してくる際の通知などなど・・・



忍者を使うには、ゲーム画面「勢力」コマンドから「派遣」を選択し伊賀や甲賀などの忍者衆と交渉。交渉が成立すると、忍者衆から大名家への貸し出される。つまり、ある程度の期間で帰ってしまう。忍者衆と友好関係にあれば契約延長も可能。
忍者の能力は A ~ E の5段階に別れ、焼き払いなどの成功率に関わる。能力の高い忍者ほど派遣期間が短く、再契約も割高になる。
さてさて、敵側の警備が手薄であれば吉報が届くハズ・・・



逆に、プレイヤー側の城にも火を放たれる。特定の城下での出火や食糧の盗難が続く場合、統率力の高い武将や剣豪の武将を赴任させて警備に当たらせると良い。
嵐世記では、軍団長の統率力が低い場合、警備に割り当てられる人数が限られてしまう。そのような状況では地域の警備を国人衆に任せるのもひとつの策。警備を依頼するには、国人衆と仲が良いことが前提となる。そのためにはコマメに物資の援助を行うのが不可欠・・・

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

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

テーマ : PCゲーム
ジャンル : ゲーム

[検索語彙への応答] 信長の野望 嵐世記 (3)

今回も「嵐世記 攻略」などで検索サイトから訪れるヒトへの応答。



前話で軍団について触れた。今回は、個々の人物特性について・・・


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


勉学が苦手であっても、運動が得意なヒトもいれば、音や描画、習字など芸事に長けたヒトもいる。
まずは、得意、苦手に関して。

嵐世記では登場人物の個性が「統率」、「政治」、「知略」などのパラメータで構成されている。それぞれの数値は 1 から 150 、また財宝を保有することで 最大 160まで。
「統率」は槍や馬、鉄砲の扱いに影響し、「政治」は農村や商業の発展、「知略」は登用や引き抜きが成功する確率に関わる。

さらに、馬での「突撃」や弓の「連射」など、20 種類の「特技」のうち4つ、財宝を保有することで さらに2つ、合計6つ

※ 購入の際、パワーアップキットを選択すると数値を変更できる

次に攻略のカギとなってくるのは「相性」。しばしば「意気投合する」や「馬が合う」、逆に「反りが合わない」と表すように、人物間の相性によってはモノゴトを運べないこともある。

信長の野望シリーズ全般で相性値の仕組みが採用されている。製品付属の解説書を確認したところ、相性値の仕組みについて図説が無かった。慣れていないヒトのために載せておこう。



相性は2軸のマトリクスで現すことも多いのだが、嵐世記では相性値は 0 から 99 の数値で表される。一周りすると100 となる円を想像すると判りやすいハズ。

武将だけでなく、姫、公家衆、商人など登場人物がそれぞれ相性値を持つ。ただし、忍者に関しては不明。二者の数値が近いほど相性が良い。逆に数値の差が大きいほど犬猿の仲である。

プレイヤーが織田信長公を選択したと仮定して話を進める。以下、各武将の敬称略。

織田信長の相性値は伝統的に 0。上記の図をもとにすれば、織田信長と相性が悪い武将の相性値は 40から 60。最も悪いのは50。



参考までに朝倉義景、一向衆 ( 本願寺衆 ) の相性値は50に設定されている。史実上でも激しく争ったので妥当な設定である。今川義元、武田信玄( 若かりし頃は武田晴信 ) も織田信長と反りが合わない側に設定されている。

武田家 ( 甲斐 ) の数ある戦の中で「川中島の戦い」が有名である。武田家と上杉家はそれぞれ山梨と新潟を拠点とし、長野県辺りでたびたび合戦を行った。両武将の相性値を見てみると武田信玄が 63、上杉謙信が 15 となっており、二者の差は 48。ほぼ険悪な仲となるように設定されている・・・

相性値は大名家間だけでなく、大名と家臣の関係においても重要。大名家を現代の会社と置き換えると判り易いだろう。
大名家間の相性は企業間での友好敵対、大名と家臣は雇う側 ( オーナー ) と雇われる側 と置き換えることができる。
相性値の差が少ないほど良く働くのに対し、相性値の差が大きい武将は不満を抱えながら仕えることになる。

例えば丹羽長秀や前田利家など織田家の躍進をもたらした武将の相性値は 0。同様に、名高い大名と家臣たちの相性値は近い値に設定されている。
逆に、「本能寺の変」を引き起こしたとされる明智光秀、明智秀満、斎藤利三が 56、溝尾茂朝が 57と設定されている。相性値の差が大きいほど、謀反や出奔しやすくなる。



ほかにも、忠誠に関わる要素として「主義」や「義理」「野望」などのパラメータがある。
「主義」は名声や義理に関して重視するか否かで4通り、「義理」は数値で 0 から 100となっていて、0に近いほど謀反や出奔といった他大名家からの誘惑に乗り易い。

相性値のことが判れば攻略への近道が見えてくる。
たしかに、力づくで城を攻め落とし続けるのもひとつの流儀ではある。少ない戦でゲームを進めることを考えよう。敵を弱体化させた後から攻めるほうが、敵の城を落とし易い。敵を弱体化させる、言い換えれば、敵を内部から崩すこと。

冒頭で述べた特技のうち「引き抜き」や「説得」を得意とする武将に「引き抜き」を指示する。



引き抜きにはいくつかパターンがある。信長の野望シリーズの他バージョンでは内応、引き抜き、謀反を細かく指定できるのだが、嵐世記では「引き抜き」のみを指示できる。
出奔、内応、謀反、大名 ( 雇用主 ) への信頼低下などなど、どの結果に至るかはランダムである。保存したゲームを再開しても、プレイする毎に結果が異なる。



「引き抜き」は現在でいうところのヘッドハンティング。敵方に有能な武将が仕えているとして、現状よりも有利な条件を提示してプレイヤー側の家臣になるよう誘う。
プレイヤーの石高が低い、つまり収入が少ない段階では十分な条件を提示できない。石高の高低は大名家の一年当たりの収入を左右する。新たに雇うには、農地の開拓や治水で石高を高めることも重要となる。

「出奔」とは逃げだすこと。出奔した武将は流浪する。まれに、警備の薄い城を乗っ取ることがあるので注意が必要。

最も有効なのは「内応」。単純な「引き抜き」との違いは、内応の約束が成立した後も敵方に残ってもらう点。



戦までは敵方に仕え、合戦の最中に部隊ごとこちらに寝返る。希に、内応を約束した武将が城を手土産に寝返ることもある。城ごと手に入れば戦を減らすこともできるだろう・・・

敵を弱体化できるということは、逆に、プレイヤー側も敵の謀略にかかる可能性がある。
敵方の引き抜きを防ぐには、
・手柄に応じた報償を与える
・警備を固める
の二点。

石高が低い段階では、登用 ( 新規採用) も慎重になる。
なかには、能力が低い割りに野望が高い武将も登場する。これらの武将は高めの知行 ( 年俸 )を欲する。有能な武将であっても、プレイヤーの石高が高くなってから引き抜いても遅くは無い。
新規採用の例外として、かつての大名が放浪していた場合は野望が高くても雇う価値がある。戦の末、大名家が滅ぶと家臣たちも行場を失っている状況。放浪していた旧大名がどこかに仕官したとなれば、旧大名の家臣たちが続々と仕官を申し込んでくる・・・

戦のほか、登用や農地開拓は担当した家臣の手柄となる。これらを任せるほど家臣に与える知行 ( 年俸 )を増やす必要がある。無頓着に進めると、いつの間にか人件費( 家臣たちへの支払い ) の増加に圧迫される。
嵐世記では知行に上限が設けられおり、一定の知行以上には上がらない。譜代の家臣、古くから仕えていて忠誠度が高く、かつ知行が高い武将に戦以外の平常業務を任せることにより、家臣たちへの支払いを抑制できる。
ただし、新人育成の観点からは外れる。同じ作業に長く関わるほど能力が上がる仕組みとなっている。

警備を固めるには、奉行コマンドから、軍事タブで警戒を担当する武将数を増やす。「剣豪」の特技を持つ武将や統率の高い武将を任命するのが良い。

嵐世記では、軍団長の統率力の高低に比例して、警備に割り当てられる人数が決まる。多くの人数を割り当てられないこともある。警備が手薄な城では食糧庫を荒らされたり、城下で火災が起きたり、ゲームの進行上好ましくない。
そのような際は、地域の警備を国人衆に依頼する。依頼してから一年間くらい警備を引き受けてもらえる。国人衆と仲が良いことが前提となるが、そのためにはコマメに物資の援助を行うのが不可欠。
逆に、国人衆との交流を怠ると一揆へ至ることもある。石高を高めたい目的で検地を行うこともあるが、これも国人衆との関係が悪化する。

嵐世記では、国人衆と親密な関係を維持できれば、城の改修に協力してもらえるほか、特産品の献上や豊作を祝うお祭りに招待されるなどのイベントが発生する。

実際のところ、戦国時代当時は階級があり、武士と農村で暮らす人々が交流する場は希であった。地域によっては、階級を超えた会話すら禁じられていた。
地位の高いヒトの中には、多くの人々と接することを避ける向きもある。危険回避の観点から、人前に姿を現さないことが良いという考えによるものだ。
せっかくのお祭り、おおぜいで楽しむにかぎる。豊富秀吉公はこの辺が巧みだったと伝えられている。現代で言うところの「顔を売る」に似た概念に近い。
普段からより多くの人々と交流しておくことで、いざ戦の際は味方に加わってもらえるのだ・・・

残りは後日・・・

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

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

テーマ : PCゲーム
ジャンル : ゲーム

[検索語彙への応答] 信長の野望 嵐世記 (2)

今回も「嵐世記 攻略」などで検索サイトから訪れるヒトへの応答。



前話で城攻めについて触れたので、その続き・・・


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


まずは、出陣できるか否かでつまづくヒトがいるかもしれないので触れておこう。
※ わが国の戦国時代に触れる場合「国」と表現される場合、現在の各都道府県と置き換えると理解しやすい。

史実上の織田信長公は尾張 ( 愛知県 ) 一帯を支配下においた後、美濃飛騨 ( 岐阜県 ) へ向かった。
このゲームでは、ほかの国へ出陣したい際、制限が加わる。詳しくは製品付属の説明書の13ページ目中段に載っている。ここで言う説明書とは「戦国兵法書」と題してある小冊子のこと。

※ ゲーム序盤では支配している城が少ない。同じ国内の別の城を攻める場合は特に制限が無い。



出陣可能な条件は
・隣接している国
・国内の過半数を支配下におくこと
の二点。

ひとつめの「隣接している国」に関してだが、その前の烈風伝や、その後のシリーズではこの制限が無い。信長の野望シリーズを楽しんできた方々は違和感かも。

ふたつめの「国内の過半数」に関してだが、注目すべきは「自家の城が過半数」という点。味方の城 ( 敵ではない ) とはいえ、主従関係や同盟関係にある他大名の城は「他家の城」である。
ここでの主従関係とは現在に置き換えれば親会社と子会社の関係のようなもの。
弱小な大名でプレイする際、期が熟すまでほかの大名に従属するのもひとつの策である。従属している間、強力な大名と敵対を避けられる。
ほかの大名に従属している状況、つまり、子会社のような状況においては、他家が攻めてきた際に親会社に援軍を要請できる。反面、他家の城を攻めても良いか否かを親会社の承諾が必要になったり、年始には馬や食糧を献上せねばならない。

もうひとつ他家と争わない方法として、大名の姫が他家へ嫁ぐことで血縁による同盟を結ぶことも可能だ。

弱小な段階では、同じ国内で同盟を結び争いを避けることができる。しかし、ゲームが進むにつれ他国へ出陣できるか否かで足かせとなってしまうかも・・・

さてさて、出陣と主従関係が判ると攻略の近道が見えてくる。
当時の首都は京都。上洛、つまり京都へ軍を進め支配することでゲームを有利に展開できる。京都にある城はひとつ。室町御所、時期によっては二条城。

仮に、現在の九州や東北地方の大名でプレイしていたとしよう。
九州であれば島津家もしくは大友家、東北地方であれば伊達家や南部家でプレイすると早めに地方統一できるハズ。
地方を統一すると、名声が高くなっているので、弱小大名を従属させることも可能。
ということで上洛への近道を・・・



ほか、紀伊 ( 和歌山 ) 、摂津河内 ( 大阪 )から京都へ向かう策や美濃飛騨、近江 ( 琵琶湖周辺 )といったルートもある。
近江では六角家、浅井家が強いだけでなく、さらに朝倉家が敵陣の援軍に加わる。同様に紀伊、摂津も強敵が存在する。
その辺を考えると、伊勢志摩、伊賀、京都へと向かうルートが安易かも。

このゲームにおいて、伊勢志摩には3つの城がある。ある時期までは北畠家と長野家が2対1の関係で支配している。もしこの状態にあるなら上洛のチャンスである。
プレイヤーの名声がある程度の高まっている場合、長野家が従属する可能性は高い。
さらに翌月以降、屈服、軍門に下せば伊勢志摩にある3つの城のうち亀山城が自家の城となる。
伊勢志摩の亀山城から北畠家の大河内御所もしくは鳥羽城へ出陣し、どちらかひとつを陥落させれば過半数を支配した状態となる。伊勢志摩から伊賀への出陣可能になる。さらに伊賀から京都へ・・・

ここで北畠家を滅す必要はない。たしかに、3つの城がある国では全てを支配したくなるものだが、ゲーム終盤までは全ての城を支配せずに過半数でとどめておくメリットもある。
例えば、支配する城が増えたとしても、武将が少ないならば警備が手薄になる。たしかに、多くの武将を登用すれば人材不足の心配から解放されるが、石高に限りがある以上、最小限の人材でゲームを進行しないと食糧不足などに陥る。さらに、大名と相性が悪い武将を雇うと反逆しかねない・・・

最後に、出陣できる部隊数について触れておきたい。参戦できる部隊の数が多いほど戦は有利になる。このゲームにおいて、ひとつの国で出陣できる部隊は最大で10。

ほかの国に攻め入る場合、その2倍、3倍の敵を相手にするかもしれない。その前に、攻め入る隣国の国人衆や水軍衆と親交を深めておけば、戦の協力を期待できる。コマメに物資の援助を行うことで親交が深まる。それらが加わっても部隊数が少ないと辛い戦もある。

複数の軍団でひとつの国に攻め入るならば、軍団の数× 10部隊 の参戦が可能となる。国によっては4方向から40部隊で攻めることもできる。
軍団とは家臣団のことで、支配する国がひとつの段階では軍団を増設できない。2つ以上の国を支配した段階で新しい軍団の作成が可能となる。最大8軍団まで。
軍団を新設したら軍団長を任命し、方針を与えることでゲームを半自動的に進めることができる。軍団長が大名の一族 ( 兄弟や子孫 ) であれば、第一軍団と同様に直接指示することも可能。



大名の属す第一軍団は、プレイヤーの意思によって好きな時に出陣できる。その際、第二軍団以降が同時に攻め入るのが望ましい。
もし、第二軍団以降の軍団長が大名の一族でない場合、直接指示できない。よって、いつ参戦するかは不明。大名の一族の武将でも能力値が低い状況もあり、ゲームを進めるにはほかの武将を任命せざるをえない状況もある。
ほかの軍団と同時に攻め入るようにするには、合戦前に、参戦させたい軍団の軍団長を大名の一族に変更する。
頻繁に軍団長を交代するのは良いことではない。もしかしたら、それまでの軍団長が不満を抱くかもしれない。不満は謀反のもと。それまでの軍団長を大名の側に移動させ、警備を固めることで敵の謀略に掛からないようにしたい。

ここでは合戦が終わるごとに、コマメに軍団の編成を見直すのが近道だ。攻めたい国の近辺を第一軍団、大名一族が率いる軍団に設定する。一方、敵と接していない国を別の軍団に配分しなおす。敵と接していない分、戦は発生しないので軍団長は能力の高い武将に任せ農業や商業に力を注ぐのが良いだろう・・・

※ 敵と接していない国でも、国人衆との交流を怠ると一揆が発生する。

前話と重複するが、全ての武将を参戦させない、留守居役を残すようにしたい。他国で敗れた武将が放浪している状況において、浪人が城を乗っ取ってしまうイベントが発生する。

残りは次回・・・

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

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

テーマ : PCゲーム
ジャンル : ゲーム

[検索語彙への応答] 信長の野望 嵐世記 (1)

信長の野望シリーズといえば、長く続いてきた歴史モノ。



学校の教科書よりも、ゲームで歴史を学んだヒトも多いのでは!?!?
今回は「嵐世記 攻略」などで検索サイトから訪れるヒトへの応答。

※ 過去記事では「嵐世紀」と記しています。「嵐世紀」ではなく「嵐世記」が正しい。


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

手元に残っていた PC 用のパッケージ にはゲーム導入用のディスク、小冊子、保証書などが入っていた。
パワーアップキット ( 拡張パック ) を購入すると武将データやシナリオが追加され、より楽しめる。



この作品が発売されてから10年以上経つ。なぜ、いまだに検索されていたのか疑問が生じる。
検索しているヒトの望みは「いかにして素早くクリアするか」といったところだろうか?
攻略も何も、「戦国兵法書」と題した 30ページほどの小冊子を読めばほぼ難しい点は無い。

古くなった PC を家族や知人間で譲渡することは多々ある。その際、ゲーム用のディスクだけが残っていて、箱や付属品を廃棄してしまったのかもしれない。
ほかに、旧作品が再販されることがある。この作品も2008年頃、廉価版が流通していた。廉価版は購入しなかったので推測で申すが、コスト削減のため、付属品が省かれてしまったのかもしれない。
そうでなければ・・・

前もって述べておくと、ここはゲームの攻略に特化したブログではない。
過去記事嵐世記 石高を増やすを最後にこの種のテーマを避けてきた。その記事から汲みとっていただきたい旨は
「PC や プログラミングに詳しくなればこのような事も可能」ということ。もちろん、詳しくなる動機として「音遊びをしたい」や、「素敵な画像を描きたい」でもよい。もっとも、プログラミングの面白味を知ると、ゲームに時間を費やすことは減るだろう・・・

不慣れなヒトのために記しておく。
わが国の戦国時代に触れる場合「国」と表現される場合、現在の各都道府県と置き換えると理解しやすい。
例えば「上野国」を今に置き換えると「群馬県」、同様に「下野国」は「栃木県」となる。
ということは戦国時代「東京都」に相当するのは「江戸国」???
現在の埼玉県、東京都、神奈川県の東部を含めた地域は「武蔵国」と呼ばれていた。
ちなみに、現在の東京湾岸エリア、徳川家康公が江戸に幕府を開いてから埋め立てが進められた。例えば、江戸城 (現在の皇居) 辺りが海と陸の境、日比谷は入り江、つまり水面下だった。
歴史の勉強となると年号や難しい語句の暗記と思いがちで苦手なヒトもいるだろう。数百年前、日比谷が水面下だった等の知識を得ることで、現在に生きる我々は防災・減災のヒントが見えてくる。歴史を学ぶことは決して無益ではない・・・

本論に戻る。
このゲームでは54の国に分けられており、各国には3つもしくは2つの拠点となる城がある。( 山梨と京都は1つ)。国内にある過半数の城を手に入れると、その国を支配したことになる。
まずは、城攻めを覚えるのが良いだろう。

製品付属の説明書に攻城作戦「トリプルアタック」との記載がある。ここで言う説明書とは「戦国兵法書」と題してある小冊子、その5ページ目上方。
簡単に書けば、部隊を3つに分け、休息、移動、攻撃をくり返す。守る側にすれば、常に攻撃にさらされ、気の休まる暇も無い。



現在に置き換えて考えるならば、24時間常に稼動している工場を想像すると判り易いかも。三組三交替制など、一日を8時間ずつに分けて作業を繰り返す例がそれに近い。
絶え間なく攻撃するのが肝となる。なぜならば、攻撃が止んでいる間、敵側の士気が少しずつ回復する。

※ 城外で敵の大将を倒してしまえば城は回復しない。

これは、あくまでもゲームにおける一例である。以下、少し脱線する。
実際のところ、24時間攻撃することや毎日城を攻め続けるのは難しかったようだ。
日没にともない一旦停戦。これは事故防止の観点から重要である。現在を基に器具、技術を考えると理解できないかもしれない。
残っている資料によれば、暗がりの中、部隊を移動させたことによりトラブルが生じた例も多い。
夜間の戦を避けるだけでなく、米の収穫期直前やお正月やお盆には戦を避ける等の暗黙の掟があったらしい。
たしかに、年始のお酒が抜けていない状況で規律ある集団行動は難しい。さらに、
「腹が減っては戦ができぬ」
「The mill stands that wants water. ( 水不足の水車は動かない )」
「軍隊の進軍は腹次第」等のことわざが示すように、戦を優位に進めるには食糧を欠かさない。
収穫後の方が食糧事情に余裕があるのは明白。
戦に向かう人々ではなく、その地に暮らす人々の立場だとしよう。人々の多くは農業に携わっていた。
「もう少しでお米が収穫できる」という時期に戦が始まってしえば、耕作地が荒らされ作物が得られないだろう。
統治する立場でいえば、国人衆と親しい関係を築くのが良い。その点からも収穫期前の戦は農村で暮らす人々の心をつかむどころか、不満へと繋がり、親しい関係から遠ざかる。
他にも、雨風や暑さといった自然の条件に左右される。雨季に鉄砲の火種を維持すること、夏季に鎧、兜などを装着する重さや熱による兵士の疲労などなど。一年を通して考えると、合戦に適した時期は限られていた・・・

※ 戦国時代の資料を眺めるかぎり、その掟は破られたようである。

さて、嵐世記の話に戻る。
休息するには宿舎が要る。いくつかの選択肢がある。
戦闘の行われている地域に味方の城 ( プレイヤーの支配下にある城、もしくは同盟軍の城 ) がある場合、その中に入り休息が可能である。
特定の宗教に属する場合は寺院、水軍衆と親密な場合は船、国人衆 ( 地域住民 ) と仲が良い場合は村で休息できる。

もっとも無難な選択肢は、出陣する部隊に荷駄隊 ( 補給部隊 ) を加えること。
理由としては、敵の近くに陣を張る ( 休息所を設ける ) ことで移動距離を縮められるから。
現在に置き換えれば、通勤通学時間を縮めることに等しい。例えば作業場のすぐ隣に宿舎があるか、それとも、毎日数時間かけて往復するかの違い。



注意したい点のひとつめは、「荷駄隊」を構成できる武将は限られている。武将の属性、兵種を確認しておく。
ふたつめは、荷駄隊は戦闘能力を持たないので守備力はゼロに等しい。荷駄隊が襲撃されると部隊が壊滅することもある。
策としては、休息の際、ひとつの部隊を陣の外で待機させ、警備するのが良いだろう。仮に敵が荷駄隊を狙ってくるとしても、早めに発見し対処すれば全部隊壊滅の可能性が低くなる。

ほか、城攻めに役立ちそうなのは強攻。説明書の5ページ目中ほどに「強攻」に関する記載がある。
いくつかの部隊を合流させて陣形を整え、強攻を発することで一定の期間、攻撃力が向上する。副作用として、強攻の後は疲労消耗が激しい。回復までの時間も長くなる。
「もう少しで城が陥落する」「城外で敵の大将と直面した」等、ここさえ乗り切れば勝利が確定する局面で強攻を発するのが良い。

初めのうちにマスターしたいポイントをまとめると、
・動と静を分ける
・より速く回復するには
の2点。
残りは後日・・・

・・・と思ったが、以下の注意事項を追記。
ほかのゲームと同様、参戦する部隊数が多いほど有利に進めることができる。
そのうえで、全ての武将を参戦させない、留守居役を残すようにしたい。
他国で敗れた武将が放浪している状況において、浪人が城を乗っ取ってしまうイベントが発生する・・・

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

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

テーマ : PCゲーム
ジャンル : ゲーム

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

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



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


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


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

この種のお題、鋭いヒトを対象に限定して綴るならば1話簡潔型に進めることも可能。・・・当ブログにたびたびお越しいただいたブロガーさんの中で、プログラミング技術の向上を目指している方々もちらほらと。中でも、学生の方々、学業の合間をみて、向上しようという意欲に心を動かされた・・・というのがこのお題の発端である。
さらに、この題目のきっかけとなったブロガーさんを見るかぎり、「まだ実戦ではなく、C/C++ 言語の習得に奮闘している段階」とのこと。たしかに、不慣れなヒトがつまづきそうな点は多々あり、ひとつひとつ綴ってゆくとかなりの分量となる。どこまでも綴るのは不可能なので、今回をもって一応のピリオドとしたい。

さてさて、Visual Studio を起動してからここまで小一時間。
「高速化のために改良したい」と考えるのは結構なことだが、それに膨大な時間を費したのでは本末転倒。
そもそも、記事にまとめるコト自体が時間のロスと言われそう・・・

環境など
OS は Windows 7 x64、CPU は Core i3 3220T、メモリは DDR3-1333 8GB (4GB 2枚) を搭載した PC 。アプリの作成に関しては、C/C++ 言語、Visual Studio 2008 を用いて x64 向けにビルドした。

筆者がこれを下書きしたのは2012 年暮れのこと。店頭に並んでいる PC を見るかぎり、Intel 製の CPU は「第 3世代 Intel Coreプロセッサー」( 通称 Ivy Bridge ) が一般的であった。なかでも、筆者が所有していた CPU は Core i7-3770 、同 3770T と i3-3220 、同 3220 T 。キャッシュやターボブーストが効かないほうが中立的な結果をえられると考え、i3-3220T を選択した。

車輪の再発明 (6)の終盤で触れたように、結果がバラつく要因を減らしたい。よって、OS を再起動後、省電力状態に突入しない段階で速度を測った。ひとつのパターンにつき10回行った平均値とした。また、極端に値が偏った場合は除いた。

結果を画像として載せる。多少見辛いかもしれない。結果を一覧表としてアップしたほうが読み易い。
以前、別のテーマにて細かい件を綴ったことがあった。結果として、筆者の許可なく転用され、悪用された。
誰しもピックアップされることは嬉しい。反面、濫りに用いられるのは不愉快でもある・・・



右側の列はパターン、手法などを記した。左側の列、縦方向は要した時間。単位はミリ秒。1000 ミリ秒で1秒。
要した時間の中には、残り回数の減算とゼロ確認、ポインタを加算、繰り返しの先頭へのジャンプが含まれている。
ロード、ストア部だけの純粋な数値ではないことに用心いただきたい。

コード等は車輪の再発明 (9)

1番目は OS に備っているメモリ転送関数を呼び出すパターン。
2番目以降は 32 ビット幅や 64 ビット幅の汎用レジスタにロードされた値がストアされる命令が生成されるように手を加えたパターン。

なお、アプリをビルドするにあたり、ターゲットを x64 (64ビット版向け)に設定している。車輪の再発明 (9)で触れた通り、32ビット版をターゲットにビルドした場合、32ビット幅を持つレジスタへのロード、ストアを2回行うように翻訳されてしまう。

汎用レジスタにロードされた値がストアされる命令が生成されたことをアセンブリ言語で出力されたファイルで確認した。アセンブリファイルを出力する方法については車輪の再発明 (7)で触れた。

ほか、ループ ( 繰り返し ) 部を
・特定の回数に達するまで繰り返す
・残りがゼロになるまで繰り返す
と変えたり、複数の汎用レジスタを用いてのロード、ストアを試みた。

ループ数の大小、つまり「何回繰り返すか?」といった部分の差は車輪の再発明 (10)で触れたように、「複数の実行ユニット」で分散する仕組みにより吸収されているようだ。
ストアと並行して残り回数の減算や確認を行っていると推測できる。

この段階で論じるならば、高速化を期待して手を加えたにも関わらず、遅くなるパターンも見受けられる。
ゆえに、OS に標準で備わっているメモリ管理系の関数や古典的な関数を用いるのが無難。

たしかに、昨今でも
「関数呼び出しはオーバーヘッドのため遅くなる。」
等の文言が唱えられている。
オーバーヘッドという表現が難しいだろうか。スポーツの分野では宙返りしながら空中のボールを蹴るコトを指したり、音を楽しむ場ではヘッドフォンを指す。ここでは、ある関数を呼び出す、その中から別の関数を呼び出すこと意図している。
例えば、実生活の場において何らか作業を依頼する場合、元請け・下請け・孫請け・ひ孫請け・やしゃご請け ~~ といった事態に陥ることがある。
依頼者からみれば、丸投げ、中抜きされた分だけ余分なコストが生じる。もし、作業にあたるヒトに直接頼むことができれば「余分なコスト」を省ける。「余分なコストを省きたい」と考えるのは自然なことである。

多段に渡る呼び出しはオーバーヘッド、つまり余分な手間、コストがかかると考えられている。
今回測った数値をみるかぎり、
関数呼び出しに要する余分なコストを差し引いてもOS に備わっているメモリ管理系の関数や古典的な関数速くなることを期待して手を加えたつもりコードとの差は微々たるもの。

次に、SSE2 命令を使うように手を加えた諸々のパターン。


コード等は車輪の再発明 (10)

1番目と2番目はロード、ストアの対象アドレスが16バイトの倍数でなくともエラーが生じないように、C/C++ 言語の組み込み関数として _mm_loadu_si128_mm_storeu_si128を用いた。
16バイトの整列、非整列に関しては車輪の再発明 (8)で触れた。
コンパイル時に出力されたアセンブリ言語のファイルを確認すれば ロードとストアの部分が MOVDQU

1番目のパターンはひとつの XMM レジスタでロード、ストアを繰り返す。
2番目は4本の XMM レジスタを用いてロード、ストアと変更した。繰り返し回数は 1/4に減る。

3番目は ロード、ストア に _mm_load_si128_mm_store_si128を用いた。
アセンブリ言語で出力されたファイルを確認すると、ロードとストアが MOVDQU から MOVDQA に変わる。

4番目、5番目 はストアをキャッシュを介さないように変更。_mm_store_si128の部分を _mm_stream_si128に変更した。
アセンブリ言語でのロードとストアが MOVDQA から MOVNTDQA に変わる。
4番目は4本の XMM レジスタを使うのに対し、5番目は8本の XMM レジスタを使う。

把握し辛いかもしれないので、簡単な棒グラフで示す。





SSE2 を使うように手を加えた段階でも、パターン1からパターン3は、SSE2 命令を使わないパターンや古典的な関数を用いた場合と大差ないように見える。
アドレスが16バイトに揃った状況ゆえに、MOVDQU と MOVDQA どちらも似通った結果が出た。
明らかに向上したのはパターン4とパターン5で、これらはストアをキャッシュを介さないに変更した効果と考えてよさそうだ。
ちなみに、車輪の再発明 (9)で触れたように、SSE4.1 以降では ロードに関してもキャッシュを介さない命令が加わっている。
※ SSE4.1 の命令が使えるのは 第2世代 の Core2 Duo , Core2 Quad 以降。

局所的に使う XMM レジスタの本数を増やす効果はどうだろう。
1本から4本に増やした場合、わずかながら効果があるようだ。一方、4本から8本に増やしても効果が無いようだ。
これは伝送路、通路や廊下が詰まってしまい効果が得られないと考えられる。むしろ、混雑により待たされるかもしれない。混んでいる間に別のコトを進める方が有利ということだ・・・

効果はいかに!?

OS に備わっている CopyMemory 関数を使う場合と手を加えた場合の差は 35 から 38 %。
この数字を見るかぎり、細工により高速化したかという面で疑問・・・



筆者は以前も同じような試みをした。Pentium 4 が全盛の頃、Core 2 Quad / Core 2 Duo が登場した頃、そして今回。

Pentium 4 が全盛の頃、ストリーミング・ストアとソフトウェア・プリフェッチをうまく組み合わせることで4倍から5倍、もしくは、それ以上の高速化を見込めた。
結果を図表に載せていないが、今回もストリーミング・ストアとソフトウェア・プリフェッチの組み合わせも試した。しかし、大幅な速度向上とはならなかった。むしろ、プリフェッチ命令が挿入された分、遅れるようにも感じる。
もしかすると、筆者の組み方が悪いのかもしれない・・・

違う視点から考えてみよう。
「Pentium 4 の頃と比べ相対的な数値が伸びなかった」
という点は否めない。一方、同じクロックの CPU として比べれば
「絶対的な数値は大幅に向上している」と解釈できる。

Pentium 4 ( Prescott 世代 ) と同じクロックの Core i3-3220 ( Ivy Bridge 世代 ) で同じベンチマークテストを行ってみた。
おおむね、メモリ操作を中心としたもので 3.5 倍、シングルスレッドの演算で約 3 倍、マルチスレッドでは 6 倍から8 倍ほどの差が出た。
大雑把に言えば基礎体力が違う。別の言い方をすれば、フットワークが軽くなっている。

フットワークの差を単純に3倍と捉えて考えてみよう。
不慣れな作業員とベテランの作業員がいるとして、同じ時間でこなせる仕事量が3倍違うと仮定する。
前者に、もともと60分かかる作業を任されたとする。本気で挑み15分で終わるようになると、かなり短縮されたと実感できる。本気で作業するにあたり、細々と指示が与えられた。
一方、後者は基礎的な力が3倍。ということは要する時間が1/3。前者が60分かかる作業、細々と指示を与えずとも20分で完了するだろうと期待される。こちらも本気で挑むとして、作業時間が20分から15分や10分と短くなったとしよう。相対的な評価となれば「かなり短縮された」とは扱われないものだ。
とはいえ、無限に短縮できるものではない。どのような分野でも超えられないカベがある。
要は伸び代がある、ない。この例で、不慣れな作業員は伸び代があるに対しベテランの作業員は伸び代がないと言える。

技術競争が活発な分野では、現状の弱点を克服しようと常に改良・改善が施される。また、新しい技法も編み出される・・・

アプリ作成、とりわけ速度に話を絞ろう。
旧来はソフトウェアレベルで人間があれこれ指示する必要があった。やがて、ハードウェアレベルで進化したことにより、細々と指示が与えなくともよくなった。
と言える。たしかに、
「現時点で遅いから何とかしたい」という希望を抱くのは良いことだ。
もちろん、ハードウェアレベルでの改良作業に携わっているならば・・・
しかし、ソフトウェアレベル、というよりアプリを組む側ならば
「現在の弱点は将来、機材の進化で解消されるかもしれない。」
と割り切って考えるべきだろう。

実際、永く続いているアプリはいくつものバージョンがリリースされてきた。機材の世代に応じた最適化が施されてきた。偏った最適化の弊害として、機材の世代によっては能力を発揮できないかもしれない。
商用としては、それも一理ある。新世代向けの新製品をリリースすれば、消費者の購買意欲を高めることができるであろう。

仮に、アプリ作成に不慣れな段階であっても、「最適化」や「高速化」に興味を持つのは多いに結構。それらを扱った文献も多い。しかし、優先すべき課題は、完走すること。ゴールまでたどり着くことだ。
ゴールと表現しているのは、アプリ、つまり作品を完成させることではない。
「実行中に暴走したり、リタイヤするような、残念なアプリを組まない」ことを目標に進むべきではないか・・・

ここから先、載せるべきか迷った。この題目のきっかけとなったブロガーさんは、2012年暮れの段階で Windows XP を利用しているとのこと。「32ビット版 のプラットフォームを利用しているヒトはまだまだいる」との判断から載せることにする。あくまでも参考程度・・・

プラットフォームを x86 、つまり 32ビット版の Windows 向けに切り替えた結果は以下の通り。



ほぼ、64ビット版のプラットフォームで試した際と同じ傾向であった。興味深い点もちらほらと。

まず、MOVSD を用いた場合の結果は 478。車輪の再発明 (9)の終盤で触れたように MOVSD は複数の意味があり、ここでは Move data from String to string 「ストリングの移動」を指す。
32ビット版の CopyMemory 関数を呼び出す場合より遅くならない、というより 15% ほど速い

PC の中ではデータの移動やコピーは頻繁に行われる。これが短縮されることは大多数のヒトにとってメリットである。
かつては、メモリ間コピーを高速化しようとさまざまな技法が提唱、編み出されてきた。なかには、頭の体操というレベルを超え、アクロバティックな技法も存在した。
逆に言えば、メモリ間コピーが速ければこの種の試みは避けられていただろう。同様に筆者もMOVSD や MOVSB , MOVSW は速くないと思い込んでいた。昨今、MOVSD を用いても遅くならないようにハードウェアレベルで進化したと解釈できる。

ほかにも、32ビット版 に限られてしまう手法としては MMX レジスタを用いてキャッシュを介しないデータ移動が考えられる。こちらは 25 % ほど速い。かつて、2倍近く高速化される例も体験したことがある。その頃に比べ相対的な伸び代がないのかも。
ただし、高速化が見込めそうだというだけで期待しないほうがよい。なぜならば、64ビット版の Windows では FPU や MMX の利用がサポートされない。
※ 浮動小数演算を高速化するための FPU と MMX は表裏一体。

SSE 本来の使い道!?

最後に、誤解がないよう加えておく。今回の記事は 「2012年の暮れ時点でロードやストアの高速化に適さなかった」を旨としている。決して、SSE や AVX などの存在意義を否定するものではない。
車輪の再発明 (2)で述べた通り、SSE や AVX などの機能が追加された経緯としては、画像や音声などのデータ処理を効率的に行うためである。
SSE や AVX などの機能は、ロードやストアの高速化を目的に拡張されたのではない。画像や音声データを加工処理する際、同じような演算が繰り返される。これを同時に処理することで時間の短縮を狙いたい場合に適している。

ロード、演算、ストアをそれぞれ1ステップとするならば、旧来からの方法は4回繰り返せば 3ステップの4回分、12ステップ。
※ 必要なクロック数ではない。演算にかかるクロック数に興味があるヒトは2012年 8月13日掲載の除算が遅いをどうぞ。

SSE/SSE2/SSE4 などを用いれば、同時に4つ分をロード、同時に4つ分の演算、4つ分ストアの3ステップに減る。
ロードとストアに要する短縮は期待できないとしても演算3回分のステップが減り、4+1+4 、それ以前と比べ高速化が期待できる・・・

判り辛いだろうか。2012年 8月13日に掲載した記事のようにスイカを4等分する例で考えてみよう。



目の前にたくさんのスイカがあるとする。
今までの4倍、まな板の上にスイカを移動する。4等分したスイカをまな板の上から皿に移す。この辺の移動に要する時間はさほど変わらない。
まな板の上でスイカを切る、割る作業を担当するヒトを今までの4倍に増やす。それまでは一人で4等分していたとすれば、4人で同時に作業にあたる。よって、まな板の上で行う作業が4倍速くなる、作業時間が短縮される・・・のように想像すれば並列に実行するメリットが判るハズ・・・

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

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

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

[未掲載分] 車輪の再発明 (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 (英語版) への検索リンク

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

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

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

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

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

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

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



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


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


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

今回もサンプルコードを載せる。特に難しいところは無いので、サラっと次へ進みたいところ。だが、不慣れなヒトのために少々述べておく。

これを下書きした時点において、これからアプリを作ろうとするヒトが参考にするような書籍や解説系のサイト等は 32ビット環境を主軸に書かれている。
たしかに、Windows 95 が登場した頃からつい数年前までは 32ビット環境が主流であった。Windows Vista の登場により 64ビット環境の構築が容易になったが、店頭販売されていた PC のうち、80 ~ 90%は 32ビット版 の OS を搭載したモデルだったように記憶している。
64 ビット版 の OS が異端というワケではなく、既存のアプリとの互換性、つまり、「昨日まで使ってきたアプリを新しい PC でも使いたい」との声に応えたかったのであろう。
筆者の個人的な感覚で言えば、 Windows 7 の導入を機に 64ビット環境への移行が進んだように感じる。

OS が変わっても昨日までと同じ動作、結果を求めがちだ。既存のアプリとの互換性で言えば、昨日まで使ってきた 32ビット版のアプリがお行儀よく組まれたアプリであれば、64ビット版 の OS 上でほぼ動く。
「互換性が保たれる」という点から甘えが生じる。「このアプリはまだまだ使える」と思い込んでしまえば、新バージョンのアプリへ乗り換えを先送りしがちだ。仮に 32ビット版の新しい OS を選択しても、お行儀の悪いアプリは何かとトラブルのもと。OS を乗り換えたら、アプリも新規に買い揃えるのが無難だろう・・・

話が逸れてきたので戻ろう。
データ幅、レジスタの幅の意識を

CPU のデータ幅やアドレス幅などを論じだすと誤解が生じる可能性がある。ここで述べるのは、数値を扱える幅、データ型のこと。

Windows 用のアプリを作る場合、独特のデータ型の呼び名を覚えることになる。
今回はC/C ++ 言語でアプリを作る例として、DWORD32 や DWORD64 というデータの型を用いる。詳しくは windef.h や basetsd.h という名称のヘッダーファイル中で定義されている。

DWORD32 は32ビット幅の「符号なし整数」。通常は 「32ビット」と指定せずに DWORD と書くことが多いだろう。
C/C++ 言語の入門書等では unsigned int などが載っているハズ。

同様に、64ビット幅の符号なし整数を用いたいならば DWORD64 や DWORDLONG を用いる。
ほかにも、符号なし整数の仲間として UINT16 , UINT32 や UINT64 等のデータの型が定義されている。これらは固定幅の整数型である。

ならば、「16ビット幅の時はDWORD16 ???」
との声も聞こえてきそうだ。
DWORD のもともとの意味は Double WORD。WORD とは16ビット幅の符号なし整数、unsigned short つまり、DWORD は 16ビット幅の符号なし整数2つ分。
16ビット幅の「符号なし整数」をお望みならば 「D」「16」も付けないWORD。
アセンブリ言語では 16ビット幅の符号なし整数4つ分、64ビット幅という意味で QWORD もある。Quad WORD の略。既出の DWORD64 や DWORDLONG よりも QWORD の方が判りような・・・

ほかに「符号なし整数」としてunsigned long が紹介されている例もみかける。たいていは 32ビット幅を指していると思われるが、プログラミングモデルによって64ビット長の符号なし整数を指すケースもあるので要注意。こちらも固定幅の整数型として用いるならば ULONG32 や ULONG64。

前話、車輪の再発明 (8)で「32 ビット版の Windows と 64 ビット版の Windows の両方のポインタに合わせてサイズが変更される整数型」として、UINT_PTR という「符号なし整数」について触れた。
「_PTR」の付く整数型は流動的に翻訳される。ポインタの変換などを扱うのには便利。


今回は 旧来の4バイトずつ転送する例と16バイトずつ転送する例を比べたい。よって、「32ビット幅の~~」と明確に意識したコードを書く。1バイトは 8 ビット、ゆえに 32ビット幅は4バイト幅を指す。

ところで、入門者向けの書籍や解説系のサイト等で扱われている整数のデータ型でもっとも多いのは int。冒頭で述べた、「32ビット環境を主軸に」言い換えれば「32ビット環境を前提に」とも言えるが、モノによっては int は 32ビット幅と決めつけているような例もある。
「int は 固定幅の整数型ではない」という考え方は筆者の思い込みかもしれないので、手元にある K & R 本を確認してみた。それによれば データ型の説明として
・「通常、特定の計算機に自然な大きさ」
・「short は int より長くてはならず」
・「int はlong より長くてはいけない」の3点が挙げられているが、int は 16ビット幅 ( 2バイト ) とも 32ビット幅 ( 4バイト ) とも明言されていない・・・

コードと説明を載せる前にもうひとつ。
データ型を確認する方法を載せておきたい。
データ型を知りたい状況になると、windef.h や basetsd.h などのヘッダーファイルを探し開く。



上の図は Windows 7 でのファイル検索の例。
エクスプローラー右上の検索ボックスを使えば多数のファイルを探し回るよりも楽。



同じような名称のファイルが複数列挙される。更新日時が目安になるかもしれないが、どれがアクティブなのか迷うのも当然。
開発環境として Visula Studio を使っているならばもっと楽になる。

例えば 、先ほど触れた DWORD32 を確認したいとしよう。



ソースコードの調べたい語句をダブルクリックすると選択された状態になる。反転表示される。
ここでキーボードの[F12]ボタンを押せばOK。

マウス主体に操作している時や[F12]ボタンが効かないこともありそうなので加えておこう。
反転表示された真ん中周りへマウスのポインタを移動し右クリック。
キーボードから操作する場合は、[Shift]ボタンを押しながら[F10]を押す、もしくはアプリケーションキーを押す。
※ アプリケーションキーとは右 Ctrl ボタンの近くにあり、メニューとマウスポインタのような矢印が描かれたボタン。



コンテクストメニューの [定義へ移動] もしくは [宣言へ移動] をクリック。



DWORD32unsigned int であることを確認できるハズ。



タブをみると、BaseTsd.h を開いていることが判る。
確認が済み次第ファイルを閉じる。くれぐれも、ヘッダーファイルに変更を加え保存しないように。

ファイルを閉じるにはタブを右クリックしてコンテクストメニューの[閉じる]を選択するか、キーボードの[Ctrl]ボタンを押しながら[F4]を押す。
キーボード操作で間違えやすいのは[Alt]ボタンを押しながら[F4]を押すことによるアプリの終了。ここで言うアプリの終了とは ウィンドウ右上の[閉じる]をクリックするのと同等、Visual Studio の終了・・・

前話の終りで 「CopyMemory 関数 を書き換えてみる」 と述べた。正しくは「自分で書いてみる」という方針。
「書き換え」という語句は API フックを指すこともある。フックとは釣鐘ではなく、関数を横取りすること。筆者もその手法を用いて作ることもあるのだが奥が深く、ここに載せるには長い。そもそも、記事のタイトルと逸れてしまう・・・

ということで、CopyMemory 関数のようなコードを自分で書いてみる。

3話前の車輪の再発明 (6)で触れた
SIZE_T i;
for(i = 10 ; i ; --i){

CopyMemory( ~ );
}

この4行のうち、for( ~ ){} の間、CopyMemory( ~ );を変更。

最初は簡単で手軽に考えてみる。仕組みとしては、あらかじめ決めた回数だけロードとストアを繰り返す。
4バイトずつ転送するので繰り返す回数は __SIZE_DUPLICATE として定義した数の1/4。
DWORD32 *pdwd , *pdws;
pdws = (DWORD32 *)(lpTemp[2]);
pdwd = (DWORD32 *)(lpTemp[3]);
SIZE_T j;
for(j = 0 ; j < (__SIZE_DUPLICATE / sizeof(DWORD32) ) ; j++){pdwd[j] = pdws[j];}
for 文による反復を使ってこんな感じだろうか・・・

#if __MYAPP_LEVEL == 2
// 第2番目のパターンのコード
#elif __MYAPP_LEVEL == 3

2話前の車輪の再発明 (7)で触れた #if ディレクティブ ~~ #else ディレクティブ ~~ #endif ディレクティブを活かし、第2パターンのところにコードを追加する。

慣れないヒトにとっては変数名が気になることだろう。
ハンガリアン記法ハンガリー表記法と呼ばれ、古くから Windows でプログラミングする際に用いられてきた。
プレフィックス、接頭辞に特徴があり、ポインタであれば「p」、データ型が DWORD ならば「dw」といった具合。
※ 「用いられてきた」と過去形で表現は、昨今の MSDN ライブラリなど、変数や関数の名前付けガイドラインに「ハンガリー表記法は使用しないでください」と記述があるから。

DWORD32 *pdwd , *pdws;
の部分は「32ビット幅の符号なし整数へのポインタを使う」
pdwd[j] = pdws[j];の部分で
ポインタ pdws の指すアドレス (= 場所) にある数値を4バイト分(= 32ビット分) 読み出し、ポインタ pdwd の指すアドレスを4バイト分書き込む。
それを for 文で繰り返す。転送するバイト数は既に __SIZE_DUPLICATE で定義している。この数を 4バイト分 で割った回数繰り返す。

本来、for 文や while 文、さらには if 文において単一の命令を繰り返すならば、命令を {} で囲む必要はない。
入門用のサンプルでは略されることが多く、{} を省いた方が見た目がシンプルである。
実際には繰り返し文の中に複数の命令コードを設けることも多いので{} で囲む。
あまりにも深いネストは直感的な読み易さを損なう。
Visual Studio では最初の { を入力すると閉じ括弧が強調表示されるようになっている。

鋭いヒトはお気づきの通り、何かが抜けている。略してあるのは端数の転送処理。
今回はたまたま、転送するバイト数が4の倍数と判りきっているので不要だ。しかし、実際には4の倍数以外を転送する、つまり、4で割って余りが出るようなケースも想定すべきなのだ。
余りはゼロから3の間。その場合は残りを1バイトずつ転送すれば良いだろう。最大3回までで済む。
ちなみに、数年前までのIntel 製 CPU では4バイト転送の直後に2バイト単位のアクセスを行うとペナルティ、つまり、遅くなると言われていた。Core 2 Duo , Quad 以降では改善されているようで、2バイトアクセスによる遅延を気にしないでもよさそうだ・・・

このままでもコンパイラが良好なコードを出力してくれる可能性が高い。
あえて、変更を加えてみよう。

SIZE_T j;
j = (__SIZE_DUPLICATE / sizeof(DWORD32) );
while(--j){
    *pdwd++ = *pdws++;
}

インクリメント演算子 (加算演算子) 、デクリメント演算子が読み辛いかも。
演算子を加算後に代入、減算後に代入に置き換えてみると以下のようになる

while(j){
    *pdwd = *pdws;
    pdws += 1;
    pdwd += 1;
    j -= 1;
}

さらに読み辛いヒトは△-=1△ = △-1と置き換えてみればわかるでしょうか・・・

最初の例と比べ for 文だった部分が while 文 へと変更。
最初の例でデータの読み書き部分がポインタと配列の組み合わせだったのに対し、ポインタ値での読み書きとなったため、自前でポインタ値を加算するように変更。
さらに、「特定の数に達したら終了」から「残り数を1つずつ減らし、ゼロになったら終了」へと変更。

for 文 と while 文の違いで速度に影響することはほぼありません。
比較の部分は「ゼロになったら終了」が速いとされてきました。というよりも、遅くならないと言い換えた方が適切かも。詳しくは結果検証のところで・・・

次に 64ビット幅、8バイトずつ転送する案を考えてみる。

DWORD64 *pdwd , *pdws;
pdws = (DWORD64 *)(lpTemp[2]);
pdwd = (DWORD64 *)(lpTemp[3]);
SIZE_T j;
j = (__SIZE_DUPLICATE / sizeof(DWORD64) );
while(--j){
    *pdwd++ = *pdws++;
}

32ビット幅ずつ転送するのに比べ異なる点は 、DWORD32 から DWORD64 への変更。
一度に読み書きできる幅が2倍になった分、読み書き作業の総数も半分となる。繰り返す回数は __SIZE_DUPLICATE として定義した数の1/8。

なお、筆者の環境は64ビット版の Windows 7 である。
32ビット版の OS 上で64ビットコードを転送するコードは返って速度向上は期待できない。なぜならば、32ビット環境では汎用レジスタが4バイト分であり、2回転送するようなコードが生成される可能性が高い。
実際に出力されたアセンブリ言語のファイルで確認するのが手っ取り早い。アセンブリファイルを出力する方法については車輪の再発明 (7)で触れた。

まず、64ビット版 のプラットフォーム向けに出力されたアセンブリファイルのコードは以下の通り。



64ビット幅を持つ1本のレジスタへ読み込み、そのレジスタの値を書き出す。

続いて32ビット版 のプラットフォーム向けに出力されたアセンブリファイル。



32ビット幅を持つ1本のレジスタへ読み込み、そのレジスタの値を書き出す。それを2回行った後ポインタの加算、残りがゼロか比較している。
やはり2回転送するようなコードが生成された・・・

32ビット幅で2度の転送を避けられないならば以下のように翻訳されるならば結果が良くなりそうだ。



32ビット幅を持つ別々の2本のレジスタへ読み込み、ポインタの加算、そのレジスタの値を書き出す。
どのようにC/C++ 言語のコードを組み変えたのか気になるヒトもいるでしょう。64ビット幅のデータ転送が最終目的ならじっくり扱いところですが、あしからず・・・

C/C++ 言語で SSE2 を使わない転送の話はここまで・・・の予定でしたが少々脱線・・・
条件は Intel 製 CPU 、アセンブリ言語、32ビット環境に限られるが、もっと手短かに転送する例として以下のように考えられる。
DWORD dwEcx , dwEsi , dwEdi;
dwEcx = __SIZE_DUPLICATE / sizeof(DWORD);
dwEsi = (DWORD)(lpTemp[2]);
dwEdi = (DWORD)(lpTemp[3]);
__asm{
    mov ecx , dwEcx
    mov esi , dwEsi
    mov edi , dwEdi
    rep movsd
}

上記の例において、__asm{ ~~ }のブロックでインラインアセンブラを使っている。
64ビット環境を前提に C/C++ 言語 で組む場合、インラインアセンブラが廃止されたことによりアセンブリ言語は利用できない。厳密に言えば、Visual Studio でもアセンブリ言語を扱えるのだが、利用の際は厳しい制限がある。
ほかにも、64ビット環境とインラインアセンブラの関係だけでなく最適化が効かない、致命的なエラーを発生させ易いなど気をつけるべき点もあり、「お手軽」とは言えない。

現在、MOVSD は複数の意味があり、ここでは Move data from String to string 「ストリングの移動」を指す。実際には、基のデータを消去しない。よって、移動ではなく複写。
もうひとつは、SSE2 機能を搭載したプロセッサで Move Scalar Double 、「倍精度浮動小数点値 の移動」を指す。
前者の MOVSD は、 ESI の指す番地にあるデータを4バイト読み出し(ロード)、EDI の指す番地に書き込む(ストア)。その後、ESI 、EDI 両レジスタに格納されている値は4加算。
ただし、ステータスレジスタ、Intel 製 CPU では EFLAGS レジスタのディレクションフラグ が 1に設定されているならば、ESI 、EDI 両レジスタともに4減算。

MOVSD の前に REP が付くのは Repeat の略で、「繰り返し」を指す。「ECX の値がゼロになるまで繰り返す」という意味になる。
C/C++ 言語において、for 文や while 文を用いて「残り数がゼロになるまで繰り返す」のと同じようなものだ。

「ストリングの移動」を指す MOVSD 命令は古くから搭載されており、速くないとされてきた。
筆者の個人的な見解かもしれないが「速くない」という表現より、「転送の条件によっては遅れが生じ易い」と言い換えた方が実際の状況に近い。

さてさて、 C/C++ 言語で SSE2 を使った例・・・と続けたいところですが・・・

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

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

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

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

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

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



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


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


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

今回は実質車輪の再発明 (6)の続き、「速度向上を目指し、データ転送部分を加工してゆく」に向けたお話。

一次資料を入手
この先、特定のハードウェア、機構に偏った部分、少々難しい話が出てくる。この類の分野に興味があるヒトは以下に挙げる資料を入手すると良いだろう。
たまに 64ビットや SSE/SSE2/SSE4/AVX などの情報を求めて検索サイト等を右往左往しているヒトもいるようで時間がモッタイナイ。転載や孫引きによる断片的な情報を辿るよりも原書を漁るのが近道となる。

日本語版に翻訳された資料を探すための検索用リンクを以下に貼る。
インテル 、日本語技術資料への検索リンク

インテルの日本語圏向け公式サイトにPDF ファイル形式にまとめられた文書が公開されているハズ。
ちなみに、コンピュータの発祥地はアメリカのペンシルベニア州。カリフォルニア州バークレーという説もあるが、いずれにせよ英語圏で牽引されて発展してきた。当然のことながら原文は英語版なので、文書の日本語訳や公開が遅れる可能性もある。

この中でタイトルに
IA-32 インテル アーキテクチャ ソフトウェア・デベロッパーズ・マニュアルと付いている上中下4巻が有益である。これらのファイルをダウンロードしておき活用すれば捗ることだろう。
PC に搭載されている Intel 製 CPU の共通事項や1970年代後半に登場した CPU から 第二世代の Pentium 4 に至るまで進化の流れを把握することができるだろう。中巻A と 中巻B の命令セットリファレンスにはアセンブリ言語を主軸にした解説となっているが、「同等のC/C++ コンパイラ組み込み関数」が併記されており、C/C++ 言語 で SSE/SSE2 を使いたい時には便利。
筆者の手もとにある資料の日付は2004 年となっている。2004 年頃は、Windows 上で動作するアプリを作るといえば 32ビットが暗黙の了解であった。その後拡張が施された、64ビットへの知識を補うならば
インテル エクステンデッド・メモリ64 テクノロジ・ソフトウェア・デベロッパーズ・ガイドの2巻分も参考になる。この2巻は32ビット版の解説と異なり、「同等のC/C++ コンパイラ組み込み関数」は併記されていない。

「エクステンデッド・メモリ64 テクノロジ」と呼ぶと長いので、略して「EM64T」もしくは「Intel64」と呼ばれている。



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

これを書いた時点で 64ビット版の OS が浸透しつつあるが、ここに至るまで紆余曲折があった。
おおざっぱに言えば、新築と増改築との違いを想像すると判りやすい。
何らか新しい局面へ移るとき、「ゼロから作り出す」 ( = フルスクラッチ) もしくは「既存のモノゴトを拡張する」の2通りに分けて考えるのが常である。

Intel 製 CPU の発展を遡ってみるとフルスクラッチ、つまり新築に該当する 32ビット CPU は 1980年代前半に発表されていた。あまり普及しなかった。その原因として「すでに浸透していた 16ビット CPU よりも性能が低い」点などが指摘されていた。

一方、1980年代後期、Intel の 32ビット CPU を搭載した PC が登場し普及した。この時採用された CPU は 80386。略して i386 や 386 と記されることもある。80386 は昨今の一般的な PC に搭載されている Core i7 や Pentium の源流である。
既存の 16ビット CPU で採用されていた命令体系をベースに追加、拡張が施され、互換性が保たれた。それまでの16ビット CPU を増改築したと言えよう。

普及する・しないを左右するのは、コストや性能といった観点だけではない。ユーザ側から観て、使い勝手の良し悪しも重要だ。
フルスクラッチの場合、既存のモノゴトに頼ることはできない。ユーザ側からすれば、ある程度状況が揃うまで待つことになる。
一方、従来からの互換性が保たれるとすれば、状況が揃うまで待つ必要はない。具体的に言えば、32ビット版のアプリが出揃うまで待つのではなく、出揃うまでの間は既存の16ビット版のアプリを使う選択肢も残されていた。
フルスクラッチの場合、以前と異なる点に直面する。過去記事アリさん100匹を観て想う-1、にて
「急激な変化だとついていけないと感じてしまう」と述べた。個人差はあれど新しいモノゴトに馴染むまで時間を要したり、全く馴染めないヒトも出てくる・・・

32ビット化された CPU が浸透した頃、次世代の64ビット CPU への模索が始まっていた。
Intel は 64ビットの命令体系として「IA-64」と呼ばれる命令体系を発表した。こちらはフルスクラッチ、つまり新築なので従来からの「IA-32」と互換性はない。
2014年 1月25日掲載分の記事で触れた Itanium シリーズが現時点で「IA-64」命令体系を実行できる CPU である。Itanium シリーズはデータセンターや大規模な科学技術計算用に向けた設計となっており、一般的な職場や家庭で使うような PC には搭載されていない。

「IA-64」を新築と例えるならば、Intel 製の CPU の互換プロセッサを作ってきた ( 競合プロセッサメーカー ) AMD から発表された「AMD64」と呼ばれる命令体系が「IA-32」に対する増改築と言える。「AMD64」は既存の 32ビット CPU で採用されていた命令体系をベースに追加、拡張が施され、扱えるレジスタ数などが強化された。もちろん互換性も考慮されている。
互換性うんぬんという表現では判り辛いかもしれない。かつて、Windows 95 など 32ビット版の OS で 16ビット CPU 用のアプリが動いたように、「64ビット版の OS でも従来からの 32ビット用アプリを動かすことができる」点は互換性が保たれていると考えてよい。
従来からの互換性が保たれる点は利用者にとって有利なだけでなく、アプリを組む側にとっても負担が少ない。それを象徴するように、OS を配給する Microsoft は Intel の「IA-64」ではなく、AMD の 「AMD64」を支持した、というよりも、積極的ではなかった。64ビット版の Windows XP が登場した際、「IA-64」用、つまり Itanium 用が流通していたが、Windows Vista 以降 Itanium シリーズはサポート外とされた。

結局、Intel は AMD が先に発表した「AMD64」の互換という後追いのような形で「EM64T」を発表した。80386 の流れをくむ 既存の32ビット CPU に「EM64T」命令セットを追加する形で 64ビット CPU への拡張が施された。2006年頃に登場したCore 2 Quad や Core 2 Duo の登場を境に「EM64T」は「Intel64」と呼ばれるようになった。

細かく見てゆくと「AMD64」と「EM64T」「Intel64」は若干の違いがある。これを書いた時点では「AMD64」や「Intel64」の総称として「x64」という呼び方が使われている。

※ Itanium にはエミュレーションモードが装備されており、従来の32ビット CPU 用のコードを実行することもできる。しかし、エミュレーションゆえの遅さは否めない。

SSE 命令を使う前に
「速度向上を目指し、データ転送部分を加工してゆく」に向けたお話に戻ります。

古典的な メモリ - メモリ間の転送とSSE2 を用いた場合を比べたいワケです。
後で出てくるSSE2 を使う場合に頭に入れておきたい点があるので先に述べておきます。
コピー元とコピー先、転送元と転送先を16 の倍数に整えておく。

Intel 製 CPU で SSE2 命令を用いるとして、XMM レジスタへのレジスタに読み込み( = ロード)、XMM レジスタ からメモリへの書き込み ( = ストア) を行う際
「アドレスは 16 バイトにアライメントが合っていなければなりません。」
等の注意事項がある。
※ アライメントをアラインメントと記す場合もある。
※ ほかの ( Intel 製 ではない ) プロセッサでも、特定の数の倍数でアクセスせねばならないモノもある。
※ AVX やそれ以降の拡張された機能では32バイトに合わせる

アドレスとは番地とも呼ぶが、データを読み出す場所、書き込む場所を指す。C/C++ 言語でいえばポインタの値、配列の場合はインデックス数を足した値が16 の倍数であるか否かということだ。
16 の倍数でないアドレスにあるデータをXMM レジスタにロードもしくはストアした場合は致命的なエラーとなって、アプリの実行が中断されてしまう。異常終了 ( ABnormal END ) は避けたいものだ・・・

「SSE や AVX など後から拡張された読み書き命令を使いたい場合、全て16 の倍数や32 の倍数に揃える必要があるのか???」
たしかに手間がかかりそう。しかし、それを避ける方法もある。
C/C++ 言語で書くとしてコンパイラ組み込み関数を用いるならば、SSE2 レジスタへの整数値ロードとストアは
_mm_load_si128
_mm_store_si128を用いる。これは「16 バイトにアライメント ~~」が必要な命令。
ところで、これらの関数名を見て戸惑ってしまうのも然り。組み込み関数の名前を分けて考えると理解しやすい。
接頭の「_mm」は SSE や MMXを使う際にお決まり、接尾の「_si128」はデータ型、この例では128bit幅の整数値データを扱う意味。接頭・接尾の語を外せば「_load」と「_store」が残る。
※ 実際には 128ビット幅の整数値の演算よりも、64ビット幅の整数値2個分、32ビット幅の整数値 4個分の演算を同時に行うためのロードやその結果をストアする。
それらの関数名に対し、「U」もしくは「u」が付いている命令が存在する。
_mm_loadu_si128
_mm_storeu_si128
先に挙げた2つの関数名と似ている。



よく比べてみよう。接尾の「_si128」の前に「u」が付いている点に気がついただろうか。



「_load」と「_store」の後ろに「u」を付けた組み込み関数を用いれば「~~ アライメントが16 バイト ~~」に気を配らなくとも良い。
冒頭で紹介した、IA-32 インテル アーキテクチャソフトウェア・デベロッパーズ・マニュアルが手元にあるならば 中巻Aの命令リファレンスより MOVDQA と MOVDQU の項目を参照。



「~~ アライメントが16 バイトに合っていなくても、一般保護例外(#GP)は発生しない。」と記載されている。
ならば、「ロード命令は全て _mm_loadu_si128 を使うと良いのでは!?」
との声も聞こえてきそう。そのご指摘はごもっとも。
SSE2 が搭載された当初、_mm_loadu_si128 など、後ろに「u」を付けた組み込み関数は遅いとされていた。それを補うように、SSE2 から SSE3 へ拡張される際、_mm_lddqu_si128 が追加された。これにより、「ほとんどの場合 movdqu よりもパフォーマンスが向上します」と説明されている。
筆者がこれを書いている時点での環境は「第 3世代 Intel Coreプロセッサー」( 通称 Ivy Bridge )。その環境において「u」を付けた組み込み関数で極端な速度低下はみられない。数年前より、この速度低下は改善されたようである。
「全て _mm_loadu_si128 を使うと良いのでは!?」と問われたのがSSE2 が搭載されて間もない頃であれば、答えはノー。しかし、速度低下というデメリットが改善された現時点では否定する理由はない。

車輪の再発明 (6)で VirtualAlloc 関数や HeapAlloc 関数を用いてメモリ空間を確保する例を挙げた。
VirtualAlloc 関数を用いた際に得られるアドレスは 64KB 、つまり 65536 バイトの倍数。2の16乗、2掛ける2掛ける2掛ける ~~ 掛ける2を 16回繰り返せば 65536。
16 は2の4乗、「2掛ける ~~」を 4回。VirtualAlloc 関数の仕組みとして 64KB 境界へ丸められるとされていることからアドレスを16で割って余りはゼロ。よって、「16 バイトにアライメント ~~」の条件を満たす。
一方、HeapAlloc 関数や他の関数ではアライメントを意識したメモリ割り当ては難しい。
※ Visual Studio ならば 2005 以降のバージョンで対応した _aligned_malloc 関数、ほかの開発環境では _mm_malloc 関数などを用いて特定のバイト数に整ったアドレスを割り当てることも可能。

アライメントや境界うんぬんの部分を自前でなんとかしてみよう。車輪の再発明 (6)で取り上げたコードの続きとして進めてゆく。

まず、16 バイトに合わせた ( 修正した ) 値を保持するための修正。
LPVOID lpTemp[2] = { NULL , NULL };

となっていた部分を
LPVOID lpTemp[4] = { NULL , NULL , NULL , NULL};

と変更する。lpTemp[0] と lpTemp[1] には VirtualAlloc 関数 や HeapAlloc 関数で確保したポインタ値、すなわちアドレスが格納されるようになっていた。lpTemp[0] と lpTemp[1] の値を16 バイトの倍数に合わせ、その値を lpTemp[2] と lpTemp[3] に格納する。
「16 バイトの倍数に合わせた値をlpTemp[0] と lpTemp[1] に直接格納しても良いのでは!?」と考えるヒトがいるかもしれない。lpTemp[0] と lpTemp[1] の値を変更しない・温存する理由は、VirtualAlloc に対する解放として VirtualFree 関数を用いるが、VirtualAlloc で得られた値と異なる値を渡すことを防ぐためである。
詳しくはMSDN ライブラリ、VirtualFree 関数の説明をご覧いただくとして、その説明の中に
「~~ this parameter must be the base address returned by the VirtualAlloc function when the region of pages is reserved.」
「~~ 領域を予約したときに VirtualAlloc 関数が返したベースアドレスを指定しなければなりません。」
といった文言がある。

16バイトに揃えるために
#if defined(WIN64) || defined(_WIN64)
#define BFMASK_PTRALIGN 0xffffffff00000000
#else
#define BFMASK_PTRALIGN 0x00000000
#endif // defined(WIN64) || defined(_WIN64)

#define MACRO_ALIGN_PTR(TP,PD,PS,OFFSET) {\
UINT_PTR uPtr = (UINT_PTR)(PS);\
if (uPtr & (OFFSET-1) ){uPtr += OFFSET;uPtr &= (BFMASK_PTRALIGN | (0xffffffff - (OFFSET-1) ) );}\
PD=(TP)(uPtr);}

この2つのマクロ定義を追加。追加する場所は冒頭、
#define MAX_LOADSTRING 100
の後あたりに追加するのが良いだろう。#define ディレクティブが複数行に連なるとき、末端の¥ ( 半角の円記号 ) を忘れないように注意。

このマクロ使い方の例は
MACRO_ALIGN_PTR(LPVOID , lpTemp[2] , lpTemp[0] , 16);

のようになる。追加する場所は VirtualAlloc 関数 や HeapAlloc 関数 が成功した後 ~ 初回の timeGetTime 関数を呼び出す前周りが良いだろう。

マクロが展開されて以下のように変換される。
{
    UINT_PTR uPtr = (UINT_PTR)(lpTemp[0]);
    if (uPtr & (16 - 1) ){
        uPtr += 16;
        uPtr &= (BFMASK_PTRALIGN | (0xffffffff - (16 - 1) ) );
    }
     lpTemp[2] = (LPVOID) ( uPtr );
}

この部分で行っているのは、
・ポインタ値が16の倍数かどうか比べる。
・16の倍数でないならば、16を足し、16で割った余りを取り除く。
uPtr +=uPtr &=の順序を逆にしたほうが伝わりやすいのかも。
今回は使う箇所が2箇所と少ないためマクロ定義を選んだ。使う箇所が多い場合はひとつの関数として設けたほうが楽。

割り当てられたアドレスが 39 だったとしよう。39は16の倍数ではない。これに近い16の倍数は 32 や 48。望ましいアドレスの値は 48。
まず uPtr += の部分で 39 に 16を足し、55となる。 uPtr &= の部分で 55 を 16で割った余り を取り除き 48となる。

55 は 日常生活で普通につかう 10進数。32 ビット幅の16進数で表現すれば0x00000037。
これを AND演算 を用いて32 ビット幅の16進数 0xfffffff0 でマスク、つまり、ふるいにかけることで16で割った余りを取り除ける。結果は16進数 で0x00000030となり 10進数 で表現すると 48。

なぜ、この方法で16で割った余りを取り除くことができるのか解せないヒトもいるだろう。
「0」と「1」の組み合わせによるAND演算を図で現すと以下のようになる。


1段目はもとの値、2段目がマスク、ふるい、3段目がAND演算した結果。
「0」を「いいえ」、「1」を「はい」と置き換えると理解しやすいかも。2つとも「はい」なら結果も「はい」。どちらか一方、もしくは両方「いいえ」ならば結果も「いいえ」つまり「0」となる
(16 - 1)を2進数「0」と「1」の組み合わせで表現すれば1111。8+4+2+1。16進数では 0xf。
AND演算でマスクした値の右端を注目、10進数 で15、16進数で f にあたる部分がゼロ。
もとになる値、2進数で下4桁分が 0 から 15 のどれであろうとマスクする値の下4桁分が 0なので、得られる結果も2進数で下4桁分は 0となる。
この手法は16 が2の4乗、2掛ける2掛ける2掛ける2であることから成り立っている。ほかにも 32 や 64 や 4096 といった2のN乗に該当する数値の場合に使える。
丁寧に進めたいならば、2のN乗に該当しない数値でも対応できるように16で割った余りを引き、その値に16を足すようなコードを書くべきだろう。なお、ここは速さを求める部分ではない。過去記事除算が遅い遅くない除算で取り上げたように、「遅くなる要因をなるべく排除したい」という観点から割り算以外の方法も選択しうるという例だ。常日頃それに適したほかの手段が存在するか否かを探すことは後々の糧となる。

BFMASK_PTRALIGN は 64ビットを対象とした場合、64ビット幅の上位32ビット分の情報を損失しないためのマスク。

ほか、UINT_PTR の部分に違和感を感じるヒトがいるかもしれない。
UINT は 「符号なし整数」を意味する。C/C++ 言語の入門書等に載っている unsigned int と同意。「_PTR」の付く整数型は 「32 ビット版の Windows と 64 ビット版の Windows の両方のポインタに合わせてサイズが変更される整数型」との説明がある。「_PTR」の付く整数型を使わずに作るとなると、プラットフォームが変わる毎に UINT や unsigned __int64 へと修正する手間が増える。

ポインタ値を符号なし整数として扱い演算し、演算結果をポインタへ戻す。丁寧に進めるならば、ポインタを整数値に変換する部分などは型変換として、reinterpret_cast を扱うべきなのだろう。
型変換について載せている解説系のサイト等を眺めると「キャストの危険性」を唱えている。ハードウェアを意識しない言語で組むヒトほど顕著なのだそうだ。
ここでは一般的な PC に搭載されている Intel 製 CPU を前提にしているのに加え、ビット幅が変わる変換ではない。よって、「キャストの危険性」は気にせずに書いてみた。
アセンブリ言語を扱うようなレベル、機械側に寄った見方で言ってしまえば、ポインタも「_PTR」の付く整数型も同じ数値。そもそも、ポインタや整数値といった概念は人間が判断しやすいように分けて使っているのであって、CPU 自身には判断できないものだ。
とはいえ、「キャストの危険性」を否定する気はない。実際にビット幅が異なる型変換はトラブルのもと。小さい型への切り詰めは情報が失われる恐れがあるので注意が必要だ。
情報が失われるという表現では飲み込み辛いヒトもいるだろう。
旅館を例にあげてみよう。以前からの本館に加え、最近新館(2番目の館)を増設したとする。
「この荷物を新館の3階の2号室に届けて」と伝えるべきところを、
「この荷物を3階の2号室に届けて」と伝えたのでは誤が生じることだろう。ここで情報が失われたのは、「本館なのか新館なのか?」という点。
たしかに、人間であれば前後の文脈や雰囲気から、届ける先が本館なのか新館なのかを判断できるかもしれない。しかし、機械は生真面目ゆえに融通が効かない・・・

これで一安心・・・ではなく・・・ほかにも重要な修正箇所があるので忘れないようにしたい。
16の倍数にポインタ、アドレスの値を修正するということは、16バイト未満の範囲で 大きくなることが想定される。よって、メモリ空間を確保する際に領域のサイズ ( 割り当てたいバイト数 ) に余白分を追加する修正が必要。
もしくは、修正済みアドレスから修正前のアドレス値を引き、その値を有効領域と変更する案もあるが面倒。

車輪の再発明 (6)で載せたコードではメモリ空間を確保する際に、余白なしでサイズ指定した。
修正が必要な箇所は
if ((lpTemp[0] = ::VirtualAlloc(NULL , __SIZE_DUPLICATE , MEM_COMMIT , PAGE_READWRITE) ) == NULL){
もしくは
if ((lpTemp[0] = (LPVOID) HeapAlloc(hHeap , HEAP_ZERO_MEMORY , __SIZE_DUPLICATE) ) != NULL){

のような部分。
最大で15 バイト分はみ出す可能性がある。余白として増やすべきサイズは 16。安全のため 32でも良いだろう。後で触れることに絡み余白を 256 と設定してみる。~~ Alloc 関数の引数
, __SIZE_DUPLICATE
となっている部分を
, (__SIZE_DUPLICATE + 256)
のように修正することで、確保したメモリ領域をはみ出すようなアスセスを防げる。

さてさて、 CopyMemory 関数 を書き換えてみる・・・と続けたいところですが・・・

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

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

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

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

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

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

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

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

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

最新記事
カレンダー
05 | 2017/06 | 07
- - - - 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 -
カテゴリ
ランキング
いつも応援いただきありがとうございました。ただいま休養中につきランキングへ参加していません・・・

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

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

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