Skip to content

Instantly share code, notes, and snippets.

@ufcpp
Created June 26, 2019 03:18
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save ufcpp/b1959315f6dd093945781926afa9f355 to your computer and use it in GitHub Desktop.
Save ufcpp/b1959315f6dd093945781926afa9f355 to your computer and use it in GitHub Desktop.
36進数 Parse
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