サブルーチンからの任意の配列の参照の仕No.09715
fzok4234 さん 22/01/13 14:42
 
こんにちは。毎度お世話になっております。


さて、サブルーチンの書き方についてですが、任意の配列変数を参照するような処理
の書き方が
分からず困っています。


現状ではサブルーチンのパラメーターに、グローバル変数か呼び出し元の親サブルー
チンの
ローカル変数かを問わず配列変数を指定しても、

$$foo = $$1[ ##index ] ;

といった読み取り操作や

##2[ ##index ] = ##bar ;

といった書き込み操作が不可となっています。できることはグローバル配列変数を名
指しして
呼び出すことぐらいです。

このため、当方で問題となっていることが 2 つあります。


1 つ目は、任意の配列変数に対してソートや 2 分探索などの基本的な操作を行った
り、配列変数を
キューやスタックなどの基本的なデータ構造として活用する、といった共通処理を行
うサブルーチンの
作成が困難になっていることです。

現状では、あるサブルーチンのローカル配列変数に対してソートなどの処理が必要に
なったときは、
そのサブルーチンの中に処理のコードを直接実装しないといけません。
これだと複数のサブルーチンで同じような配列操作が必要なときに、そのサブルーチ
ン全てに同じコードを
重複して書かないといけなくなって、コードが肥大化したり管理が大変になったりし
ています。


2 つ目は、複数の値をまとめてサブルーチンの引数や戻り値としてやり取りし辛いこ
とです。
マクロの規模が大きくなってくると、関連しあった切り離すことのできない複数の値
のまとまりがどうしても
発生します。例えば、特定の文字列範囲を表す

{ 開始行 , 開始桁 , 終了行 , 終了桁 }

の 4 個の数値とかです。ここで、この複数の値をまとめて 1 つのオブジェクトとし
て扱うためのクラスや
構造体の代用として配列を使用する必要があります。しかし、現状ではこの「疑似構
造体」としての配列を
サブルーチンの引数にも戻り値にもできず、結局バラバラのスカラー変数としてやり
取りしないといけません。
しかも、現状のサブルーチンのパラメーターは 9 個まで、戻り値は 1 個だけという
制約があるため、
グローバル変数を媒介しての入出力が必要になっている状況です。


いずれのケースでも解決のために C# で DLL を作るなど、結局外部の開発 / 実行環
境に頼らざるを得なく
なっているのが現状です。せめてアルゴリズムやデータ構造の入門書に載っているよ
うな処理の実装ぐらいは
マクロ単体で行えるようになりたいところです。また、作成したマクロの保守管理の
負担を軽減できるよう、
コードのスパゲッティ化は極力避けたいです。

できればパラメーターや戻り値で配列の参照を直接扱えるようにする機能改善を要望
したいところですが、
かなり大規模な改修が必要になりそうなのであまり期待できません。そこで、このよ
うな機能改善が無くても
グローバル配列 / ローカル配列を問わずサブルーチンの外部の配列変数を、できれ
ば配列丸ごとのコピー無しで
参照渡しで操作するための記述方法を提案してもらえれば助かります。

どうかよろしくお願いします。



[ ]
RE:09715 サブルーチンからの任意の配列のNo.09716
秀丸担当 さん 22/01/13 16:43
 
確かにサブルーチンでは配列など、参照での受け渡しはできないです。
グルーバル変数だったり、何らかの別の手段でやるしかないです。
文字列としてマクロを書いて、evalするとできるかもしれないですが、無理があると
思います。
マクロの文法でやるとどうしても綺麗ではなくなってしまうかもしれないですが、何
かうまくできる方法が無いか考えてみます。

[ ]
RE:09715 サブルーチンからの任意の配列のNo.09717
IKKI さん 22/01/14 02:55
 
こんにちは。ユーザーのIKKIです。

理想的にどうあるべきかという話は棚に上げて、現状でどうすればよいかというワー
クアラウンドの話をします。

そもそも、多数の変数が必要な場合、可能な限りマクロ変数を使わないことをおすす
めします。
秀丸マクロの変数は数が多くなるほどアクセスが遅くなります。
http://mobitan.org/hm/memo/130428/
↑これは9年前の測定結果ですが、いま測っても同じ傾向です。
手元の Core i5-7500T + 秀丸v9.12β2 で10000個の数値変数を定義したところ、100
00番目の変数を読んで書くのに0.13ミリ秒かかりました。
キューやスタックなどの基本的なデータ構造にマクロ変数を用いることがパフォーマ
ンス的に許容できるかどうかを実データで検証するべきです。
小さなテストデータは問題なく処理できても、実運用で大きなデータを食わせたら全
然処理が進まない、という可能性があります。

このことを踏まえて、ご質問の趣旨とは違ってしまうかもしれませんが、いくつかの
ワークアラウンドを提案します。

>1 つ目は、任意の配列変数に対してソートや 2 分探索などの基本的な操作を行った
>り、配列変数を
>キューやスタックなどの基本的なデータ構造として活用する、といった共通処理を
>行うサブルーチンの
>作成が困難になっていることです。

これは外部 DLL に頼るのが結局正解と思われます。
私が作るマクロでは、あべのりさん作 macrodll.dll を便利に使わせてもらっていま
す。ハッシュを使えば配列相当のことができます。
https://www.ms.u-tokyo.ac.jp/~abenori/soft/hidemaru/dllfunc.html
あるいは、こみやんまさん作の各種コンポーネントを使って、主な処理を他の言語で
書いてしまう手もありそうです。
http://秀丸マクロ.net/

>2 つ目は、複数の値をまとめてサブルーチンの引数や戻り値としてやり取りし辛い
>ことです。

これは文字列にエンコードしてしまうのが早いし速いです。例えばこのように。

call Hoge;
##_ = split($$a, $$return, "/");
message str(val($$a[0])) + ", " + str(val($$a[1])) + ", " + str(val($$a[2]))
 + ", " + str(val($$a[3]));
endmacro;

Hoge:
##lineno1 = 12;
##column1 = 34;
##lineno2 = 56;
##column2 = 78;
return sprintf("%i/%i/%i/%i", ##lineno1, ##column1, ##lineno2, ##column2);

以上、いずれも
> 参照渡しで操作するための記述方法を提案してもらえれば助かります。
というご質問の趣旨に反していることは承知の上で、現状で可能なワークアラウンド
を提案させていただきました。
ご参考になれば幸いです。

[ ]
RE:09716 サブルーチンからの任意の配列のNo.09718
秀丸担当 さん 22/01/14 11:00
 
やり方を検討しているのですが、既存のcallの文法を維持したまま、参照もできるよ
うに記述方法を追加するのはいまいちのような気がしてきました。
別の文で、例えばrefcallとかcall_by_refといった新たな文にして、とにかくパラ
メータは全部参照ということにすると、文法はそのままにできてシンプルでいいかも
しれないです。
例:
refcall Sub $a;
endmacro;
Sub:
$$temp=$$1;//$aを読む
$$1="x";//$aに書く
return;

こうするとしても、どのみち内部的な変更があるので、実際やってみると問題点があ
るかもしれないですが、こういう方向で検討してみようと思います。

[ ]
RE:09717 サブルーチンからの任意の配列のNo.09719
秀丸担当 さん 22/01/14 11:32
 
いろいろ詳しいテクニックの情報ありがとうございます。
変数は、確かに定義数が多いと、それだけで後から定義する変数のアクセスは遅かっ
たです。
V8.89〜V8.92あたりで一部改善している部分もあったのですが、定義数によって遅い
傾向があることには変わりありませんでした。
DLLを使うのが一番速いと思いますが、定義数が多いだけで遅いのはなんとかしたほ
うがいいと思うので、改善を検討しようと思います。

あと、split関数に対してjoin関数が無いので、join関数もやっておこうかと思います。

[ ]
RE:09717 サブルーチンからの任意の配列のNo.09720
fzok4234 さん 22/01/14 17:31
 
変数が多いときの実行速度低下に関する貴重な情報の提供ありがとうございます。


測定結果のグラフを拝見して正直驚きました。要素数 n に対する読み書き時間が完
全に O( n ) となっており、
ハッシュテーブルとしては最悪の結果です。

マクロ変数を管理する内部のハッシュテーブルをどのように実装したのかはこちらで
は分かりかねますが、一般に
高速なハッシュテーブルでは O( 1 ) となり、旧来からある C/C++ の標準関数の st
d::map でさえも O( Log( n ) ) と
なるようです。ちなみに、C/C++ の標準関数に最近追加された std::unordered_map
は平均で O( 1 ) となるが、
条件が悪いと最悪 O( n ) となるみたいです。また .Net の標準関数 System.Collec
tions.Generic.Dictionary< TKey , TValue > は
安定して O( 1 ) となるかなり高速な部類のようです。

おそらく、変数管理用の内部ハッシュテーブルが、
・完全に自社開発した「車輪の再発明」だが、既存の標準関数やサードパーティライ
ブラリよりも著しく品質の劣る
 「四角い車輪の再発明」を犯してしまった。
・何らかの粗悪なサードパーティライブラリを使用している。
・std::unordered_map を使用しているが何らかの最悪条件で O( n ) となっている。
・素直に std::map を使用しているものの、操作に必要なハッシュ値の生成方法が良
くない。
のいずれかにが該当しているのではないかと推察できます。

いずれにせよ、ユーザーが外部 DLL 作成に逃げる口実となっているため、サイトー
企画さんが変数管理の仕方を最悪条件でも
O( Log( n ) ) となるようなところを目標として改善するべきなのではと思う次第で
す。



[ ]
RE:09718 サブルーチンからの任意の配列のNo.09721
fzok4234 さん 22/01/14 17:34
 
わざわざ新関数の実装を検討していただきありがとうございます。
今後ともよろしくお願いいたします。


[ ]
RE:09720 サブルーチンからの任意の配列のNo.09722
でるもんたいいじま さん 22/01/14 19:48
 
こんにちは。秀丸ユーザー歴27年の「でるもんた・いいじま」です。
雑感です。

> 測定結果のグラフを拝見して正直驚きました。
> 要素数 n に対する読み書き時間が完全に O( n ) となっており、
> ハッシュテーブルとしては最悪の結果です。

あちゃ…そこまでひどかったですか。
ただこれは、30年くらい前に秀丸が最初に開発されたときの事情を考えれば「当時と
しては、変数名のハッシュテーブルがそこまで大きくなることは想定していなかっ
た」の一言に尽きるんでしょうね。たぶん当時は「テーブル全体のサイズが64KB以
内」という暗黙の制約下でコードを欠いていたのだと思います。

> おそらく、変数管理用の内部ハッシュテーブルが、
> ・完全に自社開発した「車輪の再発明」だが、既存の標準関数や
>  サードパーティライブラリよりも著しく品質の劣る
>  「四角い車輪の再発明」を犯してしまった。
> ・何らかの粗悪なサードパーティライブラリを使用している。
> ・std::unordered_map を使用しているが何らかの最悪条件で O( n ) となっている。
> ・素直に std::map を使用しているものの、操作に必要なハッシュ値の生成方法が
>良くない。
> のいずれかにが該当しているのではないかと推察できます。

std::unordered_mapという線はないです。秀丸の誕生はそれよりずっと前です。
std::mapも可能性はほぼゼロだと思います。秀丸が誕生したころの古いC++には「ラ
イブラリを名前空間に分けて管理する」という発想はまだなかったはずですし、今の
ような豊富な標準ライブラリも当然なかったはずです。

なので、90年代半ばの当時としては自前開発するしかなかったわけですが、たぶん変
数管理モジュールとマクロインタプリタ本体とのインターフェイスは
・変数名(と添え字)を渡すと、その変数の値(または値を格納したオブジェクト)
を返すメソッド
・変数名(と添え字)、設定すべき値(またはオブジェクト)を渡して、その変数に
値を代入するメソッド
のペアか、あるいは
・変数名(と添え字)を渡すと、その変数に対応するオブジェクトを返すメソッド
 ※必要に応じてこのメソッドで、それまで存在しなかった変数の生成も行う。
 ※変数の値の書き換えは、受け取ったオブジェクトに対するアクセスで行う。
のどちらかの形態になっているはずなので(前者のほうが個人的には扱いやすいかな
…)、変数管理のモジュールだけをうまく作り直せばパフォーマンスは大幅に改善す
ると思います。

> 一般に高速なハッシュテーブルでは O( 1 ) となり

これは検索文字列からハッシュ値を計算して(たとえばCRC16)、あらかじめ大きく
確保しておいた配列(CRC16なら64k個)の該当位置にその値を納める、という手法で
すね。万一ハッシュ値が競合したときのための対策はしておくものの、実際に競合が
発生する確率は非常に低い、と。

ただこれは、馬鹿正直にテーブルを検索するコストよりもハッシュ値を計算するコス
トのほうが安い、かつ、大きな配列を確保するコストも負担にならない、という前提
条件が必要です。
残念ながら、90年代前半のWin16環境はCPUもメモリもそこまで潤沢ではありませんで
した。Perl 4が本格的に普及したのがちょうどその時期で、当時は「配列の引数に文
字列を使えるのは画期的」というのが業界の共通認識でした。

> 旧来からある C/C++ の標準関数の std::map でさえも
> O( Log( n ) ) となるようです。

これは普通に木構造(B-treeあたり?)での実装ですかね。

> いずれにせよ、ユーザーが外部 DLL 作成に逃げる口実となっているため、
> サイトー企画さんが変数管理の仕方を最悪条件でも
> O( Log( n ) ) となるようなところを目標として
> 改善するべきなのではと思う次第です。

そうですね。実際問題として、そこまで秀丸を酷使するユーザーが少ないためになか
なか売上に結び付かないのと、今の内部構造次第では大々的なエンバグのリスクがあ
るのとで、すぐには踏み出せないと思います。
なので、たとえば当分の間は「トラブル対策」の中に「新方式のマクロ変数管理を使
用」みたいな項目を作ってヘビーユーザーさんに使いこんでもらって、不具合が出尽
くしたころを見計らって一般のユーザーでも新方式をデフォルトにする、みたいな
ロードマップが順当かなと思います。

[ ]
RE:09722 サブルーチンからの任意の配列のNo.09723
秀丸担当 さん 22/01/17 10:33
 
経緯としては、だいたいでるもんたいいじまさんの言われるような感じです。
最近のC++のライブラリを使うとしたら、ネックとなるのは、プロセス間で変数を共
有しているという点があります。
リスクは避けつつ、プロセスごとのハッシュのキャッシュとかして対策してみようか
と思います。

それか、setstaticvariableに第4の用途を作って、単一プロセス内だけで有効で、変
数と同じく他のマクロと競合なし、マクロ終了後には消える、というものを作るとす
れば比較的やりやすいです。

ちなみにプロセス間で共有のネックを軽減するための1つの方策として、
setcompatiblemode 0x08000000;
で他の秀丸エディタの切り替え禁止にする指定がありますが、これはexecmacroを繰
り返し呼ぶときに共有メモリを頻繁に確保しては解放するというオーバーヘッドを減
らす効果だけになっています。

[ ]
RE:09723 サブルーチンからの任意の配列のNo.09729
fzok4234 さん 22/01/21 15:09
 
v9.12β4 にて refcall 文の動作確認ができました。素早い対応ありがとうございま
す。


[ ]