Run/Debug Configurations
のEnvironment variables
(に限らず任意の項目)は表示されない状態になっていることが有る- 特に
Spring Boot
で遭遇しやすいかも
- 特に
Modify options
から設定を行うことで表示することができる
画像付きの操作方法は↓の記事の一部としてまとめてあります。
Run/Debug Configurations
のEnvironment variables
(に限らず任意の項目)は表示されない状態になっていることが有る
Spring Boot
で遭遇しやすいかもModify options
から設定を行うことで表示することができる画像付きの操作方法は↓の記事の一部としてまとめてあります。
io.r2dbc:r2dbc-postgresql
をorg.postgresql:r2dbc-postgresql
に直せば通ります。
r2dbc-postgresql
は0.9
以降でリポジトリの移行が行われています。
日頃からお世話になっているintellij-rainbow-brackets
のコードを読んでいた所、以下のコードを見つけました。
このコードはローカル関数(関数内に定義する関数)を多用する書き方をしていますが、これがパフォーマンスに影響を与えないのか気になったため確認を行いました。
上記のコードを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) }
構築は下記を利用しています。
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
推測通り、ローカル関数の利用によってパフォーマンスの問題が発生するようでした。
ベンチマーク内容について現実的な想定だと言い張るつもりは有りませんが、少なくともそれなりの影響は有ると言えます。
combined
のinner
関数を外に出すだけでもスコアが倍以上改善する様子も有りました。
この記事ではintellij-rainbow-brackets
のコードを例にローカル関数の利用がパフォーマンス低下につながることを確認しました。
ローカル関数は便利な機能であるものの、パフォーマンスが重要な場面で利用することは避けた方が良いと思います。
intellij-rainbow-brackets
のパフォーマンス改善も取り組もうと思っています。
最後に、intellij-rainbow-brackets
が継続的にやっていけるよう、スター・スポンサーもよろしくお願いします。
改善やりました。
ソースを読んだ限りですが、C#
とHaskell
は高速化すると思います。
記事執筆時点で、Flow
にはgroupBy
が実装されていません。
一方、Flux
にはgroupBy
が実装されています。
(kotlinx-coroutines-reactive
が入っているような環境では)Flow
とFlux
は相互変換できるため、検討の余地が有ると思います。
marp: true
を設定する<!-- 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
↓の続きです。
wrongwrong163377.hatenablog.com
value class
を引数に含む関数をkotlin-reflect
を用いて呼び出す処理には、以下のような問題が存在していました。
これらの問題は、kotlin-reflect
に依存するライブラリがvalue class
をサポートする上でクリティカルな障害となっていました。
また、これらの問題を検知するためのテストパターンも不足している状況でした。
Kotlin
のリポジトリに以下のPRを発行し、マージして頂きました。
JetBrains
側でより良い解決方法が出たため、マージされなかったこれらの変更によって、ライブラリの実装を妨げるようなクリティカルな障害は解消されたと思われます。
感想などは前回の記事に書いたので省略します。
リリースは今の所Kotlin 1.7
になる予定です。
これによって様々なライブラリでvalue class
サポートが進むと考えると今から非常に楽しみです。
Kotlin 1.7
でリリースされました!
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
デフォルトの内容と名前が被ってしまうためです。