【Intellij IDEA(Ultimate)】Run/Debug ConfigurationsのEnvironment variables(に限らず任意の項目)が見つからない際の対処

  • Run/Debug ConfigurationsEnvironment variables(に限らず任意の項目)は表示されない状態になっていることが有る
    • 特にSpring Bootで遭遇しやすいかも
  • Modify optionsから設定を行うことで表示することができる

画像付きの操作方法は↓の記事の一部としてまとめてあります。

qiita.com

【Kotlin】ローカル関数の利用はパフォーマンス低下につながる

TL;DR

  • ローカル関数を利用するとパフォーマンスが低下する
  • このため、パフォーマンスが重要な場面ではローカル関数を利用すべきでない

文脈

日頃からお世話になっているintellij-rainbow-bracketsのコードを読んでいた所、以下のコードを見つけました。

github.com

このコードはローカル関数(関数内に定義する関数)を多用する書き方をしていますが、これがパフォーマンスに影響を与えないのか気になったため確認を行いました。

パフォーマンス低下を推測する根拠

上記のコードをJavaデコンパイルすると、以下のような表現になっています。

   public final void annotateUtil(/* */) {
      <undefinedtype> $fun$getBracketLevel$1 = new Function1() {
         public final int invoke(@NotNull LeafPsiElement element) {
            <undefinedtype> $fun$iterateParents$1 = new Function1() {
               public final void invoke(@NotNull final PsiElement currentNode) {
                  while(true) {
                     <undefinedtype> $fun$iterateChildren$1 = new Function1() {
                        public final void invoke(@NotNull PsiElement currentChild) {
                        }
                     };
                  }
               }
            };
         };
      };
   }

かなり要約していますが、ここからは以下のような問題点が見えます。

  • 関数呼び出し毎にローカル関数のnewが行われている
  • ループ(tailrec)内では、繰り返しローカル関数のnewが行われている

検証

検証のため、分割を行っていない状態と、分割後の状態とを再現し、それらに対するJMHベンチマークを作成しました。
コードは以下の通りです。

import org.openjdk.jmh.annotations.Benchmark
import org.openjdk.jmh.annotations.Scope
import org.openjdk.jmh.annotations.State

// ベンチマーク対象(分割後)
object Separated {
    private tailrec fun inner(i: Int, limit: Int): Int =
        if (i == limit) i else inner(i + 1, limit)

    private tailrec fun outer(i: Int, limit: Int): Int =
        if (i == limit) inner(i, limit) else outer(i + 1, limit)

    private fun caller(limit: Int) = outer(0, limit)

    // limit must be an integer greater than or equal to 1.
    fun target(limit: Int) = caller(limit)
}

// ベンチマーク対象(分割前)
object Combined {
    // limit must be an integer greater than or equal to 1.
    fun target(limit: Int): Int {
        fun caller(): Int {
            tailrec fun outer(i: Int): Int {
                tailrec fun inner(i: Int): Int =
                    if (i == limit) i else inner(i + 1)

                return if (i == limit) inner(i) else outer(i + 1)
            }
            return outer(0)
        }

        return caller()
    }
}

// ベンチマーク
@State(Scope.Benchmark)
open class Measurement {
    private val limit = 10

    @Benchmark
    open fun combined() = Combined.target(limit)

    @Benchmark
    open fun separated() = Separated.target(limit)
}

構築は下記を利用しています。

qiita.com

検証結果

build.gradle.ktsに以下の設定を入れてベンチマークを行いました。

jmh {
    warmupForks = 2
    warmupBatchSize = 3
    warmupIterations = 3
    warmup = "1s"

    fork = 2
    batchSize = 3
    iterations = 2
    timeOnIteration = "1500ms"

    failOnError = true
    isIncludeTests = false

    resultFormat = "CSV"
}

自分のPCで実行した結果は以下の通りです。
combinedが旧来のコードの再現で、separatedが分割後の想定です。
スコアは高いほど良いです。

Benchmark               Mode  Cnt         Score         Error  Units
Measurement.combined   thrpt    4  12430932.298 ±  581801.766  ops/s
Measurement.separated  thrpt    4  80820942.692 ± 2977174.695  ops/s

考察

推測通り、ローカル関数の利用によってパフォーマンスの問題が発生するようでした。
ベンチマーク内容について現実的な想定だと言い張るつもりは有りませんが、少なくともそれなりの影響は有ると言えます。

combinedinner関数を外に出すだけでもスコアが倍以上改善する様子も有りました。

まとめ

