6
3

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

compose.ui.utilにあるfastXXXのリスト操作のAPIを見てみる

Last updated at Posted at 2023-05-08

androidx.compose.ui.util のライブラリに fastXXX というリストを操作する拡張関数が用意されています。ライブラリに依存することでアプリの実装でも使うことはできます。

これらの API は通常の forXXX と何が違うのか調べてみました。

コードから見る forXXX との違い

forEach との違い

fastForEach のコードを見てみます。

@Suppress("BanInlineOptIn")
@OptIn(ExperimentalContracts::class)
inline fun <T> List<T>.fastForEach(action: (T) -> Unit) {
    contract { callsInPlace(action) }
    for (index in indices) {
        val item = get(index)
        action(item)
    }
}

通常の forEach はこのようになっています。

@kotlin.internal.HidesMembers
public inline fun <T> Iterable<T>.forEach(action: (T) -> Unit): Unit {
    for (element in this) action(element)
}

ぱっと実装を見ただけでは違いが分かりにくいので、コンパイルされたコードも見てみましょう。
以下のようなコードを試しに見比べてみます。

val list = ...
list.fastForEach {
    val s = it.toString()
}
list.forEach {
    val s = it.toString()
}

fastForEach は inline 関数なので以下のように展開されます。

List $this$fastForEach$iv = list;
int $i$f$forEach = false;
index$iv = 0;

for(int var7 = list.size(); index$iv < var7; ++index$iv) {
 Object item$iv = $this$fastForEach$iv.get(index$iv);
 int it = ((Number)item$iv).intValue();
 int var10 = false;
 String var11 = String.valueOf(it);
}

forEach も inline 関数なので以下のように展開されます。

Iterable $this$forEach$iv = (Iterable)list;
$i$f$forEach = false;

int it;
String var21;
for(Iterator var17 = $this$forEach$iv.iterator(); var17.hasNext(); var21 = String.valueOf(it)) {
 Object element$iv = var17.next();
 it = ((Number)element$iv).intValue();
 var9 = false;
}

fastForEach と forEach の違いは Iterator を使うか否かのみの差になっています。
この Iterator を使わないという背景には Iterator の割り当てや GC の回収を減らすことによるパフォーマンス改善といった背景があるそうです。

map との違い

fastMap のコードを見てみます。

@Suppress("BanInlineOptIn")
@OptIn(ExperimentalContracts::class)
inline fun <T, R> List<T>.fastMap(transform: (T) -> R): List<R> {
    contract { callsInPlace(transform) }
    val target = ArrayList<R>(size)
    fastForEach {
        target += transform(it)
    }
    return target
}

通常の map はこのようになっています。

public inline fun <T, R> Iterable<T>.map(transform: (T) -> R): List<R> {
    return mapTo(ArrayList<R>(collectionSizeOrDefault(10)), transform)
}

public inline fun <T, R, C : MutableCollection<in R>> Iterable<T>.mapTo(destination: C, transform: (T) -> R): C {
    for (item in this)
        destination.add(transform(item))
    return destination
}

fastMap は内部で fastForEach を使用していたり、変換後に格納する ArrayList の配列を List のサイズ分確保していたりといった差が見られます。
コンパイルされたコードも見てみましょう。
以下のようなコードを試しに見比べてみます。

val list = ...
list.fastMap {
    it.toString()
}
list.map {
    it.toString()
}

fastMap は以下のようになります。

int $i$f$map = false;
ArrayList target$iv = new ArrayList(list.size());
List $this$fastForEach$iv$iv = list;
int $i$f$mapTo = false;
int index$iv$iv = 0;

String var27;
for(int var10 = list.size(); index$iv$iv < var10; ++index$iv$iv) {
 Object item$iv$iv = $this$fastForEach$iv$iv.get(index$iv$iv);
 int var13 = false;
 Collection var10000 = (Collection)target$iv;
 int it = ((Number)item$iv$iv).intValue();
 Collection var16 = var10000;
 int var15 = false;
 var27 = String.valueOf(it);
 var16.add(var27);
}

次に map は以下のようになります。

Iterable $this$map$iv = (Iterable)list;
int $i$f$map = false;
Collection destination$iv$iv = (Collection)(new ArrayList(CollectionsKt.collectionSizeOrDefault($this$map$iv, 10)));
int $i$f$mapTo = false;
Iterator var26 = $this$map$iv.iterator();

while(var26.hasNext()) {
 item$iv$iv = var26.next();
 int it = ((Number)item$iv$iv).intValue();
 var14 = false;
 var27 = String.valueOf(it);
 destination$iv$iv.add(var27);
}

fastMapmap の違いは fastForEach の Iterator を使うか否かであったり、配列の確保あたりが異なっています。

fastXXX の API はいつ使うのか

fastXXX の API はドキュメントに以下のように書いています。

Do not use for collections that come from public APIs, since they may not support random access in an efficient way, and this method may actually be a lot slower. Only use for collections that are created by code we control and are known to support random access.

これらの fastXXX の API は Compose のライブラリ内で使用されており、ライブラリ提供者のユーザに公開しない内部でのみパフォーマンスで改善が見込める条件で使うことが想定されています。

fastXXX の API は基本的にアプリを開発する上では使うことはなく、パフォーマンスを改善したいとなった場合に使うことを検討するくらいでしょう。

まとめ

  • fastXXX は Iterator を使わないことで割り当てや GC を回避してパフォーマンスを改善できる API
  • 基本的にアプリを開発する上では使うことはなく、ライブラリ提供者が API として公開されない部分で使用することが想定されている
6
3
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
6
3

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?