1
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

(c#/Unity/IL2CPP/Mono)Enum:如何なる場面でも高速に動作するけれど、如何ほどのスピード狂でも使用を躊躇うイディオム「CEnum」の紹介

Last updated at Posted at 2024-07-20

前振り

普段、何気なく書いているコードですが、「環境Aでは高速に動作するのに、環境Bで実行してみたら遅くなった」という経験をされた方いませんか?

或いは、「環境Aで高速に動作するから、環境Bでも大丈夫」と高を括り、知らない内に環境Bでは遅く動いている、なんて事もあるかも知れませんが、心当たりのある方いませんか?

今回は、Enumに纏わるお話です。
たかがEnum、されどEnum、今一度Enumを見直す機会と捉えてご覧頂けると幸いです。

本題

Enumは、環境だけでなく、ユースケースでも実行パフォーマンスにバラつきがある

Enumには、主に三通りの扱い方があります。

  • enum
  • System.Enum
  • where System.Enum

これらは、Enumを使う上で付いて回るものなので、回避のしようがありません。

よく使われるケースは、以下の3つでしょうか。

  • 配列の添え字(intへのキャスト)
  • switch case
  • Flags属性

つまりは、以下の表の空欄毎に実行パフォーマンスが異なり、更に環境毎でも異なるという話です。

パフォーマンス表 enum System.Enum where System.Enum
配列の添え字(intへのキャスト)
switch case
Flags属性

では、この問題をどう解決するか

遅くなりようの無いコードを書く、それだけです。

しかし、ただ利点を享受するだけでは済まないのが、今回紹介するイディオム「CEnum」です。
このイディオムには2つ欠点がありますが、ともあれ話を進めます。

先ずは、基底classです。

public class CEnum<T>//型不定の基底class
{
	public readonly T Value;
	
	protected CEnum(T v){ Value = v; }
}

public class CEnum : CEnum<int>//型確定した基底class
{
	public const int Length = 0;//このCEnumの長さ
	
	public const int Default = 0; public static readonly CEnum EDefault = new(Default);//Defaultの定義
	
	protected CEnum(int v):base(v){}
}

CEnumのユーザー定義はこの様になります。

public class EUser : CEnum//ユーザー定義class
{
	public new const int Length = CEnum.Length + 2;//このCEnumの長さ
	
	public const int Hoge = 0; public static readonly EUser EHoge = new(Hoge);//要素の定義
	public const int Fuga = 1; public static readonly EUser EFuga = new(Fuga);//要素の定義
	
	protected EUser(int v):base(v){}
}

継承めいた事も出来ます。

public class ETest : EUser//EUserの継承class
{
	public new const int Length = EUser.Length + 1;//このCEnumの長さ
	
	public const int Piyo = 2; public static readonly ETest EPiyo = new(Piyo);//要素の定義
	
	protected ETest(int v):base(v){}
}

Flags属性めいた事も出来ます。

public class EFlags : CEnum//Flags属性の真似事class
{
	public new const int Length = CEnum.Length + 2;//このCEnumの長さ
	
	public const int IsHoge = (1<<0); public static readonly EFlags EIsHoge = new(IsHoge);//要素の定義
	public const int IsFuga = (1<<1); public static readonly EFlags EIsFuga = new(IsFuga);//要素の定義
	
	protected EFlags(int v):base(v){}
}

そう、とにかく面倒くさい事この上ないのが、このイディオム1つ目の欠点です。

面倒くさいの一言で片付けましたが、色々と言いたい事はあるでしょう。
まぁ今は、そうした事は全て飲み込んで頂いて、話を進めて行きますね。

CEnumの使い方

CEnumでは、要素の扱い方が二通りあります。

  • 確定型を使う場合
    • public constで定義した名前を使う
      • 利点:定数値を直接扱える
      • 欠点:CEnum型情報を失う(whereで制約できない)
  • CEnum型を使う場合
    • public static readonlyで定義した名前を使う
      • 利点:whereで制約できる
      • 欠点:CEnumの運用ルールを覚える必要がある(このイディオム2つ目の欠点)

CEnumの運用ルール

確定型を使う場面

//定数値を配列の添え字に使用(Enumとは異なり、intへのキャストは不要)
SomeArray[EUser.Hoge] = SomeValue;
//caseの条件
switch (){
    case EUser.Hoge: break;
    case EUser.Fuga: break;
}
//Flags
if (SomeFlags & EFlags.IsHoge){}
if (SomeFlags & EFlags.IsFuga){}

CEnum型を使う場面

//変数に取る
var e = EUser.EHoge;
//変数値を配列の添え字に使用(Valueメンバ変数を使う)
SomeArray[e.Value] = SomeValue;
//ジェネリック メソッドの引数に渡す
SomeGenericMethod(EUser.EHoge);
SomeGenericMethod(e);
//switchの因子(Valueメンバ変数を使う)
switch (e.Value){
}

ジェネリック(Generic)の型制約

CEnumは、whereで制約できるので、継承めいた事をする時に役立ちます。

void SomeGenericMethod<E>(E e) where E : EUser
{
}

ベンチマーク

環境依存になるので、あくまで参考程度です。
横軸は秒で、グラフが短いほど高速です。

  • 列挙型種
    • CEnum:CEnumを扱った場合
    • enum:enumを扱った場合
    • Enum:enumをSystem.Enumとして扱った場合
    • where:enumをwhere System.Enumで扱った場合
  • テスト内容
    • value:列挙型種をintとして扱った場合
    • switch:列挙型種をswitch caseで扱った場合

Visual Studio 2022(10,000,000回ループ).png

Unity 2022.3.35f1 IL2CPP(1,000,000回ループ).png

Unity 2022.3.35f1 Mono(1,000,000回ループ).png

サンプルコードを用意したので、気になる方はお試し下さい。

まとめ

c#標準の列挙型を使う場合、enumで使えるケースはさほど多くなく、大抵の場合はwhere System.Enumで使われ、System.Enumは止む無く使われる、という温度感だと思います。

コード中にあるSystem.Enumキーワードは、そこに多大な負荷が掛かるリスクを示している、ということは覚えておいて損はないでしょう。

CEnumの利点は、どの様な場合においても、enumと同等の速度で安定していることです。

余談

如何だったでしょうか?

幾つか欠点はありますが、Source GeneratorsでenumからCEnumに自動変換できるなら、割と有用かも知れないなーと思いつつ、勇者が現れることを期待して、今回はこれにて終了と致します。

最後までご覧いただき、有難う御座いました。

ところで、CEnumのCって何?

光速の定数c
これ以上は速くならない、という意味で肖らせてもらおうかと。

1
1
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
1
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?