mapとforeachのベンチマーク

趣旨

  • mapforeachのどちらが効率的かは昔から話題になっている
  • 可読性を考えたらforeach一択なんだけど、mapのほうが効率的な場合があるかもしれない
    • 変数を明示的に作る場合と、暗黙的に作る場合で何か違うかもしれない
  • 確認してみよう

参考リンク

環境

バイス

プロセッサ   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が優勢

容量が大きくなるとメモリの確保に時間がかかるだけのような気がする。

配列に要素を追加するケース

steph-skardal.pl

  • 配列の容量が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%      --

配列の要素を変更するケース

wim-vanderbauwhede.pl

  • 配列の容量が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で生成したものです

オンライン会議用の背景画像を生成するやつを作った - hitode909の日記

この文章の目的

  • 放置していたテキストを発掘してるだけ
  • 具体的なことは何も覚えてない
  • たぶん他の人がコードレビューしている様子を見て、自分ならこうかな、と書いていたんだと思う
  • 昔の自分はちゃんとフィードバック出来ていたのかは気がかり

コードレビューをさせていただく中で「こうしたほうがいいと思うよ」という内容がいくつかあったのでまとめてみます。

コードレビューの雑なまとめ : シェルスクリプト(主に 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 が追加されたみたい
  // 既存のコード
  if (conditionA) {
    A aObject = (A) source;
    return aObject.getKey();
  }

  // 追加したコード
  if (conditionB) {
    B bObject = (B) source;
    return bObject.getKey();
  }

修正の提案はこちら。

  • instanceof 演算子でチェックしてからキャストしましょう
    • キャストの失敗は実行時エラーになる (JVMClassCastException をスローする)
    • 動いてるから、とか、テストがあるから、とか、本来頼る必要のないところで保証しても仕方ない
  • 「 既存のコードベースからコピーしました、なんでそうしてるかは知りません」はやめましょう
    • 同じロジックの重複が許されるのはそれなりに理由がある場合だけだと思います
    • 「じゃあ既存のコードベースと同じだけテストしたんですか」と聞かれたらちゃんと答えられるでしょうか
  // 既存のコード
  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 の発生する例外を捕まえたい

Apache HttpComponents Client

こういう既存実装(ログ出力にこだわりすぎだと思います)についてのやりとり。

  • 疑問
    • 外側の catch 節に並んでる例外はどれも ResponseHandler#handleResponseRuntimeException としてスローされてくるんじゃないか
      • だとするとすべての catch 節を通過して最後の Exception に到達してしまうんじゃないか
    • このままで大丈夫なんでしょうか :question:
  • 回答
    • ClientProtocolException, UnknownHostException, ConnectTimeoutException, SocketTimeoutException をスローするのは httpClient#execute だから、すべて RuntimeException になることはありません
    • 応答を受信した後で異常が発生したら、RuntimeException をスローします
      • 正常応答(200 OK)だけど本文が壊れていて EntityUtils.toStringXmlUtils.deserialize が非検査例外をスローする可能性を考慮してるのかも
      • 正常応答以外の制御を本文が壊れている場合と合わせたかったのかも
        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
    }
}
@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 を指定したメソッドに引数を束縛するとき、ModelRedirectAttributesModelAndViewContainer の別のフィールドに格納するようになっている
    • ビューを描画するときは Model を使用する
    • つまり、RedirectAttributes を引数にするならリダイレクト以外のビュー解決ができなくなる
  • 所見
    • コントローラーのメソッドだけをユニットテストをしても気付けないと思う
    • MockMVC でテストしてれば気付けると思う

説明変数が役に立ちそうな場面

提案されたコードはこちら。

  • UI 要素の表示・非表示を DTOboolean 型のフィールドで制御してる
  • ビジネスロジックの返り値 (boolean) を反転した値をフィールドに設定してる
ViewDto dto = new ViewDto();

if (params.getValue().equals(GlobalContext.getValue())) {
    // 条件が真の場合は非表示
    dto.setDisplay(false);
} else {
    dto.setDisplay(true);
}

修正の提案はこちら。

  • リテラルtrue/false はなんかいや
  • かといって setter に指定する値を否定演算子で逆転するのもいや
  • 日本語でコメント書くくらいなら説明変数を追加したほうがいい
ViewDto dto = new ViewDto();

