Created
June 26, 2019 03:18
-
-
Save ufcpp/b1959315f6dd093945781926afa9f355 to your computer and use it in GitHub Desktop.
36進数 Parse
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
using BenchmarkDotNet.Attributes; | |
using BenchmarkDotNet.Running; | |
using System; | |
using System.Linq; | |
using System.Runtime.CompilerServices; | |
public class Program | |
{ | |
private const byte X = 0xff; | |
private static ReadOnlySpan<byte> DigitTableROS => new byte[] { X, X, X, X, X, X, X, X, X, X, X, X, X, X, X, X, X, X, X, X, X, X, X, X, X, X, X, X, X, X, X, X, X, X, X, X, X, X, X, X, X, X, X, X, X, X, X, X, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, X, X, X, X, X, X, X, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, X, X, X, X, X, X, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, X, X, X, X, X, }; | |
// メソッドの中に直接 throw があるとインライン展開を阻害するらしいので、 | |
// インライン展開が掛かる前提 & 例外発生を通る確率が低い小さいメソッドではこんな感じで throw を外に追い出した方が速くなる。 | |
private static void Throw(char number) => throw new ArgumentException($"サポート外の値「{number}」です。(有効範囲は0-9A-Za-z)"); | |
// .NET Core 2.1 以降だとこれが最速。 | |
// Span 速い & DigitTableROS は最適化が掛かって配列の new 消える。 | |
[MethodImpl(MethodImplOptions.AggressiveInlining)] | |
static int GetDigitROS(char number) | |
{ | |
var i = (int)number; | |
if ((uint)i < 128U) | |
{ | |
var d = DigitTableROS[i]; | |
if (d != X) return d; | |
} | |
Throw(number); | |
return default; // ただ、残念なのが、こういう無駄 return が必要になること… | |
} | |
private static readonly byte[] DigitTableArray = new byte[] { X, X, X, X, X, X, X, X, X, X, X, X, X, X, X, X, X, X, X, X, X, X, X, X, X, X, X, X, X, X, X, X, X, X, X, X, X, X, X, X, X, X, X, X, X, X, X, X, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, X, X, X, X, X, X, X, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, X, X, X, X, X, X, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, X, X, X, X, X, }; | |
// 次点。テーブルが ReadOnlySpan じゃなくて static readonly 配列。初回に1回だけ128バイトのアロケーション発生。 | |
// .NET Core でなくても、「DigitTableROS は最適化が掛かって配列の new 消える」の最適化は C# コンパイラーがやってるので掛かるんだけど、 | |
// ただ、Span 自体が Unity だとちょっと遅いので、もしかしたらこっちの方が Unity だと速いかも。 | |
[MethodImpl(MethodImplOptions.AggressiveInlining)] | |
static int GetDigitArray(char number) | |
{ | |
var i = (int)number; | |
if ((uint)i < 128U) | |
{ | |
var d = DigitTableArray[i]; | |
if (d != X) return d; | |
} | |
Throw(number); | |
return default; | |
} | |
// 普通に条件分岐でやった場合その1。 | |
// テーブルを引くのと比べて結構遅い。3~4倍。 | |
[MethodImpl(MethodImplOptions.AggressiveInlining)] | |
static int GetDigitIf1(char number) | |
{ | |
if (number >= '0' && number <= '9') | |
{ | |
return number - '0'; | |
} | |
else if (number >= 'A' && number <= 'Z') | |
{ | |
return number - 'A' + 10; | |
} | |
else if (number >= 'a' && number <= 'z') | |
{ | |
return number - 'a' + 10; | |
} | |
Throw(number); | |
return default; | |
} | |
// 普通に条件分岐でやった場合その2。 | |
// uint 化して比較することで演算を減らしてる。その1よりもほんのちょっと速い。 | |
[MethodImpl(MethodImplOptions.AggressiveInlining)] | |
static int GetDigitIf2(char number) | |
{ | |
int tmp; | |
tmp = number - '0'; | |
if ((uint)tmp <= 9U) | |
{ | |
return tmp; | |
} | |
tmp = number - 'A'; | |
if ((uint)tmp <= 26U) | |
{ | |
return tmp + 10; | |
} | |
tmp = number - 'a'; | |
if ((uint)tmp <= 26U) | |
{ | |
return tmp + 10; | |
} | |
Throw(number); | |
return default; | |
} | |
// 以下、動作確認とベンチマーク。 | |
static void Main() | |
{ | |
var x = new Program(); | |
x.Setup(); | |
var a = x.If1(); | |
var b = x.If2(); | |
var c = x.ROS(); | |
var d = x.Array(); | |
if (a != b) throw new Exception(); | |
if (a != c) throw new Exception(); | |
if (a != d) throw new Exception(); | |
BenchmarkRunner.Run<Program>(); | |
} | |
char[] _testData; | |
[GlobalSetup] | |
public void Setup() | |
{ | |
var chars = new[] { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z' }; | |
var r = new Random(); | |
_testData = Enumerable.Range(0, 10000).Select(_ => chars[r.Next() % chars.Length]).ToArray(); | |
} | |
const int Base = 36; // 0~9 + a~z の36進数 | |
[Benchmark] | |
public int If1() | |
{ | |
var num = 0; | |
foreach (var i in _testData) num = num * Base + GetDigitIf1((char)i); | |
return num; | |
} | |
[Benchmark] | |
public int If2() | |
{ | |
var num = 0; | |
foreach (var i in _testData) num = num * Base + GetDigitIf2((char)i); | |
return num; | |
} | |
[Benchmark] | |
public int ROS() | |
{ | |
var num = 0; | |
foreach (var i in _testData) num = num * Base + GetDigitROS((char)i); | |
return num; | |
} | |
[Benchmark] | |
public int Array() | |
{ | |
var num = 0; | |
foreach (var i in _testData) num = num * Base + GetDigitArray((char)i); | |
return num; | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment