mapとforeachのベンチマーク
趣旨
map
とforeach
のどちらが効率的かは昔から話題になっている- 可読性を考えたら
foreach
一択なんだけど、map
のほうが効率的な場合があるかもしれない- 変数を明示的に作る場合と、暗黙的に作る場合で何か違うかもしれない
- 確認してみよう
参考リンク
- 2019年 Writing faster Perl code • Wim Vanderbauwhede
- 2011年 Benchmarking in Perl: Map versus For Loop | End Point Dev
- perlperf
- Time Efficiency - Programming Perl
環境
デバイス
プロセッサ Intel(R) Core(TM) i7-8565U CPU @ 1.80GHz 1.99 GHz 実装 RAM 16.0 GB (15.8 GB 使用可能) システムの種類 64 ビット オペレーティング システム、x64 ベース プロセッサ
システム
エディション Windows 11 Pro バージョン 22H2 OS ビルド 22621.819 エクスペリエンス Windows Feature Experience Pack 1000.22636.1000.0
Perl ランタイム
perl --version This is perl 5, version 32, subversion 1 (v5.32.1) built for MSWin32-x64-multi-thread
結果
まとめ
- 配列の容量が1000以下では
foreach
が優勢 - 配列の容量が1000以上では
map
が優勢
容量が大きくなるとメモリの確保に時間がかかるだけのような気がする。
配列に要素を追加するケース
- 配列の容量が1000以下では
foreach
が優勢 - 配列の容量が1000以上では
map
が優勢
$ perl steph-skardal.pl #listsize=3 Benchmark: timing 10000 iterations of foreach, map... foreach: 0.0579259 wallclock secs ( 0.05 usr + 0.00 sys = 0.05 CPU) @ 212765.96/s (n=10000) (warning: too few iterations for a reliable count) map: 0.0469069 wallclock secs ( 0.03 usr + 0.00 sys = 0.03 CPU) @ 322580.65/s (n=10000) (warning: too few iterations for a reliable count) Rate foreach map foreach 212766/s -- -34% map 322581/s 52% -- #listsize=100 Benchmark: timing 10000 iterations of foreach, map... foreach: 1.04346 wallclock secs ( 1.01 usr + 0.00 sys = 1.01 CPU) @ 9852.22/s (n=10000) map: 0.977082 wallclock secs ( 0.92 usr + 0.00 sys = 0.92 CPU) @ 10857.76/s (n=10000) Rate foreach map foreach 9852/s -- -9% map 10858/s 10% -- #listsize=1000 Benchmark: timing 10000 iterations of foreach, map... foreach: 11.135 wallclock secs ( 9.31 usr + 0.00 sys = 9.31 CPU) @ 1073.88/s (n=10000) map: 10.9311 wallclock secs ( 9.63 usr + 0.00 sys = 9.63 CPU) @ 1038.96/s (n=10000) Rate map foreach map 1039/s -- -3% foreach 1074/s 3% -- #listsize=10000 Benchmark: timing 10000 iterations of foreach, map... foreach: 152.767 wallclock secs (108.44 usr + 1.19 sys = 109.62 CPU) @ 91.22/s (n=10000) map: 138.602 wallclock secs (123.38 usr + 0.45 sys = 123.83 CPU) @ 80.76/s (n=10000) Rate map foreach map 80.8/s -- -11% foreach 91.2/s 13% --
配列の要素を変更するケース
- 配列の容量が1000以下では
for-index/foreach
が優勢 - 配列の容量が1000以上では
map
が優勢
$ perl wim-vanderbauwhede.pl #listsize=3 Benchmark: timing 10000 iterations of for-index, foreach, map... for-index: 0.0232739 wallclock secs ( 0.02 usr + 0.00 sys = 0.02 CPU) @ 625000.00/s (n=10000) (warning: too few iterations for a reliable count) foreach: 0.023376 wallclock secs ( 0.02 usr + 0.00 sys = 0.02 CPU) @ 625000.00/s (n=10000) (warning: too few iterations for a reliable count) map: 0.0108192 wallclock secs ( 0.00 usr + 0.00 sys = 0.00 CPU) (warning: too few iterations for a reliable count) Rate for-index foreach map for-index 625000/s -- 0% -100% foreach 625000/s 0% -- -100% map 10000000000000000000/s 1600000000000000% 1600000000000000% -- #listsize=100 Benchmark: timing 10000 iterations of for-index, foreach, map... for-index: 0.219677 wallclock secs ( 0.17 usr + 0.00 sys = 0.17 CPU) @ 58139.53/s (n=10000) (warning: too few iterations for a reliable count) foreach: 0.194073 wallclock secs ( 0.17 usr + 0.00 sys = 0.17 CPU) @ 58479.53/s (n=10000) (warning: too few iterations for a reliable count) map: 0.145457 wallclock secs ( 0.14 usr + 0.00 sys = 0.14 CPU) @ 71428.57/s (n=10000) (warning: too few iterations for a reliable count) Rate for-index foreach map for-index 58140/s -- -1% -19% foreach 58480/s 1% -- -18% map 71429/s 23% 22% -- #listsize=1000 Benchmark: timing 10000 iterations of for-index, foreach, map... for-index: 1.72424 wallclock secs ( 1.59 usr + 0.00 sys = 1.59 CPU) @ 6277.46/s (n=10000) foreach: 1.48833 wallclock secs ( 1.44 usr + 0.00 sys = 1.44 CPU) @ 6958.94/s (n=10000) map: 1.74567 wallclock secs ( 1.47 usr + 0.00 sys = 1.47 CPU) @ 6807.35/s (n=10000) Rate for-index map foreach for-index 6277/s -- -8% -10% map 6807/s 8% -- -2% foreach 6959/s 11% 2% -- #listsize=10000 Benchmark: timing 10000 iterations of for-index, foreach, map... for-index: 22.525 wallclock secs (16.39 usr + 0.03 sys = 16.42 CPU) @ 608.94/s (n=10000) foreach: 20.2237 wallclock secs (15.06 usr + 0.00 sys = 15.06 CPU) @ 663.88/s (n=10000) map: 18.1616 wallclock secs (16.62 usr + 0.00 sys = 16.62 CPU) @ 601.50/s (n=10000) Rate map for-index foreach map 602/s -- -1% -9% for-index 609/s 1% -- -8% foreach 664/s 10% 9% --
コードレビューでありそうなやりとり
この画像はBackground Image Generatorで生成したものです
この文章の目的
- 放置していたテキストを発掘してるだけ
- 具体的なことは何も覚えてない
- たぶん他の人がコードレビューしている様子を見て、自分ならこうかな、と書いていたんだと思う
- 昔の自分はちゃんとフィードバック出来ていたのかは気がかり
コードレビューをさせていただく中で「こうしたほうがいいと思うよ」という内容がいくつかあったのでまとめてみます。
コードレビューの雑なまとめ : シェルスクリプト(主に bash)
テキストファイルを一行ずつ処理するようなやつ
提案されたコードはこちら。
- 全行処理するために行数が欲しい
- それぞれの行はカンマ区切り文字列を分解したい
# $1 はファイル名 row_num=$(cat $1 | wc -l) for ((i=1 ; i<=${row_num} ; i++)); do row=$(sed -n $((i))P $1); arr=(`echo $row | tr -s ',' ' '`); from=${arr[0]}; to=${arr[1]}; # ... いろいろ続いていく done
修正の提案はこちら。
- 行単位で繰り返すだけならわざわざ
sed
を使う必要はないと考え$(cat $1)
への変更を提案- ❎ それぞれの行に空白文字が含まれる場合を考慮してないので筋が悪い
- 対象ファイルのデータフォーマットを確かめておかないといけない
- 提案する側には当然のことなのでわざわざ書いてないだけかもしれない
- ❎ それぞれの行に空白文字が含まれる場合を考慮してないので筋が悪い
- 配列変数を作ること自体に意味があるわけじゃなかったので文字列ヒアドキュメントへの変更を提案
- ✅
echo
とパイプが減ったから見通しが良くなったような気がする
- ✅
for row in $(cat $1); do from=$(cut -d , -f 1 <<< $row); to=$(cut -d , -f 2 <<< $row); done
コードレビューの雑なまとめ : 製品コード(主に Java)
キャストは必ずチェックする
提案されたコードはこちら。
- (既存のコードより推測)
conditionB
が追加されたみたい- こちらも
source
がB
のインスタンスになるらしい
- こちらも
// 既存のコード if (conditionA) { A aObject = (A) source; return aObject.getKey(); } // 追加したコード if (conditionB) { B bObject = (B) source; return bObject.getKey(); }
修正の提案はこちら。
instanceof
演算子でチェックしてからキャストしましょう- キャストの失敗は実行時エラーになる (JVM が
ClassCastException
をスローする) - 動いてるから、とか、テストがあるから、とか、本来頼る必要のないところで保証しても仕方ない
- キャストの失敗は実行時エラーになる (JVM が
- 「 既存のコードベースからコピーしました、なんでそうしてるかは知りません」はやめましょう
- 同じロジックの重複が許されるのはそれなりに理由がある場合だけだと思います
- 「じゃあ既存のコードベースと同じだけテストしたんですか」と聞かれたらちゃんと答えられるでしょうか
// 既存のコード if (conditionA) { if (source instanceof A) { A keyObject = (A) source; return keyObject.getKey(); } } // 追加したコード if (conditionB) { if (source instanceof B) { B keyObject = (B) source; return keyObject.getKey(); } }
配列オブジェクトの拡張
提案されたコードはこちら。
- メソッド設計の都合で文字列引数と可変長引数を連結した配列オブジェクトにしたいらしい
public static String getMessage(String format, String apiName, Object... params) { List<Object> paramList = Arrays.stream(params).collect(Collectors.toList()); paramList.add(0, apiName); return String.format(format, paramList.toArray()); }
修正の提案はこちら。
Stream
同士は連結できるので利用しましょう
private static String getMessage(String format, String apiName, Object... params) { Object[] args = Stream.concat(Stream.of(apiName), Stream.of(params)).toArray(); return String.format(format, args); }
提案しなかった別案。
apiName
という引数の型は重要じゃなさそうなので可変長引数に押し込んでいいはず
private static String getMessage(String format, Object... args) { return String.format(format, args); }
org.apache.http.HttpStatus
より org.springframework.http.HttpStatus
を使う
提案されたコードはこちら。
- Web API の応答コードに対してエラーメッセージを生成したいようだ
import `org.apache.http.HttpStatus; public static String getHttpStatusMessage(String apiName, int httpStatus) { String format; switch (httpStatus) { case HttpStatus.SC_OK: return ""; case HttpStatus.SC_CONTINUE: case HttpStatus.SC_SWITCHING_PROTOCOLS: case HttpStatus.SC_PROCESSING: case HttpStatus.SC_CREATED: case HttpStatus.SC_ACCEPTED: case HttpStatus.SC_NON_AUTHORITATIVE_INFORMATION: case HttpStatus.SC_NO_CONTENT: case HttpStatus.SC_RESET_CONTENT: case HttpStatus.SC_PARTIAL_CONTENT: case HttpStatus.SC_MULTI_STATUS: format = HTTP_STATUS_UNEXPECTED_SUCCESS; break; case HttpStatus.SC_NOT_FOUND: format = HTTP_STATUS_404; break; default: format = HTTP_STATUS_ERROR; break; } return getMessage(format, apiName, httpStatus); }
修正の提案はこちら。
org.apache.http.HttpStatus
はほとんど素のままなので、少し賢いorg.springframework.http.HttpStatus
を使うようにしましょうorg.springframework.http.HttpStatus
は Spring MVC の提供するenum
です
import org.springframework.http.HttpStatus; public static String getHttpStatusMessage(String apiName, int httpStatus) { String format; HttpStatus status = HttpStatus.valueOf(httpStatus); if (status == HttpStatus.OK) { return ""; } else if (status.is1xxInformational() || status.is2xxSuccessful()) { format = HTTP_STATUS_UNEXPECTED_SUCCESS; } else if (status == HttpStatus.NOT_FOUND) { format = HTTP_STATUS_404; } else { format = HTTP_STATUS_ERROR; } return getMessage(format, apiName, httpStatus); }
有る無しを boolean
変数に反映するだけなら条件分岐する必要はない
提案されたコードはこちら。
- 配列オブジェクトへ安全にアクセスするため条件分岐している
boolean needSkip = false; if (parameters.length > 1) { needSkip = SKIP_VALUE.equals(parameters[1]); }
修正の提案はこちら。
- 先頭要素、という制約がないなら
Stream#anyMatch
で判断するほうが分かりやすいと思います
boolean needSkip = Stream.of(parameters).anyMatch(SKIP_VALUE::equals);
コレクションクラスのコンストラクタを知ろう
提案されたコードはこちら。
- 引数の
Map
にいろいろ追加したいらしい
private List<Something> method(Map<String, Object> param) { Map<String, Object> copyParam = new HashMap<>(); param.forEach(copyParam::put); copyParam.put("k1", v1); copyParam.put("k2", v2); copyParam.put("k3", v3); copyParam.put("k4", v4); copyParam.put("k5", v5); // copyParam を使った処理…
修正の提案はこちら。
HashMap
には複製したいMap
を引数にとるコンストラクタがあります
private List<Something> method(Map<String, Object> param) { Map<String, Object> copyParam = new HashMap<>(param); copyParam.put("k1", v1); copyParam.put("k2", v2); copyParam.put("k3", v3); copyParam.put("k4", v4); copyParam.put("k5", v5); // copyParam を使った処理…
提案しなかった別案。
- 分かりやすさは改善しないので無理に使わないほうがいいと思います
private List<Something> method(Map<String, Object> param) { Map<String, Object> copyParam = new HashMap<>(param); copyParam.putAll( Stream.of(new Object[][]{ { "k1", v1 }, { "k2", v2 }, { "k3", v3 }, { "k4", v4 }, { "k5", v5 }, }) .collect(Collectors.toMap(v -> v[0].toString(), v -> v[1])) ); // copyParam を使った処理…
Apache HttpComponents Client の発生する例外を捕まえたい
こういう既存実装(ログ出力にこだわりすぎだと思います)についてのやりとり。
- 疑問
- 外側の
catch
節に並んでる例外はどれもResponseHandler#handleResponse
でRuntimeException
としてスローされてくるんじゃないか- だとするとすべての
catch
節を通過して最後のException
に到達してしまうんじゃないか
- だとするとすべての
- このままで大丈夫なんでしょうか :question:
- 外側の
- 回答
ClientProtocolException, UnknownHostException, ConnectTimeoutException, SocketTimeoutException
をスローするのはhttpClient#execute
だから、すべてRuntimeException
になることはありません- 応答を受信した後で異常が発生したら、
RuntimeException
をスローします- 正常応答(200 OK)だけど本文が壊れていて
EntityUtils.toString
やXmlUtils.deserialize
が非検査例外をスローする可能性を考慮してるのかも - 正常応答以外の制御を本文が壊れている場合と合わせたかったのかも
- 正常応答(200 OK)だけど本文が壊れていて
try (CloseableHttpClient httpClient = HttpClientBuilder.create().setDefaultRequestConfig(requestConfig).build()) { SomeObject result = httpClient.execute(request, new ResponseHandler<SomeObject>() { @Override public SomeObject handleResponse(HttpResponse response) throws IOException { StatusLine statusLine = response.getStatusLine(); String statusMessage = getHttpStatusMessage(statusLine.getStatusCode()); if (statusMessage.isEmpty()) { String xmlString = EntityUtils.toString(response.getEntity(), StandardCharsets.UTF_8); return XmlUtils.deserialize(xmlString, SomeObject.class); } throw new RuntimeException(message); } }); return result; } catch (ClientProtocolException e) { LOG.error(e.getMessage(), e); throw new ApplicationException(e); } catch (UnknownHostException e) { LOG.error(getUnknownHostMessage()); throw new ApplicationException(e); } catch (ConnectTimeoutException e) { LOG.error(getConnectionTimeoutMessage()); throw new ApplicationException(e); } catch (SocketTimeoutException e) { LOG.error(getSocketTimeoutMessage()); throw new ApplicationException(e); } catch (Exception e) { LOG.error(e.getMessage(), e); throw new ApplicationException(e); }
Spring Framework の仕組みを学ぶ
相談されたコードはこちら。
外部から呼び出してるメソッド xxxx
から、@Transactional
を指定したメソッド yyyy
を呼び出してるのにトランザクションが開始せず、エラーになってしまうとか。
@Component public class XXXService { public void xxx() { // something yyyy() // XXX throws Exception } @Transactional public void yyyy() { // some database processing } }
- 発生している現象について
- Spring Framework は同一クラス(インスタンス)のメソッド呼び出しに介入しないため、トランザクションを開始するきっかけがない
- だからトランザクションが開始しない
- 解決策
- メソッド
yyyy
を別のクラスに移動して、そのクラスを DI して呼び出すようにしてください
- メソッド
- 理由
- 参考
@Component public class XXXService { private final YYYService yyySrevice; @Authwired public XXXService(YYYService yyySrevice) { this.yyySrevice = yyySrevice; } public void xxx() { // something yyyService.yyyy() // OK } } @Component public class YYYService { @Transactional public void yyyy() { // some database processing } }
RequestMapping を指定したメソッドに対する引数の割り当てと利用
相談されたコードはこちら。
RedirectAttributes
を引数に持つメソッドpost
からModel
を引数に持つメソッドinternal
を呼ぶと、ビューの描画が壊れてしまうModel
を引数に持つメソッドget
からModel
を引数に持つメソッドinternal
を呼ぶと、ビューの描画は正常
@Controller @RequestMapping("/item") public class ItemController { @GetMapping String get( @RequestParam(required = false) String message, Model model ) { model.addAttribute("message", message); return internal(model); } String internal(Model model) { if (!model.containsAttribute("message")) { model.addAttribute("message", "internal"); } model.addAttribute("someValue", "someValue"); return "index"; } @PostMapping( consumes = {MediaType.APPLICATION_JSON_VALUE} ) String post( @RequestBody Item item, RedirectAttributes redirectAttributes ) { if (item.getName() == null) { redirectAttributes.addAttribute("message", "no item"); return internal(redirectAttributes); } redirectAttributes.addAttribute("message", "success"); return "redirect:/item"; }
- 原因
- ビューを描画するときに使用する
Model
にアプリケーションが設定した値が反映されていないため
- ビューを描画するときに使用する
- 理由
Requestmapping
を指定したメソッドに引数を束縛するとき、Model
とRedirectAttributes
はModelAndViewContainer
の別のフィールドに格納するようになっている- ビューを描画するときは
Model
を使用する - つまり、
RedirectAttributes
を引数にするならリダイレクト以外のビュー解決ができなくなる
- 所見
- コントローラーのメソッドだけをユニットテストをしても気付けないと思う
- MockMVC でテストしてれば気付けると思う
説明変数が役に立ちそうな場面
提案されたコードはこちら。
ViewDto dto = new ViewDto(); if (params.getValue().equals(GlobalContext.getValue())) { // 条件が真の場合は非表示 dto.setDisplay(false); } else { dto.setDisplay(true); }
修正の提案はこちら。
ViewDto dto = new ViewDto(); boolean disableWhenOk = params.getValue().equals(GlobalContext.getValue()); dto.setDisplay(disableWhenOk);
そもそも表示・非表示のような制御変数を持ちまわすこと自体がいまいち
DTO だからメソッドを持ってはいけないと考えるのではなく、そういう役割のモデルの存在を明確にしたほうがいい
コードレビューの雑なまとめ : テストコード(主に Java)
Java の製品コードを Groovy でテストする
提案されたコードはこちら。
- Spring Framework の製品コードに対して Groovy + Spock で記述したテストコード
SomeLogic#createSomeInformation
がSomeRecord
からSomeInformation
を生成するみたい- 四捨五入の結果が同値になるかどうかを確かめてるみたい(推測)
def 'xxxとyyyには同じ編集ロジックが適用される'() { given: SomeLogic sut = new SomeLogic() SomeRecord record = new SomeRecord() record.xxx = "12.51" record.yyy = "13.49" when: SomeInformation actual = ReflectionTestUtils.invokeMethod(sut, 'createSomeInformation', record) then: actual.xxx == actual.yyy }
修正の提案はこちら。
- テスト対象クラスのオブジェクト(System Under Test) の準備はテストメソッドから分離したほうがいい
- 見通しがいいかどうかの問題
- 特に必要なリソースもないのでフィールド初期化で済ませてる
- 小数点以下の切り上げ・切り捨てにフォーカスしたデータテストへ変形
- メソッド名に変数名を埋め込むとテスト結果の確認が楽になる
with
ブロックでオブジェクトのフィールドを初期化する- 代入や setter メソッドの呼び出しがあるなら同じ場所に囲みたい
- テストメソッド内では
def
で変数を宣言する- 基本的に多くの仕事をしないのでクラス名を記述するのは冗長になりやすいから
- Groovy はプライベートメソッド・フィールドにアクセスできる
- 好ましい作法じゃないけどそういう言語仕様になっている
- この程度でリフレクション API を使う必要があるならテスト対象クラスの設計を見直したほうがいい
SomeLogic sut = new SomeLogic() @Unroll def 'SomeLogic は xxx と yyy を整数部だけにする: ケース:[#context] 入力:[#in] 出力:[#out]'() { given: def record = new SomeRecord().with { it.xxx = in it.yyy = in return it } when: def information = sut.createSomeInformation(record) then: information.xxx == out and: information.yyy == out and: information.xxx == information.yyy where: context | in | out '0.5未満は切り捨て' | '11.0' | '11' '整数部が奇数なら0.5以上は切り上げ' | '11.50' | '12' '整数部が偶数なら0.5以下は切り捨て' | '10.50' | '10' }
Bashの設定スクリプト集をBash-it向けに移植した
だいぶ前に、いろんな環境で汎用的に使えるBashのスクリプト集を作っていた。
docker
コマンドや kubectl
コマンドの接続先をいい感じに管理したくて、自分しか参照できない非公開の Dropbox や Google Drive に接続情報を置いて、必要に応じて半自動的に同期するようなことをしていた。大変すぎる。
SSHの公開鍵認証をエージェント経由で行うために、WSL debian なら ssh-add
で秘密鍵を登録する、Windows PC なら pageant
で秘密鍵を登録する、登録した鍵の一覧はどちらも ssh-add -l
で参照できる、みたいな場合分けなどもやっていた。ssh-pageant
というファイル名がいつもスペルチェッカーに怒られている。
それはそれとして、ちゃんとした技術情報の記事を書こうと思ったけど、いろんなものが動かなくなっていたので、先に環境を整えないといけなくなってしまった。
世の中の流行に乗ってみたくなったので awesome-bash を見てくることにする。
awesome-bash
awesome-bashのShell Package Management というセクションに、たぶん定番のライブラリが並んでいる。
名前が気に入ったので Bash-it を使うことにした。
Bash-it
oh-my-zsh のようにリポジトリをcloneする方式だった。 readthedocs にちゃんとしたドキュメントが整備されていて分かりやすい。
カスタマイズする方法は Custom Content — Bash-it documentation に書いてある。
BASH_IT_CUSTOM
という環境変数に指定したディレクトリへ置いてある *.bash
という名前のファイルを読み取ることになっている。
直下だけじゃなくて、もう一つ下の階層も探すようになっている。普通は直下を見るだけでいいと思うんだけど、なんで一つだけ下を見るようにしているんだろう。
https://github.com/Bash-it/bash-it/blob/master/bash_it.sh#L76-L86
# Custom _log_debug "Loading general custom files..." for _bash_it_main_file_custom in "${BASH_IT_CUSTOM}"/*.bash "${BASH_IT_CUSTOM}"/*/*.bash; do if [[ -s "${_bash_it_main_file_custom}" ]]; then _bash-it-log-prefix-by-path "${_bash_it_main_file_custom}" _log_debug "Loading custom file..." # shellcheck disable=SC1090 source "$_bash_it_main_file_custom" fi BASH_IT_LOG_PREFIX="core: main: " done
今の状態になる前は、/**/*.bash
という書き方をしていて、空白文字を含むユーザー名など展開に失敗する場合があるからときれいに書き直された結果、深いところまで探索する機能が失われていた。
コード品質は高まったからOKだと絶賛されている。
という状態なので、深い階層を作らないよう少し気にしながら自作のスクリプト集を移植した。
それぞれのスクリプトの中で alias を定義したいときや completion を定義したいときは文字列を source してるけど、本当は aliases/custom.aliases.bash
や completion/custom.completion.bash
から読み取らせたい。
試したけどちゃんと動かなかったので放り出してしまった。
git-cmd.exeのソースコードgit-wrapper.c を読んだ
Git for Windowsでは、コンソールプロセスで実行した対話型コマンド実行環境のpowershell.exeやcmd.exeやbash.exeから、キャラクターモードアプリケーションとしてgit.exeを実行する。
コンソールプロセスやキャラクターモードアプリケーションについてはMicrosoft Documentation(昔のMSDN?)に書かれてる。
そのために用意されているのが、同じgit-wrapper.cをコンパイル、リンクしたgit-cmd.exeやgit-bash.exeだった(cmd/git.exeもそうだとは知らなかった)。
Makefile
名前を頼りにリポジトリを眺めていると見つかる。
mingw-w64-git/mingw-w64-git.mak#L21-L25
git-bash.exe git-cmd.exe compat-bash.exe \ cmd/git.exe cmd/gitk.exe cmd/git-gui.exe: \ %.exe: git-wrapper.o git.res @mkdir -p cmd $(QUIET_LINK)$(CC) $(ALL_LDFLAGS) $(COMPAT_CFLAGS) -o $@ $^ -lshlwapi
git-wrapper.c
順番に読んでいるだけで、実際の使われ方と対応付けることはしてない。
- エントリポイントはL950のwmain
- 実体はL713のmain
- プロセスの実行ファイルの完全パスを突き止めたりしている
- 最初にリンクしたリソースファイルの内容から実行されているプロセスを区別している
- git-cmd.exeの引数
--no-cd
などはこの辺でチェックされている - 引数に
--command
を見つけたらその後ろはチェックしないようになっている - git-lfs.exeやscalar.exeとして実行されることもあるようになっている
- git.exeで実行されているときはここに入る
mingw64/bin/git.exe
が存在しないときはbin/git.exe
を使うようだ- 環境変数を整えてから最終的に実行するコマンド文字列を組み立てている
- git-cmd.exeで実行されているときはカレントディレクトリに置かれたgit.exeを実行してしまわないように工夫している
- doskeyのマクロとして設定されている?よくわからないな
- この辺でコマンドを実行する
- https://github.com/git-for-windows/MINGW-packages/blob/main/mingw-w64-git/git-wrapper.c#L912-L922
- CreateProcessはCマクロでCreateProcessAのユニコード版CreateProcessWになる
- 返り値はboolean
- プロセスの起動が成功したら
SIGINT
で停止できるようにしてからWaitForSingleObject
で完了を待つ
10年以上見ないふりをしてきた銀行預金を見直した
こういう話を相談できる人間関係を築いてこなかったので、1度考えたアイデアを無理やり忘れて、しばらくしてから客観的に見直す、みたいなことをしていた。
あまり考えが広がらないので試しにお金の健康診断で相談してみたら、相手の話を鵜呑みにすることなく、今どうなっていて、今後どうありたいかを納得できたような気がする。(なんなら危険性もあるんだろうけど今の私には役立つサービスだった)
今のストック
- 40代独身でギャンブルやらないとそれなりに貯まるものだった
- 転職するたびに口座を開設しているような気もする
気にしてるところ
- 万が一金融機関が破綻した時 : 預金保険機構
- 収入が途絶えてからの固定費の支払い
- 過去3年くらいの固定費と変動費を眺めると10年くらい猶予がある
- 普通は突然働かなくなることを想定しないと思う
- 突然働けなくなることは想定するとも思う
- そのための生命保険や傷病保険
- 何も考えてない…
- 過去3年くらいの固定費と変動費を眺めると10年くらい猶予がある
考え
- 保護からはみ出るかもしれない預金をどうにかする
- かといって、普段は為替や株価や金利の上下を気にしたくない
- 入金時の元本を保証する、積立型の終身保険がよさそうだと思ってる
- 極端な元本割れのリスクがある商品じゃなければいい
- 積立期間は何も意識しないで生活できるはず
商品の検討ポイント
- 積立期間は10年間(返戻額の控除が生じる期間)
- できれば10年間は(仮に収入が途絶えても)固定費の支払いのことを忘れたい
- そのためにできること
- 今のうちに必要な預金を確保する
- 収入が途絶えても安心なプラン
- 期間内に預金が無くなってから考える
- 考えることを先送りする少し不安なプラン
- 収入が途絶えていたら何かしないといけない
- 収入が途絶えていなければ何もしなくていい
- 今のうちに必要な預金を確保する
- ところで、収入が途絶えない(今の仕事を続けられる)自信はどれくらいあるだろう
- 1年 固い
- 3年 大丈夫だと思う
- 5年 不安
- 10年 何も分からない
Windows11へアップデートする、あるいは、Windows11でWindows Updateするときは、オプションのドライバやファームウェアも見てみよう
趣旨
- 😱困ったことは何?
- PCを無線LANに接続しているときの通信速度は100Mbps以上だったのに、突然6Kbpsになった😱
- 直前に何かやったんじゃないですか?
- Windows Updateを適用した
- 2022年9月から2022年10月末くらいに配信されたすべてのパッチで、どれが原因かはわからない
- なんで気付いたんですか?
- ログオン時に開くようにしていたSlackのウインドウが、白いままになっていた
- "ネットワークとインターネット"でWifi接続のプロパティを見たら、リンク速度が6000/300000Kpsになっていた
- ✅解消したんですか?
- 私の場合はWindows Updateで配布されていたPC本体のファームウェアのパッチを適用したら解消した✅
- Windows Updateの最新化だけでは解決できなかったと思う
- PCメーカーのサポートに問い合わせていたら多分全部初期化していそう
- 本体の物理的な故障のときはお世話になりました
- マイクロソフトのFAQやフォーラムがもっと充実して欲しい
問題解決までの経緯
問題の切り分け
- 無線LANの問題?違う
- PCの問題?たぶんそう
- 有線LANに接続しているPCでは、通信速度が100Mbps以上
トラブルシューティング
つまり思いついたことをいろいろやったということ。
- 富士通がこの型式のPC用に提供しているドライバーを適用:解決しない
- Windowsのトラブルシューティングツールを試す:特に意味はない
- もちろん何も問題を見つけられなかった
- 何を検証してるのかわからないけど、これまでに何か解決できた試しがない
- ✅Windows Updateの詳細オプションで、追加オプションの"オプションの更新プログラム"からめぼしいものを探して適用:解決した
- 私の環境ではIntelチップセット用のドライバやオーディオドライバが並んでいた
- 最初に
Fujitsu Firmware Update 1.19.0.0
というアイテムが気になったので適用した- メーカーのWebサイトにもFAQにも情報がなくて、何を適用したのかいまだにわからない
- Windows Updateの配布物一覧には
Client 1.31.0.0
が存在しているけど、やはり情報は少なすぎて更新する気になれない
- 他に並んでいるアイテムを適用しないのか?
- またおかしくなっても困るのでやらない
- 問題は解消したのでトラブルシューティングはここまで✅
環境
- PC本体はLIFEBOOK UHシリーズのWU2/C3(古い)
- インターネット接続事業者はNURO 光 for マンション
- レンタルしているONTはSGP200W
- よく当たりと称されている
- レンタルしているONTはSGP200W
TLA+に再挑戦(2022.5)
英語のドキュメント を読んだけどあまり理解できてなかったので 日本語 で再挑戦する会。
進捗
- 前回
- 「7章 アルゴリズム」を読んだり試したり
- 「8章 データ構造」を読み始めた
- 今回 2022.5 実践TLA+もくもく会 - connpass
- 「8.1 リンクリスト」の157ページ
ノードのサブセットをすべて生成し
まで
- 「8.1 リンクリスト」の157ページ
- 次回 2022.6 実践TLA+もくもく会 - connpass
- 「8.1 リンクリスト」の157ページ
リンクリストと名の付くものには開始点があるはずだ
から
- 「8.1 リンクリスト」の157ページ
8.1 リンクリスト
まず、ノード間で考えられるマッピングをすべて定義する
次に、「最後のノード」の概念が必要である
これだけで全ての要素の全ての組み合わせを定義したことになる。便利だ。
CONSTANT NULL PointerMaps(Nodes) == [Nodes -> Nodes \union {NULL}]
実は先に「8.2検証」まで読んでから戻ってきたのだけど、ようやく説明してることがわかってきた。
全ての要素の全ての組み合わせを定義してる
から それらのマッピングが全てリンクリストであるとは限らない
のか。
そこで次のようなアルゴリズムを導入しようとしている。
リンクリストのノードを「たどっていく」とシーケンスになることに着目するのである。
そうすると、与えられたPointerMapが単一のリンクリストである場合は、それ以降のすべての要素が
その手前の要素の次のノードであるというノードのシーケンスが得られる。たとえば、
リンクリストが[a |-> b, b |-> NULL, c |-> a] であるとすれば、シーケンスは<<c, a, b>>になる。
こういう解釈であってるのかな。
- 「それ以降の全ての要素」とは
a |-> b
b |-> NULL
c |-> a
のこと
- 「その手前の要素の次のノードであるというノード」とは
a |-> b
について- 「次のノード」が
a
になるのはc |-> a
だからc
- 「次のノード」が
b |-> NULL
について- 「次のノード」が
b
になるのはa |-> b
だからa
- 「次のノード」が
c |-> a
について- 「次のノード」が
c
になるノードはない、つまりNULL
だからb
- 「次のノード」が
- したがって、シーケンスは
<<c, a, b>>
になる
(1周目)以降の説明や実装がよくわからなかったので、「8.2検証」を読んだら戻ってくることにする。
---------------------------- MODULE LinkedLists ---------------------------- CONSTANT NULL LOCAL INSTANCE FiniteSets \* Cardinality LOCAL INSTANCE Sequences \* Len LOCAL INSTANCE TLC \* Assert \* 考えられるメモリマッピングの集合 \* まだこの演算子の役割がわからない LOCAL PointerMaps(Nodes) == [Nodes -> Nodes \union {NULL}] \* シーケンスをセットに変換する LOCAL Range(f) == {f[x]: x \in DOMAIN f} \* PointerMapはPointerMapsの要素 \* 与えられたPointerMapが単一のリンクリストになっていることをチェックする \* PointerMapが[a |-> b, b |-> NULL, c |-> a]なら <<c, a, b>> になっていることをチェックする LOCAL isLinkedList(PointerMap) == LET nodes == DOMAIN PointerMap \* 1..Cardinality(nodes) は 1..len(nodes) の範囲になる \* つまりnodesの全ての要素について、重複を許す全ての組み合わせのシーケンスのセットになる \* [1..3 -> {4,5,6}] なら {<<4,4,4>>,...,<<6,6,6>>} になる all_seqs == [1..Cardinality(nodes) -> nodes] \* PointerMapに含まれる全てのノードの全ての並び順を網羅したシーケンスから、 \* リンクリストの構成要件を満たすシーケンスが見つかれば、それはリンクリストだと判断できる IN \E ordering \in all_seqs: \* ordering[i]=a |-> bならPointerMap[ordering[i]]=c |-> aになる \* だからordering[i+1]=c |-> aになっていて欲しい /\ \A i \in 1..Len(ordering)-1: PointerMap[ordering[i]] = ordering[i+1] \* /\ \A n \in nodes: \* \E i \in 1..Len(ordering): \* ordering[i] = n \* マッピングのすべてのノードは ordering に含まれる \* Rangeはシーケンスのorderingをセットに変換する \* orderingはnodesの要素から生成した全ての組み合わせのシーケンスなので、 \* 一部の要素だけ選択した場合nodesが部分集合にならない場合がある /\ nodes \subseteq Range(ordering) \* リンクリスト演算子 LinkedLists(Nodes) == IF NULL \in Nodes THEN Assert(FALSE, "NULL cannot be in Nodes") ELSE {pm \in PointerMaps(Nodes): isLinkedList(pm)}
この時点で main モジュールから LinkedList モジュールを読み込んで、実行できるようになる。
そうすると Evaluate Constant Expression
でどういう結果になるのか観測できるようになる。
1度実行しないと Model Checking Results は出てこない。
-------------------------- MODULE main -------------------------- CONSTANTS NULL INSTANCE LinkedLists WITH NULL <- NULL
- Model Overview
- What is the behavior spec?:
No behavior spec
- What is the model?
NULL <- [model value]
- What is the behavior spec?:
Model Checking Results でこういう結果を観測できる。
Expression: LinkedLists({"a", "b"}) Value: { [a |-> NULL, b |-> "a"], [a |-> "a", b |-> "a"], [a |-> "b", b |-> NULL], [a |-> "b", b |-> "a"], [a |-> "b", b |-> "b"] }
これが
しかし、LinkedLists(Nodes) を呼び出してみると、長さが Cardinality(Nodes) のリンクリストが得られるだけである
ということなのかな。
NULL
で終端できてないことや、循環してることは問題ないんだろうか。
それらは後で記述されているから、ここではリストの長さが等しくなってしまう、つまり機能不足であることを問題視しているんだな。
考えられるノードのサブセットをすべて生成し、サブセットごとにポインタマップをすべて定義し、生成されたポインタマップのすべてで isLinkedList を呼び出せばよい
すると次のように変化した。なるほどバリエーションが出てきてよさそう。
Expression: LinkedLists({"a", "b"}) Value: { [a |-> NULL], [a |-> "a"], [b |-> NULL], [b |-> "b"], [a |-> NULL, b |-> "a"], [a |-> "a", b |-> "a"], [a |-> "b", b |-> NULL], [a |-> "b", b |-> "a"], [a |-> "b", b |-> "b"] }
開始点や循環リストの話は次回にする。
参考リンク
- Practical TLA+ | SpringerLink
- 原著
- 実践TLA+ 電子書籍|翔泳社の本
- 翻訳
- The TLA+ Home Page
- TLA+ の総本山的なWebページ