boolean disableWhenOk = params.getValue().equals(GlobalContext.getValue());
dto.setDisplay(disableWhenOk);

そもそも表示・非表示のような制御変数を持ちまわすこと自体がいまいち

DTO だからメソッドを持ってはいけないと考えるのではなく、そういう役割のモデルの存在を明確にしたほうがいい

コードレビューの雑なまとめ : テストコード(主に Java)

Java の製品コードを Groovy でテストする

提案されたコードはこちら。

  • Spring Framework の製品コードに対して Groovy + Spock で記述したテストコード
  • SomeLogic#createSomeInformationSomeRecord から 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スクリプト集を作っていた。

github.com

docker コマンドや kubectl コマンドの接続先をいい感じに管理したくて、自分しか参照できない非公開の DropboxGoogle 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

github.com

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だと絶賛されている。

github.com

という状態なので、深い階層を作らないよう少し気にしながら自作のスクリプト集を移植した。 それぞれのスクリプトの中で alias を定義したいときや completion を定義したいときは文字列を source してるけど、本当は aliases/custom.aliases.bashcompletion/custom.completion.bash から読み取らせたい。 試したけどちゃんと動かなかったので放り出してしまった。

github.com

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

順番に読んでいるだけで、実際の使われ方と対応付けることはしてない。

mingw-w64-git/git-wrapper.c

10年以上見ないふりをしてきた銀行預金を見直した

こういう話を相談できる人間関係を築いてこなかったので、1度考えたアイデアを無理やり忘れて、しばらくしてから客観的に見直す、みたいなことをしていた。

あまり考えが広がらないので試しにお金の健康診断で相談してみたら、相手の話を鵜呑みにすることなく、今どうなっていて、今後どうありたいかを納得できたような気がする。(なんなら危険性もあるんだろうけど今の私には役立つサービスだった)

今のストック

  • 40代独身でギャンブルやらないとそれなりに貯まるものだった
  • 転職するたびに口座を開設しているような気もする

気にしてるところ

  • 万が一金融機関が破綻した時 : 預金保険機構
  • 収入が途絶えてからの固定費の支払い
    • 過去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用に提供しているドライバーを適用:解決しない
    • メーカー製のPCを使っているとこういうときに助かる
    • 無線LANドライバ、Bluetoothドライバ、オーディオドライバを更新した
  • Windowsトラブルシューティングツールを試す:特に意味はない
    • もちろん何も問題を見つけられなかった
    • 何を検証してるのかわからないけど、これまでに何か解決できた試しがない
  • Windows Updateの詳細オプションで、追加オプションの"オプションの更新プログラム"からめぼしいものを探して適用:解決した
    • 私の環境ではIntelチップセット用のドライバやオーディオドライバが並んでいた
    • 最初に Fujitsu Firmware Update 1.19.0.0 というアイテムが気になったので適用した
      • メーカーのWebサイトにもFAQにも情報がなくて、何を適用したのかいまだにわからない
      • Windows Updateの配布物一覧には Client 1.31.0.0 が存在しているけど、やはり情報は少なすぎて更新する気になれない
    • 他に並んでいるアイテムを適用しないのか?
      • またおかしくなっても困るのでやらない
    • 問題は解消したのでトラブルシューティングはここまで✅

環境

TLA+に再挑戦(2022.5)

英語のドキュメント を読んだけどあまり理解できてなかったので 日本語 で再挑戦する会。

進捗

8.1 リンクリスト

  • ユースケース
    • 循環を持つリンクリストをアルゴリズムが生成しないようにする。
      • 検証したいのはアルゴリズムで、リンクリストを部品として使用するっぽい
    • 循環を見つけ出すアルゴリズムを記述する。
      • リンクリスト自体を検証したいっぽい
    • 循環を持つリンクリストが与えられてもシステムが正しく動作するようにする。
      • 検証したいのはシステムで、リンクリストを部品として使用するっぽい

まず、ノード間で考えられるマッピングをすべて定義する
次に、「最後のノード」の概念が必要である

これだけで全ての要素の全ての組み合わせを定義したことになる。便利だ。

CONSTANT NULL
PointerMaps(Nodes) == [Nodes -> Nodes \union {NULL}]

PointerMapsは考えられるメモリマッピングの集合だが、それらのマッピングが全てリンクリストであるとは限らない

実は先に「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]

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"] }

開始点や循環リストの話は次回にする。

参考リンク