この記事ではintellij-rainbow-bracketsのコードを例にローカル関数の利用がパフォーマンス低下につながることを確認しました。
ローカル関数は便利な機能であるものの、パフォーマンスが重要な場面で利用することは避けた方が良いと思います。

終わりに

intellij-rainbow-bracketsのパフォーマンス改善も取り組もうと思っています。

最後に、intellij-rainbow-bracketsが継続的にやっていけるよう、スター・スポンサーもよろしくお願いします。

github.com

追記

改善やりました。
ソースを読んだ限りですが、C#Haskellは高速化すると思います。

github.com

【Kotlin coroutine/Java Reactor】Flow.groupByしたい場合Flux.groupByが使えるかも

記事執筆時点で、FlowにはgroupByが実装されていません。

kotlin.github.io

一方、FluxにはgroupByが実装されています。

projectreactor.io

kotlinx-coroutines-reactiveが入っているような環境では)FlowFluxは相互変換できるため、検討の余地が有ると思います。

【Marp】公式のダークテーマを使う

やり方

  1. marp: trueを設定する
  2. <!-- class: invert -->を設定する

設定後のヘッダーは↓のようになります。

---
marp: true
# header: 'header text'
# footer: '![height:50](image1.png)'
---
<!-- theme: default -->
<!-- class: invert -->
<!-- size: 16:9 -->
<!-- page_number: true -->
<!-- paginate: true -->

背景というか

やり方そのものは↓にちゃんと書いてあるんですが、自分は理解するのに時間がかかったので書きました。
こちらのページからは公式テーマの一覧も確認できます。 github.com

【日記】kotlin-reflectのvalue classを引数に含む関数呼び出しに関するバグ修正に携わった

↓の続きです。

wrongwrong163377.hatenablog.com

問題の内容

value classを引数に含む関数をkotlin-reflectを用いて呼び出す処理には、以下のような問題が存在していました。

これらの問題は、kotlin-reflectに依存するライブラリがvalue classをサポートする上でクリティカルな障害となっていました。
また、これらの問題を検知するためのテストパターンも不足している状況でした。

やったこと

Kotlinリポジトリに以下のPRを発行し、マージして頂きました。

これらの変更によって、ライブラリの実装を妨げるようなクリティカルな障害は解消されたと思われます。

終わりに

感想などは前回の記事に書いたので省略します。

リリースは今の所Kotlin 1.7になる予定です。
これによって様々なライブラリでvalue classサポートが進むと考えると今から非常に楽しみです。


追記

Kotlin 1.7でリリースされました!

github.com

【jOOQ】Recordからの値読み出しを省力化する【Kotlin】

定義したもの

import org.jooq.Field
import org.jooq.Record

inline fun <reified T> Record.read(field: Field<*>): T = this[field, T::class.java]

使い方

以下のように使います。

import org.jooq.Field
import org.jooq.Record

// FOOテーブルに定義された文字列型のFieldを想定
val field: Field<String?> = FOO.STRING_FIELD
// select結果などを保持するRecordを想定
val record: Record

// 以下のように、要求された型に応じた読み出しができる
val s1: String = record.read(field)
val s2: String? = record.read(field)
// エルビス演算子もよしなに判断してくれる
val s3: String = record.read(field) ?: ""
嬉しさ

jOOQがデフォルトで想定している読み出し方法の内最もシンプルなものは、Recordに定義された<T> T get(Field<T> field)を利用することです。
一方、KotlinGeneratorで生成されるコードではField(TableField)の型引数が常にnullableとなるため、non-nullな値として読み出す場合、一々!!で強制アンラップを行う必要が有ります。

// 単純にgetを用いた例
val s: String = record[field1]!!

また、Recordから何かしらの変換を伴う読み出しを行う場合は<U> U get(Field<?> field, Class<? extends U> type)を利用することになりますが、この関数はKClassを受け付けません。
このため、Kotlinから利用した際の見た目がよろしくありません。

// 変換を伴うgetを用いた例
import java.time.LocalDate

val d: LocalDate = record[field2, LocalDate::class.java]

今回定義した関数を使えば、両方ともシンプルかつ画一的な書き方ができます。
また、読み出し先がnullableになるような場合でも同様に書くことができます。

// 今回定義した関数を使った例
import java.time.LocalDate

val s: String = record.read(field1)
val d: LocalDate = record.read(field2)

// 要求される値がnullableな場合も同様に書ける
val s2: String? = record.read(field1)

クエリに対して期待される読み出し結果は基本的にマップ先の型に全て書かれているため、この関数を利用することで表現の重複を減らして機械的に書きやすくなります。

補足

関数の名前をreadにしているのは、getにするとjOOQデフォルトの内容と名前が被ってしまうためです。